Commit 79d55aa3e91242c30bfcb9e90b3acf95e3df0288

Authored by 芋道源码
Committed by Gitee
2 parents 0de7fdae c7924e04

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

Merge pull request !245 from xingyu/master
Showing 30 changed files with 310 additions and 96 deletions
README.md
... ... @@ -192,14 +192,14 @@ ps:核心功能已经实现,正在对接微信小程序中...
192 192 | [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
193 193 | [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.0.0 | - |
194 194  
195   -### Vue2 前端
  195 +### [Vue2 前端](./yudao-ui-admin)
196 196  
197 197 | 框架 | 说明 | 版本 |
198 198 |------------------------------------------------------------------------------|---------------|--------|
199 199 | [Vue](https://cn.vuejs.org/index.html) | JavaScript 框架 | 2.6.12 |
200 200 | [Vue Element Admin](https://panjiachen.github.io/vue-element-admin-site/zh/) | 后台前端解决方案 | - |
201 201  
202   -### Vue3 前端
  202 +### [Vue3 前端](./yudao-ui-admin-vue3)
203 203  
204 204 | 框架 | 说明 | 版本 |
205 205 |----------------------------------------------------------------------|------------------|--------|
... ...
yudao-ui-admin-vue3/README.md
... ... @@ -22,6 +22,8 @@
22 22 - node >=14.18.0(建议使用 16 版本) ,pnpm >=7
23 23 - 开发建议使用 [谷歌浏览器-开发者版](https://www.google.cn/intl/zh-CN/chrome/dev/) 不支持 IE\QQ 等浏览器
24 24  
  25 +### 点击查看[使用说明](./use.md)
  26 +
25 27 ### 前端依赖
26 28  
27 29 | 框架 | 说明 | 版本 |
... ...
yudao-ui-admin-vue3/package.json
... ... @@ -65,8 +65,8 @@
65 65 "@types/nprogress": "^0.2.0",
66 66 "@types/qrcode": "^1.4.2",
67 67 "@types/qs": "^6.9.7",
68   - "@typescript-eslint/eslint-plugin": "^5.31.0",
69   - "@typescript-eslint/parser": "^5.31.0",
  68 + "@typescript-eslint/eslint-plugin": "^5.32.0",
  69 + "@typescript-eslint/parser": "^5.32.0",
70 70 "@vitejs/plugin-vue": "^3.0.1",
71 71 "@vitejs/plugin-vue-jsx": "^2.0.0",
72 72 "autoprefixer": "^10.4.8",
... ... @@ -91,7 +91,7 @@
91 91 "stylelint-config-standard": "^26.0.0",
92 92 "stylelint-order": "^5.0.0",
93 93 "typescript": "4.7.4",
94   - "unplugin-vue-define-options": "^0.6.2",
  94 + "unplugin-vue-define-options": "^0.7.1",
95 95 "vite": "3.0.4",
96 96 "vite-plugin-compression": "^0.5.1",
97 97 "vite-plugin-eslint": "^1.7.0",
... ...
yudao-ui-admin-vue3/src/api/system/dept/types.ts
1 1 export type DeptVO = {
2 2 id: number
3 3 name: string
4   - status: number
5 4 parentId: number
6   - createTime: string
  5 + status: number
  6 + sort: number
  7 + leaderUserId: number
  8 + phone: string
  9 + email: string
7 10 }
8 11  
9 12 export type DeptListReqVO = {
... ...
yudao-ui-admin-vue3/src/config/axios/index.ts
1 1 import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
2   -import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'
  2 +import { ElMessage, ElNotification } from 'element-plus'
3 3 import qs from 'qs'
4 4 import { config } from '@/config/axios/config'
5   -import { getAccessToken, getRefreshToken, getTenantId } from '@/utils/auth'
  5 +import { getAccessToken, getTenantId, removeToken } from '@/utils/auth'
6 6 import errorCode from './errorCode'
7 7 import { useI18n } from '@/hooks/web/useI18n'
8 8  
... ... @@ -22,7 +22,7 @@ export const isRelogin = { show: false }
22 22 // 请求队列
23 23 // const requestList = []
24 24 // 是否正在刷新中
25   -let isRefreshToken = false
  25 +// const isRefreshToken = false
26 26  
27 27 export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH]
28 28  
... ... @@ -55,18 +55,26 @@ service.interceptors.request.use(
55 55 config.data = qs.stringify(data)
56 56 }
57 57 // get参数编码
58   - if (config.method?.toUpperCase() === 'GET' && config.params) {
59   - let url = config.url as string
  58 + if (config.method?.toUpperCase() === 'GET' && params) {
  59 + let url = config.url + '?'
  60 + for (const propName of Object.keys(params)) {
  61 + const value = params[propName]
  62 + if (value !== void 0 && value !== null && typeof value !== 'undefined') {
  63 + if (typeof value === 'object') {
  64 + for (const val of Object.keys(value)) {
  65 + const params = propName + '[' + val + ']'
  66 + const subPart = encodeURIComponent(params) + '='
  67 + url += subPart + encodeURIComponent(value[val]) + '&'
  68 + }
  69 + } else {
  70 + url += `${propName}=${encodeURIComponent(value)}&`
  71 + }
  72 + }
  73 + }
60 74 // 给 get 请求加上时间戳参数,避免从缓存中拿数据
61 75 // const now = new Date().getTime()
62 76 // params = params.substring(0, url.length - 1) + `?_t=${now}`
63   - url += '?'
64   - const keys = Object.keys(params)
65   - for (const key of keys) {
66   - if (params[key] !== void 0 && params[key] !== null) {
67   - url += `${key}=${encodeURIComponent(params[key])}&`
68   - }
69   - }
  77 + url = url.slice(0, -1)
70 78 config.params = {}
71 79 config.url = url
72 80 }
... ... @@ -90,6 +98,13 @@ service.interceptors.response.use(
90 98 const { t } = useI18n()
91 99 // 未设置状态码则默认成功状态
92 100 const code = data.code || result_code
  101 + // 二进制数据则直接返回
  102 + if (
  103 + response.request.responseType === 'blob' ||
  104 + response.request.responseType === 'arraybuffer'
  105 + ) {
  106 + return response.data
  107 + }
93 108 // 获取错误信息
94 109 const msg = data.msg || errorCode[code] || errorCode['default']
95 110 if (ignoreMsgs.indexOf(msg) !== -1) {
... ... @@ -97,15 +112,16 @@ service.interceptors.response.use(
97 112 return Promise.reject(msg)
98 113 } else if (code === 401) {
99 114 // 如果未认证,并且未进行刷新令牌,说明可能是访问令牌过期了
100   - if (!isRefreshToken) {
101   - isRefreshToken = true
102   - // 1. 如果获取不到刷新令牌,则只能执行登出操作
103   - if (!getRefreshToken()) {
104   - return handleAuthorized()
105   - }
106   - // 2. 进行刷新访问令牌
107   - // TODO: 引入refreshToken会循环依赖报错
108   - }
  115 + return handleAuthorized()
  116 + // if (!isRefreshToken) {
  117 + // isRefreshToken = true
  118 + // // 1. 如果获取不到刷新令牌,则只能执行登出操作
  119 + // if (!getRefreshToken()) {
  120 + // return handleAuthorized()
  121 + // }
  122 + // // 2. 进行刷新访问令牌
  123 + // // TODO: 引入refreshToken会循环依赖报错
  124 + // }
109 125 } else if (code === 500) {
110 126 ElMessage.error(t('sys.api.errMsg500'))
111 127 return Promise.reject(new Error(msg))
... ... @@ -149,21 +165,12 @@ service.interceptors.response.use(
149 165 return Promise.reject(error)
150 166 }
151 167 )
152   -function handleAuthorized() {
  168 +const handleAuthorized = () => {
153 169 const { t } = useI18n()
154 170 if (!isRelogin.show) {
  171 + removeToken()
155 172 isRelogin.show = true
156   - ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {
157   - confirmButtonText: t('login.relogin'),
158   - cancelButtonText: t('common.cancel'),
159   - type: 'warning'
160   - })
161   - .then(() => {
162   - isRelogin.show = false
163   - })
164   - .catch(() => {
165   - isRelogin.show = false
166   - })
  173 + ElNotification.error(t('sys.api.timeoutMessage'))
167 174 }
168 175 return Promise.reject(t('sys.api.timeoutMessage'))
169 176 }
... ...
yudao-ui-admin-vue3/src/utils/tree.ts
... ... @@ -213,7 +213,7 @@ export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
213 213 * @param {*} parentId 父节点字段 默认 'parentId'
214 214 * @param {*} children 孩子节点字段 默认 'children'
215 215 */
216   -export const handleTree = (data, id?: string, parentId?: string, children?: string) => {
  216 +export const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => {
217 217 const config = {
218 218 id: id || 'id',
219 219 parentId: parentId || 'parentId',
... ... @@ -222,7 +222,7 @@ export const handleTree = (data, id?: string, parentId?: string, children?: stri
222 222  
223 223 const childrenListMap = {}
224 224 const nodeIds = {}
225   - const tree = []
  225 + const tree: any[] = []
226 226  
227 227 for (const d of data) {
228 228 const parentId = d[config.parentId]
... ...
yudao-ui-admin-vue3/src/views/infra/apiAccessLog/apiAccessLog.data.ts
... ... @@ -50,7 +50,16 @@ const crudSchemas = reactive<CrudSchema[]>([
50 50 },
51 51 {
52 52 label: '请求时间',
53   - field: 'beginTime'
  53 + field: 'beginTime',
  54 + search: {
  55 + show: true,
  56 + component: 'DatePicker',
  57 + componentProps: {
  58 + type: 'datetimerange',
  59 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  60 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  61 + }
  62 + }
54 63 },
55 64 {
56 65 label: '执行时长',
... ...
yudao-ui-admin-vue3/src/views/infra/apiErrorLog/apiErrorLog.data.ts
... ... @@ -50,7 +50,16 @@ const crudSchemas = reactive<CrudSchema[]>([
50 50 },
51 51 {
52 52 label: '异常发生时间',
53   - field: 'exceptionTime'
  53 + field: 'exceptionTime',
  54 + search: {
  55 + show: true,
  56 + component: 'DatePicker',
  57 + componentProps: {
  58 + type: 'datetimerange',
  59 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  60 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  61 + }
  62 + }
54 63 },
55 64 {
56 65 label: '异常名',
... ...
yudao-ui-admin-vue3/src/views/infra/codegen/codegen.data.ts
... ... @@ -50,6 +50,15 @@ const crudSchemas = reactive<CrudSchema[]>([
50 50 field: 'createTime',
51 51 form: {
52 52 show: false
  53 + },
  54 + search: {
  55 + show: true,
  56 + component: 'DatePicker',
  57 + componentProps: {
  58 + type: 'datetimerange',
  59 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  60 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  61 + }
53 62 }
54 63 },
55 64 {
... ...
yudao-ui-admin-vue3/src/views/infra/config/config.data.ts
... ... @@ -91,6 +91,15 @@ const crudSchemas = reactive<CrudSchema[]>([
91 91 field: 'createTime',
92 92 form: {
93 93 show: false
  94 + },
  95 + search: {
  96 + show: true,
  97 + component: 'DatePicker',
  98 + componentProps: {
  99 + type: 'datetimerange',
  100 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  101 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  102 + }
94 103 }
95 104 },
96 105 {
... ...
yudao-ui-admin-vue3/src/views/infra/dataSourceConfig/dataSourceConfig.data.ts
... ... @@ -58,6 +58,15 @@ const crudSchemas = reactive<CrudSchema[]>([
58 58 field: 'createTime',
59 59 form: {
60 60 show: false
  61 + },
  62 + search: {
  63 + show: true,
  64 + component: 'DatePicker',
  65 + componentProps: {
  66 + type: 'datetimerange',
  67 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  68 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  69 + }
61 70 }
62 71 },
63 72 {
... ...
yudao-ui-admin-vue3/src/views/infra/fileConfig/fileConfig.data.ts
... ... @@ -76,6 +76,15 @@ const crudSchemas = reactive<CrudSchema[]>([
76 76 field: 'createTime',
77 77 form: {
78 78 show: false
  79 + },
  80 + search: {
  81 + show: true,
  82 + component: 'DatePicker',
  83 + componentProps: {
  84 + type: 'datetimerange',
  85 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  86 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  87 + }
79 88 }
80 89 },
81 90 {
... ...
yudao-ui-admin-vue3/src/views/infra/fileList/fileList.data.ts
... ... @@ -36,6 +36,15 @@ const crudSchemas = reactive<CrudSchema[]>([
36 36 field: 'createTime',
37 37 form: {
38 38 show: false
  39 + },
  40 + search: {
  41 + show: true,
  42 + component: 'DatePicker',
  43 + componentProps: {
  44 + type: 'datetimerange',
  45 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  46 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  47 + }
39 48 }
40 49 },
41 50 {
... ...
yudao-ui-admin-vue3/src/views/pay/app/app.data.ts
... ... @@ -8,9 +8,10 @@ const { t } = useI18n() // 国际化
8 8 // 表单校验
9 9 export const rules = reactive({
10 10 name: [required],
11   - code: [required],
12   - sort: [required],
13   - status: [required]
  11 + status: [required],
  12 + payNotifyUrl: [required],
  13 + refundNotifyUrl: [required],
  14 + merchantId: [required]
14 15 })
15 16  
16 17 // CrudSchema
... ... @@ -53,6 +54,15 @@ const crudSchemas = reactive<CrudSchema[]>([
53 54 field: 'createTime',
54 55 form: {
55 56 show: false
  57 + },
  58 + search: {
  59 + show: true,
  60 + component: 'DatePicker',
  61 + componentProps: {
  62 + type: 'datetimerange',
  63 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  64 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  65 + }
56 66 }
57 67 },
58 68 {
... ...
yudao-ui-admin-vue3/src/views/pay/merchant/merchant.data.ts
... ... @@ -7,9 +7,9 @@ const { t } = useI18n() // 国际化
7 7  
8 8 // 表单校验
9 9 export const rules = reactive({
  10 + no: [required],
10 11 name: [required],
11   - code: [required],
12   - sort: [required],
  12 + shortName: [required],
13 13 status: [required]
14 14 })
15 15  
... ... @@ -77,6 +77,15 @@ const crudSchemas = reactive<CrudSchema[]>([
77 77 field: 'createTime',
78 78 form: {
79 79 show: false
  80 + },
  81 + search: {
  82 + show: true,
  83 + component: 'DatePicker',
  84 + componentProps: {
  85 + type: 'datetimerange',
  86 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  87 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  88 + }
80 89 }
81 90 },
82 91 {
... ...
yudao-ui-admin-vue3/src/views/pay/order/order.data.ts
... ... @@ -158,6 +158,22 @@ const crudSchemas = reactive<CrudSchema[]>([
158 158 field: 'channelOrderNo'
159 159 },
160 160 {
  161 + label: t('common.createTime'),
  162 + field: 'createTime',
  163 + form: {
  164 + show: false
  165 + },
  166 + search: {
  167 + show: true,
  168 + component: 'DatePicker',
  169 + componentProps: {
  170 + type: 'datetimerange',
  171 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  172 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  173 + }
  174 + }
  175 + },
  176 + {
161 177 label: t('table.action'),
162 178 field: 'action',
163 179 width: '270px',
... ...
yudao-ui-admin-vue3/src/views/pay/refund/refund.data.ts
... ... @@ -84,6 +84,15 @@ const crudSchemas = reactive<CrudSchema[]>([
84 84 field: 'createTime',
85 85 form: {
86 86 show: false
  87 + },
  88 + search: {
  89 + show: true,
  90 + component: 'DatePicker',
  91 + componentProps: {
  92 + type: 'datetimerange',
  93 + valueFormat: 'YYYY-MM-DD HH:mm:ss',
  94 + defaultTime: [new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]
  95 + }
87 96 }
88 97 },
89 98 {
... ...
yudao-ui-admin-vue3/src/views/system/dept/dept.data.ts
1 1 import { required } from '@/utils/formRules'
2 2 import { reactive } from 'vue'
  3 +// 表单校验
  4 +export const rules = reactive({
  5 + name: [required],
  6 + sort: [required],
  7 + email: [required],
  8 + phone: [
  9 + {
  10 + pattern:
  11 + /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,
  12 + trigger: 'blur',
  13 + message: '请输入正确的手机号码'
  14 + }
  15 + ]
  16 +})
3 17  
4 18 export const modelSchema = reactive<FormSchema[]>([
5 19 {
... ... @@ -17,7 +31,7 @@ export const modelSchema = reactive&lt;FormSchema[]&gt;([
17 31 },
18 32 {
19 33 label: '负责人',
20   - field: 'email',
  34 + field: 'leaderUserId',
21 35 component: 'Input'
22 36 },
23 37 {
... ... @@ -33,7 +47,7 @@ export const modelSchema = reactive&lt;FormSchema[]&gt;([
33 47 {
34 48 label: '显示排序',
35 49 field: 'sort',
36   - component: 'Input'
  50 + component: 'InputNumber'
37 51 },
38 52 {
39 53 label: '状态',
... ...
yudao-ui-admin-vue3/src/views/system/dept/index.vue
1 1 <script setup lang="ts">
2 2 import { useI18n } from '@/hooks/web/useI18n'
3   -import { ElInput, ElCard, ElTree, ElTreeSelect } from 'element-plus'
  3 +import { ElInput, ElCard, ElTree, ElTreeSelect, ElSelect, ElOption } from 'element-plus'
4 4 import { handleTree } from '@/utils/tree'
5 5 import { onMounted, ref, unref, watch } from 'vue'
6 6 import * as DeptApi from '@/api/system/dept'
7 7 import { Form, FormExpose } from '@/components/Form'
8   -import { modelSchema } from './dept.data'
  8 +import { modelSchema, rules } from './dept.data'
9 9 import { DeptVO } from '@/api/system/dept/types'
10 10 import { useMessage } from '@/hooks/web/useMessage'
  11 +import { getListSimpleUsersApi } from '@/api/system/user'
11 12 const message = useMessage()
12 13 interface Tree {
13 14 id: number
... ... @@ -34,7 +35,7 @@ const filterText = ref(&#39;&#39;)
34 35 const deptOptions = ref() // 树形结构
35 36 const treeRef = ref<InstanceType<typeof ElTree>>()
36 37 const getTree = async () => {
37   - const res = await DeptApi.getDeptPageApi(null)
  38 + const res = await DeptApi.listSimpleDeptApi()
38 39 deptOptions.value = handleTree(res)
39 40 }
40 41 const filterNode = (value: string, data: Tree) => {
... ... @@ -44,6 +45,13 @@ const filterNode = (value: string, data: Tree) =&gt; {
44 45 watch(filterText, (val) => {
45 46 treeRef.value!.filter(val)
46 47 })
  48 +// 用户列表
  49 +const userOption = ref()
  50 +const leaderUserId = ref()
  51 +const getUserList = async () => {
  52 + const res = await getListSimpleUsersApi()
  53 + userOption.value = res
  54 +}
47 55 // 新增
48 56 const handleAdd = (data: { id: number }) => {
49 57 // 重置表单
... ... @@ -54,11 +62,12 @@ const handleAdd = (data: { id: number }) =&gt; {
54 62 }
55 63 // 编辑
56 64 const handleUpdate = async (data: { id: number }) => {
57   - showForm.value = true
58 65 const res = await DeptApi.getDeptApi(data.id)
59 66 formTitle.value = '修改- ' + res?.name
60 67 deptParentId.value = res.parentId
  68 + leaderUserId.value = res.leaderUserId
61 69 unref(formRef)?.setValues(res)
  70 + showForm.value = true
62 71 }
63 72 // 删除
64 73 const handleDelete = async (data: { id: number }) => {
... ... @@ -78,7 +87,7 @@ const submitForm = async () =&gt; {
78 87 try {
79 88 const data = unref(formRef)?.formModel as DeptVO
80 89 data.parentId = deptParentId.value
81   - // TODO: 表单提交待完善
  90 + data.leaderUserId = leaderUserId.value
82 91 if (formTitle.value.startsWith('新增')) {
83 92 await DeptApi.createDeptApi(data)
84 93 } else if (formTitle.value.startsWith('修改')) {
... ... @@ -92,6 +101,7 @@ const submitForm = async () =&gt; {
92 101 }
93 102 onMounted(async () => {
94 103 await getTree()
  104 + await getUserList()
95 105 })
96 106 </script>
97 107 <template>
... ... @@ -123,14 +133,29 @@ onMounted(async () =&gt; {
123 133 <span class="custom-tree-node">
124 134 <span>{{ node.label }}</span>
125 135 <span>
126   - <el-button link v-hasPermi="['system:dept:create']" @click="handleAdd(data)">
127   - <Icon icon="ep:plus" class="mr-1px" />
  136 + <el-button
  137 + link
  138 + type="primary"
  139 + v-hasPermi="['system:dept:create']"
  140 + @click="handleAdd(data)"
  141 + >
  142 + <Icon icon="ep:zoom-in" class="mr-5px" /> {{ t('action.add') }}
128 143 </el-button>
129   - <el-button link v-hasPermi="['system:dept:update']" @click="handleUpdate(data)">
130   - <Icon icon="ep:edit" class="mr-1px" />
  144 + <el-button
  145 + link
  146 + type="primary"
  147 + v-hasPermi="['system:dept:update']"
  148 + @click="handleUpdate(data)"
  149 + >
  150 + <Icon icon="ep:edit" class="mr-1px" /> {{ t('action.edit') }}
131 151 </el-button>
132   - <el-button link v-hasPermi="['system:dept:delete']" @click="handleDelete(data)">
133   - <Icon icon="ep:delete" class="mr-1px" />
  152 + <el-button
  153 + link
  154 + type="primary"
  155 + v-hasPermi="['system:dept:delete']"
  156 + @click="handleDelete(data)"
  157 + >
  158 + <Icon icon="ep:delete" class="mr-1px" /> {{ t('action.del') }}
134 159 </el-button>
135 160 </span>
136 161 </span>
... ... @@ -149,7 +174,7 @@ onMounted(async () =&gt; {
149 174 </div>
150 175 <div v-if="showForm">
151 176 <!-- 操作工具栏 -->
152   - <Form :schema="modelSchema" ref="formRef">
  177 + <Form ref="formRef" :schema="modelSchema" :rules="rules">
153 178 <template #parentId>
154 179 <el-tree-select
155 180 node-key="id"
... ... @@ -159,6 +184,16 @@ onMounted(async () =&gt; {
159 184 check-strictly
160 185 />
161 186 </template>
  187 + <template #leaderUserId>
  188 + <el-select v-model="leaderUserId">
  189 + <el-option
  190 + v-for="item in userOption"
  191 + :key="parseInt(item.id)"
  192 + :label="item.nickname"
  193 + :value="parseInt(item.id)"
  194 + />
  195 + </el-select>
  196 + </template>
162 197 </Form>
163 198 <!-- 操作按钮 -->
164 199 <el-button
... ...
yudao-ui-admin-vue3/src/views/system/dict/dict.data.ts
... ... @@ -7,9 +7,9 @@ import { CrudSchema, useCrudSchemas } from &#39;@/hooks/web/useCrudSchemas&#39;
7 7 const { t } = useI18n()
8 8 // 表单校验
9 9 export const dictDataRules = reactive({
10   - dictType: [required],
11 10 label: [required],
12   - value: [required]
  11 + value: [required],
  12 + sort: [required]
13 13 })
14 14 // crudSchemas
15 15 export const crudSchemas = reactive<CrudSchema[]>([
... ... @@ -29,6 +29,9 @@ export const crudSchemas = reactive&lt;CrudSchema[]&gt;([
29 29 field: 'dictType',
30 30 table: {
31 31 show: false
  32 + },
  33 + form: {
  34 + show: false
32 35 }
33 36 },
34 37 {
... ...
yudao-ui-admin-vue3/src/views/system/menu/index.vue
... ... @@ -25,11 +25,12 @@ import { MenuVO } from &#39;@/api/system/menu/types&#39;
25 25 import { SystemMenuTypeEnum, CommonStatusEnum } from '@/utils/constants'
26 26 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
27 27 import { useMessage } from '@/hooks/web/useMessage'
  28 +import { required } from '@/utils/formRules.js'
28 29 const message = useMessage()
29 30 const { t } = useI18n() // 国际化
30 31 // ========== 创建菜单树结构 ==========
31 32 const loading = ref(true)
32   -const menuData = ref([]) // 树形结构
  33 +const menuData = ref<any[]>([]) // 树形结构
33 34 const getList = async () => {
34 35 const res = await MenuApi.getMenuListApi(queryParams)
35 36 menuData.value = handleTree(res)
... ... @@ -44,7 +45,7 @@ const menuProps = {
44 45 const menuOptions = ref() // 树形结构
45 46 const getTree = async () => {
46 47 const res = await MenuApi.listSimpleMenusApi()
47   - const menu = { id: 0, name: '主类目', children: [] }
  48 + const menu = { id: 0, name: '主类目', children: [] as any[] }
48 49 menu.children = handleTree(res)
49 50 console.info(menu)
50 51 menuOptions.value = menu
... ... @@ -85,12 +86,12 @@ const menuForm = ref&lt;MenuVO&gt;({
85 86 createTime: ''
86 87 })
87 88 // 表单校验
88   -const rules = {
89   - name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
90   - sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
91   - path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],
92   - status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
93   -}
  89 +const rules = reactive({
  90 + name: [required],
  91 + sort: [required],
  92 + path: [required],
  93 + status: [required]
  94 +})
94 95 // 设置标题
95 96 const setDialogTile = (type: string) => {
96 97 dialogTitle.value = t('action.' + type)
... ...
yudao-ui-admin-vue3/src/views/system/notice/notice.data.ts
... ... @@ -8,8 +8,7 @@ const { t } = useI18n() // 国际化
8 8 // 表单校验
9 9 export const rules = reactive({
10 10 title: [required],
11   - type: [required],
12   - status: [required]
  11 + type: [required]
13 12 })
14 13  
15 14 // CrudSchema
... ...
yudao-ui-admin-vue3/src/views/system/post/post.data.ts
... ... @@ -9,8 +9,7 @@ const { t } = useI18n() // 国际化
9 9 export const rules = reactive({
10 10 name: [required],
11 11 code: [required],
12   - sort: [required],
13   - status: [required]
  12 + sort: [required]
14 13 })
15 14  
16 15 // CrudSchema
... ...
yudao-ui-admin-vue3/src/views/system/sms/smsTemplate/sms.template.data.ts
... ... @@ -8,13 +8,12 @@ const { t } = useI18n() // 国际化
8 8 // 表单校验
9 9 export const rules = reactive({
10 10 type: [required],
  11 + status: [required],
  12 + code: [required],
11 13 name: [required],
12 14 content: [required],
13 15 apiTemplateId: [required],
14   - channelId: [required],
15   - code: [required],
16   - sort: [required],
17   - status: [required]
  16 + channelId: [required]
18 17 })
19 18  
20 19 // CrudSchema
... ...
yudao-ui-admin-vue3/src/views/system/user/index.vue
... ... @@ -9,6 +9,7 @@ import {
9 9 ElTreeSelect,
10 10 ElSelect,
11 11 ElOption,
  12 + ElTransfer,
12 13 ElForm,
13 14 ElFormItem,
14 15 ElUpload,
... ... @@ -62,7 +63,7 @@ const { getList, setSearchParams, delList, exportList } = methods
62 63  
63 64 // ========== 创建部门树结构 ==========
64 65 const filterText = ref('')
65   -const deptOptions = ref([]) // 树形结构
  66 +const deptOptions = ref<any[]>([]) // 树形结构
66 67 const searchForm = ref<FormExpose>()
67 68 const treeRef = ref<InstanceType<typeof ElTree>>()
68 69 const getTree = async () => {
... ... @@ -268,6 +269,7 @@ const handleFileSuccess = (response: any): void =&gt; {
268 269 text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'
269 270 }
270 271 message.alert(text)
  272 + getList()
271 273 }
272 274 // 文件数超出提示
273 275 const handleExceed = (): void => {
... ... @@ -281,8 +283,8 @@ const excelUploadError = (): void =&gt; {
281 283 onMounted(async () => {
282 284 await getTree()
283 285 await getPostOptions()
  286 + await getList()
284 287 })
285   -getList()
286 288 </script>
287 289  
288 290 <template>
... ... @@ -482,7 +484,7 @@ getList()
482 484 </template>
483 485 </Dialog>
484 486 <!-- 分配用户角色 -->
485   - <Dialog v-model="roleDialogVisible" title="分配角色">
  487 + <Dialog v-model="roleDialogVisible" title="分配角色" maxHeight="450px">
486 488 <el-form :model="userRole" label-width="80px">
487 489 <el-form-item label="用户名称">
488 490 <el-input v-model="userRole.username" :disabled="true" />
... ... @@ -491,14 +493,15 @@ getList()
491 493 <el-input v-model="userRole.nickname" :disabled="true" />
492 494 </el-form-item>
493 495 <el-form-item label="角色">
494   - <el-select v-model="userRole.roleIds" multiple>
495   - <el-option
496   - v-for="item in roleOptions"
497   - :key="parseInt(item.id)"
498   - :label="item.name"
499   - :value="parseInt(item.id)"
500   - />
501   - </el-select>
  496 + <el-transfer
  497 + v-model="userRole.roleIds"
  498 + :titles="['角色列表', '已选择']"
  499 + :props="{
  500 + key: 'id',
  501 + label: 'name'
  502 + }"
  503 + :data="roleOptions"
  504 + />
502 505 </el-form-item>
503 506 </el-form>
504 507 <!-- 操作按钮 -->
... ...
yudao-ui-admin-vue3/src/views/system/user/user.data.ts
... ... @@ -7,8 +7,18 @@ import { CrudSchema, useCrudSchemas } from &#39;@/hooks/web/useCrudSchemas&#39;
7 7 const { t } = useI18n()
8 8 // 表单校验
9 9 export const rules = reactive({
  10 + username: [required],
10 11 nickname: [required],
11   - status: [required]
  12 + email: [required],
  13 + status: [required],
  14 + mobile: [
  15 + {
  16 + pattern:
  17 + /^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8}$/,
  18 + trigger: 'blur',
  19 + message: '请输入正确的手机号码'
  20 + }
  21 + ]
12 22 })
13 23 // crudSchemas
14 24 const crudSchemas = reactive<CrudSchema[]>([
... ...
yudao-ui-admin-vue3/use.md 0 → 100644
  1 +# 注意事项
  2 +
  3 +- 项目路径请不要使用中文命名!!!会造成解析乱码!!!请使用全英文路径!!!全英文路径!!!全英文路径!!!
  4 +- node >=16 , pnpm >=7,非node16+ pnpm 7+ 安装问题不予解决、不适配
  5 +- 开发建议使用 [谷歌浏览器-开发者版](https://www.google.cn/intl/zh-CN/chrome/dev/) 不支持 IE\QQ 等浏览器
  6 +- 本框架使用 TypeScript ,简称ts,和java的类型差不多,为了简化没过多使用 type
  7 +- 本框架使用 Vue3.2 + setup语法糖,请自行学习相关内容
  8 +- [点击查看:为什么Vue3.2,什么是setup,Vite为什么第一次加载速度慢](https://www.baidu.com)
  9 +- idea 怎么开发? 不知道。
  10 +- 启动方式详见[README.md](./README.md)
  11 +
  12 +## 简单使用
  13 +
  14 +- 目录结构与 vue2 版本基本保持一致
  15 +- 一个页面(以post为例)由4部分组成
  16 +
  17 +```bash
  18 +/src/api/system/post/ [index.ts | types.ts]
  19 +/src/views/system/post/ [index.vue | post.data.ts]
  20 +```
  21 +
  22 +- 其中api内index.ts 与 vue2 基本一致,只不过axios封装了get post put delete upload download 等方法,不用写method: 'get' 了
  23 +- api内types.ts,是接口中的类型声明,与java中vo等保持一致, java中long int => ts 中 number
  24 +- views中,index.vue 与 vue2 基本一致,本框架封装了Search Table Form Descriptions等组件,也可以按照vue2方式去写,参考menu
  25 +- post.data.ts 中主要是表单校验 rules 和表单 crudSchemas ,通过修改crudSchemas 就可以控制增删改查的字段、输入框还是下拉框等等
  26 +- 本框架集成了国际化,不需要可以自己想办法移除,后期不会提供删减版 使用方式
  27 +
  28 +```bash
  29 +import { useI18n } from '@/hooks/web/useI18n'
  30 +const { t } = useI18n()
  31 +t('common.createTime')
  32 +对应翻译文档在 src/locales
  33 +```
... ...
yudao-ui-admin-vue3/vite.config.ts
... ... @@ -138,6 +138,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig =&gt; {
138 138 'vue-router',
139 139 'vue-types',
140 140 'vue-i18n',
  141 + 'element-plus/es',
141 142 'element-plus/es/locale/lang/zh-cn',
142 143 'element-plus/es/locale/lang/en',
143 144 '@iconify/iconify',
... ...
yudao-ui-admin/src/views/system/dept/index.vue
... ... @@ -167,7 +167,7 @@ export default {
167 167 ],
168 168 phone: [
169 169 {
170   - pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
  170 + 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}$/,
171 171 message: "请输入正确的手机号码",
172 172 trigger: "blur"
173 173 }
... ...
yudao-ui-admin/src/views/system/tenant/index.vue
... ... @@ -183,8 +183,6 @@ export default {
183 183 accountCount: [{ required: true, message: "账号额度不能为空", trigger: "blur" }],
184 184 expireTime: [{ required: true, message: "过期时间不能为空", trigger: "blur" }],
185 185 domain: [{ required: true, message: "绑定域名不能为空", trigger: "blur" }],
186   - username: [{ required: true, message: "用户名称不能为空", trigger: "blur" }],
187   - password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }],
188 186 }
189 187 };
190 188 },
... ...