!245 优化表单校验和查询参数,增加 vue3 简单使用说明

Merge pull request !245 from xingyu/master
pull/2/head
芋道源码 2022-08-02 04:49:49 +00:00 committed by Gitee
commit 79d55aa3e9
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
30 changed files with 310 additions and 96 deletions

View File

@ -192,14 +192,14 @@ ps核心功能已经实现正在对接微信小程序中...
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - | | [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - | | [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - |
### Vue2 前端 ### [Vue2 前端](./yudao-ui-admin)
| 框架 | 说明 | 版本 | | 框架 | 说明 | 版本 |
|------------------------------------------------------------------------------|---------------|--------| |------------------------------------------------------------------------------|---------------|--------|
| [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.6.12 | | [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.6.12 |
| [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案 | - | | [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案 | - |
### Vue3 前端 ### [Vue3 前端](./yudao-ui-admin-vue3)
| 框架 | 说明 | 版本 | | 框架 | 说明 | 版本 |
|----------------------------------------------------------------------|------------------|--------| |----------------------------------------------------------------------|------------------|--------|

View File

@ -22,6 +22,8 @@
- node >=14.18.0(建议使用 16 版本) ,pnpm >=7 - node >=14.18.0(建议使用 16 版本) ,pnpm >=7
- 开发建议使用 [谷歌浏览器-开发者版](https://www.google.cn/intl/zh-CN/chrome/dev/) 不支持 IE\QQ 等浏览器 - 开发建议使用 [谷歌浏览器-开发者版](https://www.google.cn/intl/zh-CN/chrome/dev/) 不支持 IE\QQ 等浏览器
### 点击查看[使用说明](./use.md)
### 前端依赖 ### 前端依赖
| 框架 | 说明 | 版本 | | 框架 | 说明 | 版本 |

View File

@ -65,8 +65,8 @@
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"@types/qrcode": "^1.4.2", "@types/qrcode": "^1.4.2",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.31.0", "@typescript-eslint/parser": "^5.32.0",
"@vitejs/plugin-vue": "^3.0.1", "@vitejs/plugin-vue": "^3.0.1",
"@vitejs/plugin-vue-jsx": "^2.0.0", "@vitejs/plugin-vue-jsx": "^2.0.0",
"autoprefixer": "^10.4.8", "autoprefixer": "^10.4.8",
@ -91,7 +91,7 @@
"stylelint-config-standard": "^26.0.0", "stylelint-config-standard": "^26.0.0",
"stylelint-order": "^5.0.0", "stylelint-order": "^5.0.0",
"typescript": "4.7.4", "typescript": "4.7.4",
"unplugin-vue-define-options": "^0.6.2", "unplugin-vue-define-options": "^0.7.1",
"vite": "3.0.4", "vite": "3.0.4",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-eslint": "^1.7.0", "vite-plugin-eslint": "^1.7.0",

View File

@ -1,9 +1,12 @@
export type DeptVO = { export type DeptVO = {
id: number id: number
name: string name: string
status: number
parentId: number parentId: number
createTime: string status: number
sort: number
leaderUserId: number
phone: string
email: string
} }
export type DeptListReqVO = { export type DeptListReqVO = {

View File

@ -1,8 +1,8 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { ElMessage, ElMessageBox, ElNotification } from 'element-plus' import { ElMessage, ElNotification } from 'element-plus'
import qs from 'qs' import qs from 'qs'
import { config } from '@/config/axios/config' import { config } from '@/config/axios/config'
import { getAccessToken, getRefreshToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId, removeToken } from '@/utils/auth'
import errorCode from './errorCode' import errorCode from './errorCode'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
@ -22,7 +22,7 @@ export const isRelogin = { show: false }
// 请求队列 // 请求队列
// const requestList = [] // const requestList = []
// 是否正在刷新中 // 是否正在刷新中
let isRefreshToken = false // const isRefreshToken = false
export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH] export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH]
@ -55,18 +55,26 @@ service.interceptors.request.use(
config.data = qs.stringify(data) config.data = qs.stringify(data)
} }
// get参数编码 // get参数编码
if (config.method?.toUpperCase() === 'GET' && config.params) { if (config.method?.toUpperCase() === 'GET' && params) {
let url = config.url as string let url = config.url + '?'
for (const propName of Object.keys(params)) {
const value = params[propName]
if (value !== void 0 && value !== null && typeof value !== 'undefined') {
if (typeof value === 'object') {
for (const val of Object.keys(value)) {
const params = propName + '[' + val + ']'
const subPart = encodeURIComponent(params) + '='
url += subPart + encodeURIComponent(value[val]) + '&'
}
} else {
url += `${propName}=${encodeURIComponent(value)}&`
}
}
}
// 给 get 请求加上时间戳参数,避免从缓存中拿数据 // 给 get 请求加上时间戳参数,避免从缓存中拿数据
// const now = new Date().getTime() // const now = new Date().getTime()
// params = params.substring(0, url.length - 1) + `?_t=${now}` // params = params.substring(0, url.length - 1) + `?_t=${now}`
url += '?' url = url.slice(0, -1)
const keys = Object.keys(params)
for (const key of keys) {
if (params[key] !== void 0 && params[key] !== null) {
url += `${key}=${encodeURIComponent(params[key])}&`
}
}
config.params = {} config.params = {}
config.url = url config.url = url
} }
@ -90,6 +98,13 @@ service.interceptors.response.use(
const { t } = useI18n() const { t } = useI18n()
// 未设置状态码则默认成功状态 // 未设置状态码则默认成功状态
const code = data.code || result_code const code = data.code || result_code
// 二进制数据则直接返回
if (
response.request.responseType === 'blob' ||
response.request.responseType === 'arraybuffer'
) {
return response.data
}
// 获取错误信息 // 获取错误信息
const msg = data.msg || errorCode[code] || errorCode['default'] const msg = data.msg || errorCode[code] || errorCode['default']
if (ignoreMsgs.indexOf(msg) !== -1) { if (ignoreMsgs.indexOf(msg) !== -1) {
@ -97,15 +112,16 @@ service.interceptors.response.use(
return Promise.reject(msg) return Promise.reject(msg)
} else if (code === 401) { } else if (code === 401) {
// 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了 // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
if (!isRefreshToken) { return handleAuthorized()
isRefreshToken = true // if (!isRefreshToken) {
// 1. 如果获取不到刷新令牌,则只能执行登出操作 // isRefreshToken = true
if (!getRefreshToken()) { // // 1. 如果获取不到刷新令牌,则只能执行登出操作
return handleAuthorized() // if (!getRefreshToken()) {
} // return handleAuthorized()
// 2. 进行刷新访问令牌 // }
// TODO: 引入refreshToken会循环依赖报错 // // 2. 进行刷新访问令牌
} // // TODO: 引入refreshToken会循环依赖报错
// }
} else if (code === 500) { } else if (code === 500) {
ElMessage.error(t('sys.api.errMsg500')) ElMessage.error(t('sys.api.errMsg500'))
return Promise.reject(new Error(msg)) return Promise.reject(new Error(msg))
@ -149,21 +165,12 @@ service.interceptors.response.use(
return Promise.reject(error) return Promise.reject(error)
} }
) )
function handleAuthorized() { const handleAuthorized = () => {
const { t } = useI18n() const { t } = useI18n()
if (!isRelogin.show) { if (!isRelogin.show) {
removeToken()
isRelogin.show = true isRelogin.show = true
ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), { ElNotification.error(t('sys.api.timeoutMessage'))
confirmButtonText: t('login.relogin'),
cancelButtonText: t('common.cancel'),
type: 'warning'
})
.then(() => {
isRelogin.show = false
})
.catch(() => {
isRelogin.show = false
})
} }
return Promise.reject(t('sys.api.timeoutMessage')) return Promise.reject(t('sys.api.timeoutMessage'))
} }

View File

@ -213,7 +213,7 @@ export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
* @param {*} parentId 'parentId' * @param {*} parentId 'parentId'
* @param {*} children 'children' * @param {*} children 'children'
*/ */
export const handleTree = (data, id?: string, parentId?: string, children?: string) => { export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => {
const config = { const config = {
id: id || 'id', id: id || 'id',
parentId: parentId || 'parentId', parentId: parentId || 'parentId',
@ -222,7 +222,7 @@ export const handleTree = (data, id?: string, parentId?: string, children?: stri
const childrenListMap = {} const childrenListMap = {}
const nodeIds = {} const nodeIds = {}
const tree = [] const tree: any[] = []
for (const d of data) { for (const d of data) {
const parentId = d[config.parentId] const parentId = d[config.parentId]

View File

@ -50,7 +50,16 @@ const crudSchemas = reactive<CrudSchema[]>([
}, },
{ {
label: '请求时间', label: '请求时间',
field: 'beginTime' field: 'beginTime',
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
}
}, },
{ {
label: '执行时长', label: '执行时长',

View File

@ -50,7 +50,16 @@ const crudSchemas = reactive<CrudSchema[]>([
}, },
{ {
label: '异常发生时间', label: '异常发生时间',
field: 'exceptionTime' field: 'exceptionTime',
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
}
}, },
{ {
label: '异常名', label: '异常名',

View File

@ -50,6 +50,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -91,6 +91,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -58,6 +58,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -76,6 +76,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -36,6 +36,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -8,9 +8,10 @@ const { t } = useI18n() // 国际化
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
name: [required], name: [required],
code: [required], status: [required],
sort: [required], payNotifyUrl: [required],
status: [required] refundNotifyUrl: [required],
merchantId: [required]
}) })
// CrudSchema // CrudSchema
@ -53,6 +54,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -7,9 +7,9 @@ const { t } = useI18n() // 国际化
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
no: [required],
name: [required], name: [required],
code: [required], shortName: [required],
sort: [required],
status: [required] status: [required]
}) })
@ -77,6 +77,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -157,6 +157,22 @@ const crudSchemas = reactive<CrudSchema[]>([
label: '渠道订单号', label: '渠道订单号',
field: 'channelOrderNo' field: 'channelOrderNo'
}, },
{
label: t('common.createTime'),
field: 'createTime',
form: {
show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
}
},
{ {
label: t('table.action'), label: t('table.action'),
field: 'action', field: 'action',

View File

@ -84,6 +84,15 @@ const crudSchemas = reactive<CrudSchema[]>([
field: 'createTime', field: 'createTime',
form: { form: {
show: false show: false
},
search: {
show: true,
component: 'DatePicker',
componentProps: {
type: 'datetimerange',
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
}
} }
}, },
{ {

View File

@ -1,5 +1,19 @@
import { required } from '@/utils/formRules' import { required } from '@/utils/formRules'
import { reactive } from 'vue' import { reactive } from 'vue'
// 表单校验
export const rules = reactive({
name: [required],
sort: [required],
email: [required],
phone: [
{
pattern:
/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,
trigger: 'blur',
message: '请输入正确的手机号码'
}
]
})
export const modelSchema = reactive<FormSchema[]>([ export const modelSchema = reactive<FormSchema[]>([
{ {
@ -17,7 +31,7 @@ export const modelSchema = reactive<FormSchema[]>([
}, },
{ {
label: '负责人', label: '负责人',
field: 'email', field: 'leaderUserId',
component: 'Input' component: 'Input'
}, },
{ {
@ -33,7 +47,7 @@ export const modelSchema = reactive<FormSchema[]>([
{ {
label: '显示排序', label: '显示排序',
field: 'sort', field: 'sort',
component: 'Input' component: 'InputNumber'
}, },
{ {
label: '状态', label: '状态',

View File

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { ElInput, ElCard, ElTree, ElTreeSelect } from 'element-plus' import { ElInput, ElCard, ElTree, ElTreeSelect, ElSelect, ElOption } from 'element-plus'
import { handleTree } from '@/utils/tree' import { handleTree } from '@/utils/tree'
import { onMounted, ref, unref, watch } from 'vue' import { onMounted, ref, unref, watch } from 'vue'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
import { Form, FormExpose } from '@/components/Form' import { Form, FormExpose } from '@/components/Form'
import { modelSchema } from './dept.data' import { modelSchema, rules } from './dept.data'
import { DeptVO } from '@/api/system/dept/types' import { DeptVO } from '@/api/system/dept/types'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { getListSimpleUsersApi } from '@/api/system/user'
const message = useMessage() const message = useMessage()
interface Tree { interface Tree {
id: number id: number
@ -34,7 +35,7 @@ const filterText = ref('')
const deptOptions = ref() // const deptOptions = ref() //
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
const getTree = async () => { const getTree = async () => {
const res = await DeptApi.getDeptPageApi(null) const res = await DeptApi.listSimpleDeptApi()
deptOptions.value = handleTree(res) deptOptions.value = handleTree(res)
} }
const filterNode = (value: string, data: Tree) => { const filterNode = (value: string, data: Tree) => {
@ -44,6 +45,13 @@ const filterNode = (value: string, data: Tree) => {
watch(filterText, (val) => { watch(filterText, (val) => {
treeRef.value!.filter(val) treeRef.value!.filter(val)
}) })
//
const userOption = ref()
const leaderUserId = ref()
const getUserList = async () => {
const res = await getListSimpleUsersApi()
userOption.value = res
}
// //
const handleAdd = (data: { id: number }) => { const handleAdd = (data: { id: number }) => {
// //
@ -54,11 +62,12 @@ const handleAdd = (data: { id: number }) => {
} }
// //
const handleUpdate = async (data: { id: number }) => { const handleUpdate = async (data: { id: number }) => {
showForm.value = true
const res = await DeptApi.getDeptApi(data.id) const res = await DeptApi.getDeptApi(data.id)
formTitle.value = '修改- ' + res?.name formTitle.value = '修改- ' + res?.name
deptParentId.value = res.parentId deptParentId.value = res.parentId
leaderUserId.value = res.leaderUserId
unref(formRef)?.setValues(res) unref(formRef)?.setValues(res)
showForm.value = true
} }
// //
const handleDelete = async (data: { id: number }) => { const handleDelete = async (data: { id: number }) => {
@ -78,7 +87,7 @@ const submitForm = async () => {
try { try {
const data = unref(formRef)?.formModel as DeptVO const data = unref(formRef)?.formModel as DeptVO
data.parentId = deptParentId.value data.parentId = deptParentId.value
// TODO: data.leaderUserId = leaderUserId.value
if (formTitle.value.startsWith('新增')) { if (formTitle.value.startsWith('新增')) {
await DeptApi.createDeptApi(data) await DeptApi.createDeptApi(data)
} else if (formTitle.value.startsWith('修改')) { } else if (formTitle.value.startsWith('修改')) {
@ -92,6 +101,7 @@ const submitForm = async () => {
} }
onMounted(async () => { onMounted(async () => {
await getTree() await getTree()
await getUserList()
}) })
</script> </script>
<template> <template>
@ -123,14 +133,29 @@ onMounted(async () => {
<span class="custom-tree-node"> <span class="custom-tree-node">
<span>{{ node.label }}</span> <span>{{ node.label }}</span>
<span> <span>
<el-button link v-hasPermi="['system:dept:create']" @click="handleAdd(data)"> <el-button
<Icon icon="ep:plus" class="mr-1px" /> link
type="primary"
v-hasPermi="['system:dept:create']"
@click="handleAdd(data)"
>
<Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
</el-button> </el-button>
<el-button link v-hasPermi="['system:dept:update']" @click="handleUpdate(data)"> <el-button
<Icon icon="ep:edit" class="mr-1px" /> link
type="primary"
v-hasPermi="['system:dept:update']"
@click="handleUpdate(data)"
>
<Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
</el-button> </el-button>
<el-button link v-hasPermi="['system:dept:delete']" @click="handleDelete(data)"> <el-button
<Icon icon="ep:delete" class="mr-1px" /> link
type="primary"
v-hasPermi="['system:dept:delete']"
@click="handleDelete(data)"
>
<Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
</el-button> </el-button>
</span> </span>
</span> </span>
@ -149,7 +174,7 @@ onMounted(async () => {
</div> </div>
<div v-if="showForm"> <div v-if="showForm">
<!-- 操作工具栏 --> <!-- 操作工具栏 -->
<Form :schema="modelSchema" ref="formRef"> <Form ref="formRef" :schema="modelSchema" :rules="rules">
<template #parentId> <template #parentId>
<el-tree-select <el-tree-select
node-key="id" node-key="id"
@ -159,6 +184,16 @@ onMounted(async () => {
check-strictly check-strictly
/> />
</template> </template>
<template #leaderUserId>
<el-select v-model="leaderUserId">
<el-option
v-for="item in userOption"
:key="parseInt(item.id)"
:label="item.nickname"
:value="parseInt(item.id)"
/>
</el-select>
</template>
</Form> </Form>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<el-button <el-button

View File

@ -7,9 +7,9 @@ import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() const { t } = useI18n()
// 表单校验 // 表单校验
export const dictDataRules = reactive({ export const dictDataRules = reactive({
dictType: [required],
label: [required], label: [required],
value: [required] value: [required],
sort: [required]
}) })
// crudSchemas // crudSchemas
export const crudSchemas = reactive<CrudSchema[]>([ export const crudSchemas = reactive<CrudSchema[]>([
@ -29,6 +29,9 @@ export const crudSchemas = reactive<CrudSchema[]>([
field: 'dictType', field: 'dictType',
table: { table: {
show: false show: false
},
form: {
show: false
} }
}, },
{ {

View File

@ -25,11 +25,12 @@ import { MenuVO } from '@/api/system/menu/types'
import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants' import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { required } from '@/utils/formRules.js'
const message = useMessage() const message = useMessage()
const { t } = useI18n() // const { t } = useI18n() //
// ========== ========== // ========== ==========
const loading = ref(true) const loading = ref(true)
const menuData = ref([]) // const menuData = ref<any[]>([]) //
const getList = async () => { const getList = async () => {
const res = await MenuApi.getMenuListApi(queryParams) const res = await MenuApi.getMenuListApi(queryParams)
menuData.value = handleTree(res) menuData.value = handleTree(res)
@ -44,7 +45,7 @@ const menuProps = {
const menuOptions = ref() // const menuOptions = ref() //
const getTree = async () => { const getTree = async () => {
const res = await MenuApi.listSimpleMenusApi() const res = await MenuApi.listSimpleMenusApi()
const menu = { id: 0, name: '主类目', children: [] } const menu = { id: 0, name: '主类目', children: [] as any[] }
menu.children = handleTree(res) menu.children = handleTree(res)
console.info(menu) console.info(menu)
menuOptions.value = menu menuOptions.value = menu
@ -85,12 +86,12 @@ const menuForm = ref<MenuVO>({
createTime: '' createTime: ''
}) })
// //
const rules = { const rules = reactive({
name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }], name: [required],
sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }], sort: [required],
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }], path: [required],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] status: [required]
} })
// //
const setDialogTile = (type: string) => { const setDialogTile = (type: string) => {
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)

View File

@ -8,8 +8,7 @@ const { t } = useI18n() // 国际化
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
title: [required], title: [required],
type: [required], type: [required]
status: [required]
}) })
// CrudSchema // CrudSchema

View File

@ -9,8 +9,7 @@ const { t } = useI18n() // 国际化
export const rules = reactive({ export const rules = reactive({
name: [required], name: [required],
code: [required], code: [required],
sort: [required], sort: [required]
status: [required]
}) })
// CrudSchema // CrudSchema

View File

@ -8,13 +8,12 @@ const { t } = useI18n() // 国际化
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
type: [required], type: [required],
status: [required],
code: [required],
name: [required], name: [required],
content: [required], content: [required],
apiTemplateId: [required], apiTemplateId: [required],
channelId: [required], channelId: [required]
code: [required],
sort: [required],
status: [required]
}) })
// CrudSchema // CrudSchema

View File

@ -9,6 +9,7 @@ import {
ElTreeSelect, ElTreeSelect,
ElSelect, ElSelect,
ElOption, ElOption,
ElTransfer,
ElForm, ElForm,
ElFormItem, ElFormItem,
ElUpload, ElUpload,
@ -62,7 +63,7 @@ const { getList, setSearchParams, delList, exportList } = methods
// ========== ========== // ========== ==========
const filterText = ref('') const filterText = ref('')
const deptOptions = ref([]) // const deptOptions = ref<any[]>([]) //
const searchForm = ref<FormExpose>() const searchForm = ref<FormExpose>()
const treeRef = ref<InstanceType<typeof ElTree>>() const treeRef = ref<InstanceType<typeof ElTree>>()
const getTree = async () => { const getTree = async () => {
@ -268,6 +269,7 @@ const handleFileSuccess = (response: any): void => {
text += '< ' + username + ': ' + data.failureUsernames[username] + ' >' text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
} }
message.alert(text) message.alert(text)
getList()
} }
// //
const handleExceed = (): void => { const handleExceed = (): void => {
@ -281,8 +283,8 @@ const excelUploadError = (): void => {
onMounted(async () => { onMounted(async () => {
await getTree() await getTree()
await getPostOptions() await getPostOptions()
await getList()
}) })
getList()
</script> </script>
<template> <template>
@ -482,7 +484,7 @@ getList()
</template> </template>
</Dialog> </Dialog>
<!-- 分配用户角色 --> <!-- 分配用户角色 -->
<Dialog v-model="roleDialogVisible" title="分配角色"> <Dialog v-model="roleDialogVisible" title="分配角色" maxHeight="450px">
<el-form :model="userRole" label-width="80px"> <el-form :model="userRole" label-width="80px">
<el-form-item label="用户名称"> <el-form-item label="用户名称">
<el-input v-model="userRole.username" :disabled="true" /> <el-input v-model="userRole.username" :disabled="true" />
@ -491,14 +493,15 @@ getList()
<el-input v-model="userRole.nickname" :disabled="true" /> <el-input v-model="userRole.nickname" :disabled="true" />
</el-form-item> </el-form-item>
<el-form-item label="角色"> <el-form-item label="角色">
<el-select v-model="userRole.roleIds" multiple> <el-transfer
<el-option v-model="userRole.roleIds"
v-for="item in roleOptions" :titles="['角色列表', '已选择']"
:key="parseInt(item.id)" :props="{
:label="item.name" key: 'id',
:value="parseInt(item.id)" label: 'name'
/> }"
</el-select> :data="roleOptions"
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 操作按钮 --> <!-- 操作按钮 -->

View File

@ -7,8 +7,18 @@ import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
const { t } = useI18n() const { t } = useI18n()
// 表单校验 // 表单校验
export const rules = reactive({ export const rules = reactive({
username: [required],
nickname: [required], nickname: [required],
status: [required] email: [required],
status: [required],
mobile: [
{
pattern:
/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,
trigger: 'blur',
message: '请输入正确的手机号码'
}
]
}) })
// crudSchemas // crudSchemas
const crudSchemas = reactive<CrudSchema[]>([ const crudSchemas = reactive<CrudSchema[]>([

View File

@ -0,0 +1,33 @@
# 注意事项
- 项目路径请不要使用中文命名!!!会造成解析乱码!!!请使用全英文路径!!!全英文路径!!!全英文路径!!!
- node >=16 , pnpm >=7非node16+ pnpm 7+ 安装问题不予解决、不适配
- 开发建议使用 [谷歌浏览器-开发者版](https://www.google.cn/intl/zh-CN/chrome/dev/) 不支持 IE\QQ 等浏览器
- 本框架使用 TypeScript 简称ts和java的类型差不多为了简化没过多使用 type
- 本框架使用 Vue3.2 + setup语法糖请自行学习相关内容
- [点击查看为什么Vue3.2什么是setupVite为什么第一次加载速度慢](https://www.baidu.com)
- idea 怎么开发? 不知道。
- 启动方式详见[README.md](./README.md)
## 简单使用
- 目录结构与 vue2 版本基本保持一致
- 一个页面(以post为例)由4部分组成
```bash
/src/api/system/post/ [index.ts | types.ts]
/src/views/system/post/ [index.vue | post.data.ts]
```
- 其中api内index.ts 与 vue2 基本一致只不过axios封装了get post put delete upload download 等方法不用写method: 'get' 了
- api内types.ts是接口中的类型声明与java中vo等保持一致 java中long int => ts 中 number
- views中index.vue 与 vue2 基本一致本框架封装了Search Table Form Descriptions等组件也可以按照vue2方式去写参考menu
- post.data.ts 中主要是表单校验 rules 和表单 crudSchemas 通过修改crudSchemas 就可以控制增删改查的字段、输入框还是下拉框等等
- 本框架集成了国际化,不需要可以自己想办法移除,后期不会提供删减版 使用方式
```bash
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
t('common.createTime')
对应翻译文档在 src/locales
```

View File

@ -138,6 +138,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
'vue-router', 'vue-router',
'vue-types', 'vue-types',
'vue-i18n', 'vue-i18n',
'element-plus/es',
'element-plus/es/locale/lang/zh-cn', 'element-plus/es/locale/lang/zh-cn',
'element-plus/es/locale/lang/en', 'element-plus/es/locale/lang/en',
'@iconify/iconify', '@iconify/iconify',

View File

@ -167,7 +167,7 @@ export default {
], ],
phone: [ phone: [
{ {
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, pattern: /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,
message: "请输入正确的手机号码", message: "请输入正确的手机号码",
trigger: "blur" trigger: "blur"
} }

View File

@ -183,8 +183,6 @@ export default {
accountCount: [{ required: true, message: "账号额度不能为空", trigger: "blur" }], accountCount: [{ required: true, message: "账号额度不能为空", trigger: "blur" }],
expireTime: [{ required: true, message: "过期时间不能为空", trigger: "blur" }], expireTime: [{ required: true, message: "过期时间不能为空", trigger: "blur" }],
domain: [{ required: true, message: "绑定域名不能为空", trigger: "blur" }], domain: [{ required: true, message: "绑定域名不能为空", trigger: "blur" }],
username: [{ required: true, message: "用户名称不能为空", trigger: "blur" }],
password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }],
} }
}; };
}, },