feat 表格相关hooks调试, 新增查询器组件

This commit is contained in:
xxm
2022-10-07 21:30:21 +08:00
parent be13e7b425
commit 79bc023011
24 changed files with 461 additions and 3354 deletions

View File

@@ -31,14 +31,14 @@ module.exports = {
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
0,
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'no-unused-vars': [
'error',
0,
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',

View File

@@ -0,0 +1,82 @@
<template>
<a-form class="query" layout="inline">
<a-row :gutter="gutter">
<query-item
:key="i"
v-show="i < defaultItemCount || toggleSearchStatus"
v-for="(field, i) in fields"
:field="field"
:md="defaultItemMd"
:query-params="queryParams"
/>
<a-col :md="defaultItemMd" :sm="24">
<a-space>
<a-button type="primary" :disabled="disabledQuery" @click="query">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
<a v-show="fields.length > defaultItemCount" @click="handleToggleSearch" style="margin-left: 8px">
{{ toggleSearchStatus ? '收起' : '展开' }}
<up-outlined v-if="toggleSearchStatus" />
<down-outlined v-else />
</a>
</a-col>
</a-row>
</a-form>
</template>
<script lang="ts" setup>
import QueryItem from './QueryItem.vue'
import { ref } from 'vue'
import { DownOutlined, UpOutlined } from '@ant-design/icons-vue'
// 切换搜索条件展开状态
let toggleSearchStatus = ref(false)
const props = defineProps({
fields: {
type: Array,
default: () => {
return []
},
},
// 查询条件
queryParams: {
type: Object,
required: true,
},
// 默认展示几个
defaultItemCount: { type: Number, default: 2 },
defaultItemMd: { type: Number, default: 6 },
// 禁用查询
disabledQuery: { type: Boolean, default: false },
gutter: { type: Number, default: 10 },
})
const emits = defineEmits(['update:modelValue', 'query', 'reset'])
/**
* 查询
*/
function query() {
emits('query')
}
/**
* 重置
*/
function reset() {
emits('reset')
}
/**
* 切换搜索条件展开状态
*/
function handleToggleSearch() {
toggleSearchStatus.value = !toggleSearchStatus.value
}
</script>
<style lang="less" scoped>
/deep/ .ant-form-item {
margin-bottom: 8px;
}
.ant-row {
width: 100%;
}
</style>

View File

@@ -0,0 +1,89 @@
<template>
<a-col :md="field.md || md" :sm="24">
<a-form-item :label="field.name">
<!-- 文本输入 -->
<a-input
allowClear
v-if="field.type === STRING"
:placeholder="field.placeholder ? field.placeholder : '请输入查询值'"
v-model:value="queryParams[field.field]"
/>
<!-- 数字输入 -->
<a-input-number
allowClear
style="width: 100%"
v-else-if="field.type === NUMBER"
:placeholder="field.placeholder ? field.placeholder : '请输入查询值'"
:precision="field.precision ? field.precision : 0"
v-model:value="queryParams[field.field]"
/>
<!-- 布尔 -->
<a-radio-group v-else-if="field.type === BOOLEAN" v-model:value="queryParams[field.field]">
<a-radio :value="true"></a-radio>
<a-radio :value="false"></a-radio>
</a-radio-group>
<!-- 列表 -->
<a-select
allowClear
v-else-if="field.type === LIST"
:placeholder="field.placeholder ? field.placeholder : '请选择查询值'"
v-model:value="queryParams[field.field]"
:options="field.list"
/>
<!-- 日期 -->
<a-date-picker
allowClear
v-else-if="field.type === DATE"
style="width: 100%"
:placeholder="field.placeholder ? field.placeholder : '请选择日期'"
:valueFormat="queryParams.format ? queryParams.format : 'yyyy-MM-DD'"
v-model:value="queryParams[field.field]"
/>
<!-- 时间 -->
<a-time-picker
allowClear
v-else-if="field.type === TIME"
style="width: 100%"
:placeholder="field.placeholder ? field.placeholder : '请选择时间'"
:valueFormat="queryParams.format ? queryParams.format : 'HH:mm:ss'"
v-model:value="queryParams[field.field]"
/>
<!-- 日期时间 -->
<a-date-picker
allowClear
showTime
v-else-if="field.type === DATE_TIME"
style="width: 100%"
:placeholder="field.placeholder ? field.placeholder : '请选择日期时间'"
:valueFormat="queryParams.format ? queryParams.format : 'yyyy-MM-DD HH:mm:ss'"
v-model:value="queryParams[field.field]"
/>
<!-- 默认文本输入 -->
<a-input
allowClear
v-else
:placeholder="field.placeholder ? field.placeholder : '请输入查询值'"
v-model:value="queryParams[field.field]"
/>
</a-form-item>
</a-col>
</template>
<script lang="ts" setup>
import { BOOLEAN, DATE, DATE_TIME, LIST, NUMBER, QueryField, STRING, TIME } from './SuperQueryCode'
const props = withDefaults(
defineProps<{
// 查询字段属性
field: QueryField
// 查询条件
queryParams: object
// md 栅格占位
md: number
}>(),
{
md: 6,
},
)
</script>
<style scoped></style>

View File

@@ -0,0 +1,20 @@
// 数字
export const NUMBER = 'number'
// 字符串
export const STRING = 'string'
// 布尔
export const BOOLEAN = 'boolean'
// 日期
export const DATE = 'date'
// 时间
export const TIME = 'time'
// 日期时间
export const DATE_TIME = 'date_time'
// 列表
export const LIST = 'list'
export interface QueryField {
type: string
placeholder: string
field: string
}

View File

@@ -9,10 +9,34 @@ import 'vxe-table/lib/style.css'
*/
export function useTable(app: App) {
app.use(VXETable)
// 给 vue 实例挂载内部对象,例如:
// app.config.globalProperties.$XModal = VXETable.modal
// app.config.globalProperties.$XPrint = VXETable.print
// app.config.globalProperties.$XSaveFile = VXETable.saveFile
// app.config.globalProperties.$XReadFile = VXETable.readFile
}
/**
* 配置
*/
VXETable.setup({
// 表格配置
table: {
resizable: true,
border: true,
stripe: true,
showOverflow: true,
showHeaderOverflow: true,
size: 'medium',
tooltipConfig: {
enterable: true,
},
},
// 工具条配置
toolbar: {
size: null,
custom: true,
buttons: [],
tools: [],
},
// 分页配置
pager: {
border: true,
size: 'medium',
},
})

View File

@@ -1,7 +1,58 @@
import type { App } from 'vue'
import { Button } from './Button'
import { Input, Layout } from 'ant-design-vue'
import {
Layout,
Input,
InputNumber,
Empty,
Select,
Tree,
TreeSelect,
Card,
Form,
Row,
Col,
Modal,
Dropdown,
Radio,
Steps,
Spin,
Menu,
Drawer,
Tooltip,
Tag,
Divider,
DatePicker,
TimePicker,
Descriptions,
Space,
} from 'ant-design-vue'
export function registerGlobComp(app: App) {
app.use(Input).use(Button).use(Layout)
app.use(Input)
app.use(Button)
app.use(Layout)
app.use(InputNumber)
app.use(Tag)
app.use(Space)
app.use(Modal)
app.use(Drawer)
app.use(Row)
app.use(Col)
app.use(Radio)
app.use(Divider)
app.use(DatePicker)
app.use(TimePicker)
app.use(Empty)
app.use(Select)
app.use(Tree)
app.use(TreeSelect)
app.use(Card)
app.use(Menu)
app.use(Tooltip)
app.use(Descriptions)
app.use(Steps)
app.use(Form)
app.use(Spin)
app.use(Dropdown)
}

View File

@@ -1,8 +1,80 @@
import { PageParams } from '/#/web'
import { TablePageModel } from '/#/web'
import { reactive, toRefs } from 'vue'
import { PageResult } from '/#/axios'
/**
* 重置当前页数
* 获取数据对象
*/
export function resetPage(pages: PageParams) {
pages.current = 1
export default function (queryPageCallback: CallableFunction) {
// 数据内容
const model = reactive({
pages: {
size: 10,
current: 1,
},
queryParam: {},
loading: false,
batchOperateFlag: false,
superQueryFlag: false,
pagination: {},
} as TablePageModel)
// 拆分
const { loading, batchOperateFlag, superQueryFlag } = toRefs(model)
// 不可以被重新赋值, 否则会失去绑定
const { pages, pagination } = model
// 普通查询
function query() {
model.superQueryFlag = false
resetPage()
queryPageCallback()
}
// 表格翻页或变动
function handleTableChange({ currentPage, pageSize }) {
pages.current = currentPage
pages.size = pageSize
queryPageCallback()
}
// 重置当前页数
function resetPage() {
pages.current = 1
}
// 分页查询返回结果处理
function pageQueryResHandel(res: PageResult) {
pagination.current = Number(res.current)
pagination.size = Number(res.size)
pagination.total = Number(res.total)
pagination.records = res.records
model.loading = false
}
// 重置查询
function resetQuery() {
resetQueryParams()
queryPageCallback()
}
// 重置查询参数
function resetQueryParams() {
model.superQueryFlag = false
model.queryParam = {}
}
// ok按钮
function handleOk() {
queryPageCallback()
}
return {
model,
loading,
pages,
pagination,
batchOperateFlag,
superQueryFlag,
query,
resetPage,
pageQueryResHandel,
handleTableChange,
resetQuery,
resetQueryParams,
handleOk,
}
}

View File

@@ -0,0 +1,19 @@
<template>
<a-modal :visible="visible"> 123 </a-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const visible = ref(false)
const i = ref(3)
const emits = defineEmits(['ok'])
function edit() {
console.log(123)
}
defineExpose({
visible,
})
</script>
<style scoped></style>

View File

@@ -1,103 +1,85 @@
<template>
<div class="m-5 p-3 bg-white">
<div class="table-page-search-wrapper">
<a-form layout="inline">
<a-row :gutter="10">
<a-col :md="6" :sm="24">
<a-form-item label="编码">
<a-input v-model="queryParam.code" placeholder="请输入编码" />
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-form-item label="名称">
<a-input v-model="queryParam.name" placeholder="请输入名称" />
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-space>
<a-button type="primary" @click="query">查询</a-button>
<a-button @click="resetQuery">重置</a-button>
</a-space>
</a-col>
</a-row>
</a-form>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query :query-params="model.queryParam" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
</div>
<vxe-toolbar>
<template #buttons>
<a-button type="primary" icon="plus" @click="add">新建</a-button>
<a-button @click="getPage">查询</a-button>
</template>
</vxe-toolbar>
<vxe-table row-id="id" :data="pagination.records" :loading="loading">
<vxe-column type="seq" width="60" />
<vxe-column field="code" title="编码" />
<vxe-column field="name" title="名称" />
<vxe-column field="captcha" title="系统内置">
<template #default="{ row }">
<a-tag v-if="row.system" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
<div class="m-3 p-3 bg-white">
<vxe-toolbar>
<template #buttons>
<a-space>
<a-button type="primary" @click="add">新建</a-button>
<a-button @click="queryPage">查询</a-button>
</a-space>
</template>
</vxe-column>
<vxe-column field="enable" title="启用状态">
<template #default="{ row }">
<a-tag v-if="row.enable" color="green">启用</a-tag>
<a-tag v-else color="red">停用</a-tag>
</template>
</vxe-column>
<vxe-column field="description" title="描述" />
<vxe-column field="createTime" title="创建时间" />
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
</vxe-toolbar>
<vxe-table row-id="id" :data="pagination.records" :loading="loading">
<vxe-column type="seq" width="60" />
<vxe-column field="code" title="编码" />
<vxe-column field="name" title="名称" />
<vxe-column field="captcha" title="系统内置">
<template #default="{ row }">
<a-tag v-if="row.system" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</template>
</vxe-column>
<vxe-column field="enable" title="启用状态">
<template #default="{ row }">
<a-tag v-if="row.enable" color="green">启用</a-tag>
<a-tag v-else color="red">停用</a-tag>
</template>
</vxe-column>
<vxe-column field="description" title="描述" />
<vxe-column field="createTime" title="创建时间" />
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
<client-edit ref="clientEdit" @ok="queryPage" />
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, toRefs } from 'vue'
import { onMounted, ref } from 'vue'
import { page } from '/@/views/modules/system/client/Client.api'
import { PageResult } from '/#/axios'
// 初始化
function init() {
getPage()
}
import useTable from '/@/hooks/bootx/useTable'
import ClientEdit from './ClientEdit.vue'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { STRING } from '/@/components/Bootx/Query/SuperQueryCode'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, pages, model, loading } = useTable(queryPage)
const clientEdit = ref()
// 查询条件
const fields = [
{ field: 'code', type: STRING, name: '编码', placeholder: '请输入数据源编码' },
{ field: 'name', type: STRING, name: '名称', placeholder: '请输入数据源名称' },
]
onMounted(() => {
init()
queryPage()
})
const pageParam = reactive({
size: 10,
current: 1,
})
const queryParam = reactive({})
const model = reactive({
loading: false,
pagination: { size: 10, current: 1, total: 0, records: [] } as PageResult,
})
const { loading, pagination } = toRefs(model)
// 分页
function getPage() {
// 分页查询
function queryPage() {
model.loading = true
page({
...queryParam,
...pageParam,
...model.queryParam,
...pages,
}).then(({ data }) => {
model.pagination = data
model.loading = false
pageQueryResHandel(data)
})
}
// 页面变动
function handleTableChange({ currentPage, pageSize }) {
pageParam.current = currentPage
pageParam.size = pageSize
init()
// 新增
function add() {
clientEdit.value.visible = true
}
</script>
<style scoped></style>
<style lang="less" scoped></style>

View File

@@ -1,15 +0,0 @@
# Test Server
It is used to start the test interface service, which can test the upload, websocket, login and other interfaces.
## Usage
```bash
cd ./test/server
pnpm install
pnpm run start
```

View File

@@ -1,18 +0,0 @@
import FileService from '../service/FileService'
class FileController {
private service: FileService = new FileService()
upload = async (ctx) => {
const files = ctx.request.files.file
console.log(files)
if (files.length === undefined) {
this.service.upload(ctx, files, false)
} else {
this.service.upload(ctx, files, true)
}
}
}
export default new FileController()

View File

@@ -1,15 +0,0 @@
import UserService from '../service/UserService'
class UserController {
private service: UserService = new UserService()
login = async (ctx) => {
ctx.body = await this.service.login()
}
getUserInfoById = async (ctx) => {
ctx.body = await this.service.getUserInfoById()
}
}
export default new UserController()

View File

@@ -1,18 +0,0 @@
const { name } = require('./package.json')
const path = require('path')
module.exports = {
apps: [
{
name,
script: path.resolve(__dirname, './dist/index.js'),
instances: require('os').cpus().length,
autorestart: true,
watch: true,
env_production: {
NODE_ENV: 'production',
PORT: 8080,
},
},
],
}

View File

@@ -1,63 +0,0 @@
import Koa from 'koa'
import path from 'path'
import Router from 'koa-router'
import body from 'koa-body'
import cors from 'koa2-cors'
import koaStatic from 'koa-static'
import websockify from 'koa-websocket'
import route from 'koa-route'
import AppRoutes from './routes'
const PORT = 3300
const app = websockify(new Koa())
app.ws.use(function (ctx, next) {
ctx.websocket.send('connection succeeded!')
return next(ctx)
})
app.ws.use(
route.all('/test', function (ctx) {
// ctx.websocket.send('Hello World');
ctx.websocket.on('message', function (message) {
// do something with the message from client
if (message !== 'ping') {
const data = JSON.stringify({
id: Math.ceil(Math.random() * 1000),
time: new Date().getTime(),
res: `${message}`,
})
ctx.websocket.send(data)
}
console.log(message)
})
}),
)
const router = new Router()
// router
AppRoutes.forEach((route) => router[route.method](route.path, route.action))
app.use(cors())
app.use(
body({
encoding: 'gzip',
multipart: true,
formidable: {
// uploadDir: path.join(__dirname, '/upload/'), // 设置文件上传目录
keepExtensions: true,
maxFieldsSize: 20 * 1024 * 1024,
},
}),
)
app.use(router.routes())
app.use(router.allowedMethods())
app.use(koaStatic(path.join(__dirname)))
app.listen(PORT, () => {
console.log(`Application started successfully: http://localhost:${PORT}`)
})

View File

@@ -1,8 +0,0 @@
{
"watch": ["src"],
"ext": "ts",
"exec": "ts-node -r tsconfig-paths/register index.ts",
"events": {
"restart": "clear"
}
}

View File

@@ -1,36 +0,0 @@
{
"name": "server",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"start": "nodemon",
"build": "rimraf ./dist && tsup ./index.ts --dts --format cjs,esm ",
"prod": "npx pm2 start ecosystem.config.js --env production",
"restart": "pm2 restart ecosystem.config.js --env production",
"stop": "npx pm2 stop ecosystem.config.js"
},
"dependencies": {
"fs-extra": "^10.0.1",
"koa": "^2.13.4",
"koa-body": "^4.2.0",
"koa-bodyparser": "^4.3.0",
"koa-route": "^3.2.0",
"koa-router": "^10.1.1",
"koa-static": "^5.0.0",
"koa-websocket": "^6.0.0",
"koa2-cors": "^2.0.6"
},
"devDependencies": {
"@types/koa": "^2.13.4",
"@types/koa-bodyparser": "^5.0.2",
"@types/koa-router": "^7.4.4",
"@types/node": "^17.0.21",
"nodemon": "^2.0.15",
"pm2": "^5.2.0",
"rimraf": "^3.0.2",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.0",
"tsup": "^5.12.1",
"typescript": "^4.6.2"
}
}

View File

@@ -1,23 +0,0 @@
import UserController from './controller/UserController'
import FileController from './controller/FileController'
export default [
// user
{
path: '/login',
method: 'post',
action: UserController.login,
},
{
path: '/getUserInfoById',
method: 'get',
action: UserController.getUserInfoById,
},
// file
{
path: '/upload',
method: 'post',
action: FileController.upload,
},
]

View File

@@ -1,54 +0,0 @@
import path from 'path'
import fs from 'fs-extra'
const uploadUrl = 'http://localhost:3300/static/upload'
const filePath = path.join(__dirname, '../static/upload/')
fs.ensureDir(filePath)
export default class UserService {
async upload(ctx, files, isMultiple) {
let fileReader, fileResource, writeStream
const fileFunc = function (file) {
fileReader = fs.createReadStream(file.path)
fileResource = filePath + `/${file.name}`
console.log(fileResource)
writeStream = fs.createWriteStream(fileResource)
fileReader.pipe(writeStream)
}
const returnFunc = function (flag) {
if (flag) {
let url = ''
for (let i = 0; i < files.length; i++) {
url += uploadUrl + `/${files[i].name},`
}
url = url.replace(/,$/gi, '')
ctx.body = {
url: url,
code: 0,
message: 'upload Success!',
}
} else {
ctx.body = {
url: uploadUrl + `/${files.name}`,
code: 0,
message: 'upload Success!',
}
}
}
console.log(isMultiple, files.length)
if (isMultiple) {
for (let i = 0; i < files.length; i++) {
const f1 = files[i]
fileFunc(f1)
}
} else {
fileFunc(files)
}
fs.ensureDir(filePath)
returnFunc(isMultiple)
}
}

View File

@@ -1,25 +0,0 @@
import { Result } from '../utils'
const fakeUserInfo = {
userId: '1',
username: 'vben',
realName: 'Vben Admin',
desc: 'manager',
password: '123456',
token: 'fakeToken1',
roles: [
{
roleName: 'Super Admin',
value: 'super',
},
],
}
export default class UserService {
async login() {
return Result.success(fakeUserInfo)
}
async getUserInfoById() {
return Result.success(fakeUserInfo)
}
}

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": false,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es6",
"sourceMap": false,
"esModuleInterop": true,
"outDir": "./dist",
"baseUrl": "./"
},
"exclude": ["node_modules"]
}

View File

@@ -1,9 +0,0 @@
export class Result {
static success(data: any) {
return {
code: 0,
success: true,
result: data,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,8 @@
"sourceMap": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"experimentalDecorators": true,
"lib": ["dom", "esnext"],
"noImplicitAny": false,

24
types/web.d.ts vendored
View File

@@ -1,3 +1,5 @@
import { PageResult } from "/#/axios";
/**
* 分页属性
*/
@@ -10,7 +12,27 @@ export interface Pagination {
/**
* 分页参数
*/
export interface PageParams {
export interface PageParam {
// 每页数量
size: number
// 当前页数
current: number
}
/**
* 表格分页对象
*/
export interface TablePageModel<T = any> {
// 加载状态
loading: boolean
// 批量操作标识
batchOperateFlag: boolean
// 高级查询条件生效状态
superQueryFlag: boolean
// 分页参数
pages: PageParam
// 查询参数
queryParam: object
// 结果
pagination: PageResult<T>
}