create-young-proj 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. package/README.md +5 -3
  2. package/dist/index.mjs +9 -9
  3. package/package.json +1 -1
  4. package/template-nuxt-admin/Dockerfile +41 -0
  5. package/template-nuxt-admin/README.md +35 -0
  6. package/template-nuxt-admin/app.vue +41 -0
  7. package/template-nuxt-admin/boot.mjs +16 -0
  8. package/template-nuxt-admin/components/ScreenFull.vue +18 -0
  9. package/template-nuxt-admin/components/TopSearch.vue +73 -0
  10. package/template-nuxt-admin/components/TopUser.vue +69 -0
  11. package/template-nuxt-admin/components/YoungChangePassword.vue +87 -0
  12. package/template-nuxt-admin/components/YoungCodeInput.vue +60 -0
  13. package/template-nuxt-admin/components/YoungLink.vue +23 -0
  14. package/template-nuxt-admin/components/YoungLoading.vue +39 -0
  15. package/template-nuxt-admin/components/layout/Footer.vue +29 -0
  16. package/template-nuxt-admin/components/layout/Logo.vue +52 -0
  17. package/template-nuxt-admin/components/layout/Main.vue +41 -0
  18. package/template-nuxt-admin/components/layout/NavBar.vue +77 -0
  19. package/template-nuxt-admin/components/layout/SideBar.vue +90 -0
  20. package/template-nuxt-admin/components/layout/SubMenu.vue +46 -0
  21. package/template-nuxt-admin/components/layout/TabsBar.vue +183 -0
  22. package/template-nuxt-admin/composables/api.ts +104 -0
  23. package/template-nuxt-admin/composables/apis/delete.ts +37 -0
  24. package/template-nuxt-admin/composables/apis/get.ts +83 -0
  25. package/template-nuxt-admin/composables/apis/index.ts +10 -0
  26. package/template-nuxt-admin/composables/apis/patch.ts +74 -0
  27. package/template-nuxt-admin/composables/apis/post.ts +85 -0
  28. package/template-nuxt-admin/composables/config.ts +13 -0
  29. package/template-nuxt-admin/composables/icon.ts +27 -0
  30. package/template-nuxt-admin/composables/nav.ts +60 -0
  31. package/template-nuxt-admin/composables/tags.ts +108 -0
  32. package/template-nuxt-admin/composables/user.ts +29 -0
  33. package/template-nuxt-admin/config/.devrc +1 -0
  34. package/template-nuxt-admin/config/.onlinerc +1 -0
  35. package/template-nuxt-admin/config/.testrc +1 -0
  36. package/template-nuxt-admin/env.d.ts +47 -0
  37. package/template-nuxt-admin/error.vue +53 -0
  38. package/template-nuxt-admin/layouts/blank.vue +9 -0
  39. package/template-nuxt-admin/layouts/default.vue +124 -0
  40. package/template-nuxt-admin/middleware/auth.global.ts +39 -0
  41. package/template-nuxt-admin/nuxt.config.ts +101 -0
  42. package/template-nuxt-admin/package.json +44 -0
  43. package/template-nuxt-admin/pages/home/[id].vue +28 -0
  44. package/template-nuxt-admin/pages/index.vue +20 -0
  45. package/template-nuxt-admin/pages/login.vue +179 -0
  46. package/template-nuxt-admin/pages/system/api.vue +166 -0
  47. package/template-nuxt-admin/pages/system/hooks/useRole.ts +336 -0
  48. package/template-nuxt-admin/pages/system/menuList.vue +329 -0
  49. package/template-nuxt-admin/pages/system/role.vue +117 -0
  50. package/template-nuxt-admin/pages/system/user.vue +214 -0
  51. package/template-nuxt-admin/plugins/directive.ts +26 -0
  52. package/template-nuxt-admin/public/default_avatar.svg +1 -0
  53. package/template-nuxt-admin/public/favicon.ico +0 -0
  54. package/template-nuxt-admin/public/image_placeholder.svg +15 -0
  55. package/template-nuxt-admin/public/tabbar_bg.png +0 -0
  56. package/template-nuxt-admin/rome.json +26 -0
  57. package/template-nuxt-admin/server/api/[...all].ts +10 -0
  58. package/template-nuxt-admin/server/plugins/env.ts +89 -0
  59. package/template-nuxt-admin/server/routes/get/env.ts +13 -0
  60. package/template-nuxt-admin/server/tsconfig.json +3 -0
  61. package/template-nuxt-admin/server/utils/index.ts +35 -0
  62. package/template-nuxt-admin/styles/element.scss +30 -0
  63. package/template-nuxt-admin/styles/index.scss +59 -0
  64. package/template-nuxt-admin/styles/variable.scss +103 -0
  65. package/template-nuxt-admin/tsconfig.json +7 -0
  66. package/template-nuxt-admin/typings/global.d.ts +16 -0
  67. package/template-nuxt-admin/typings/system.d.ts +66 -0
  68. package/template-nuxt-admin/typings/user.d.ts +19 -0
  69. package/template-nuxt-admin/uno.config.ts +40 -0
  70. package/template-nuxt-admin/utils/tool.ts +207 -0
  71. package/template-nuxt-admin/yarn.lock +6849 -0
  72. package/template-uni-app/_npmrc +2 -0
  73. package/template-uni-app/src/layouts/tabbar.vue +6 -6
  74. package/template-uni-app/src/pages/index.vue +16 -12
  75. package/template-uni-app/src/pages/my.vue +7 -11
@@ -0,0 +1,329 @@
1
+ <!--
2
+ * @Author: zhangyang
3
+ * @Date: 2023-07-25 16:45:17
4
+ * @LastEditTime: 2023-07-31 14:21:34
5
+ * @Description:
6
+ -->
7
+ <script lang="ts" setup>
8
+ import { YoungTablePro, useFormMode, YoungDialog, YoungSelect } from '@bluesyoung/ui-vue3-element-plus';
9
+ import type { TableHeadItem } from '@bluesyoung/ui-vue3-element-plus';
10
+ import { deepClone, isArray } from '@bluesyoung/utils';
11
+ import { ElButton } from 'element-plus';
12
+
13
+ definePageMeta({
14
+ title: '菜单管理'
15
+ });
16
+
17
+ const FORM_TEMP: NavArrItem = {
18
+ breadcrumb: 0,
19
+ component: '',
20
+ createdAt: '',
21
+ creator: '',
22
+ icon: '',
23
+ id: 0,
24
+ name: '',
25
+ not_dev: 0,
26
+ parentId: 0,
27
+ path: '',
28
+ permission: '',
29
+ redirect: '',
30
+ sort: 0,
31
+ status: 1,
32
+ title: '',
33
+ updatedAt: '',
34
+ visible: 1
35
+ };
36
+ // key 的类型必须为 string 才会生效!!!
37
+ const expandKeys = ref<Set<string>>(new Set());
38
+
39
+ let tempArr: string[] = [];
40
+ const getFatherAndSon = (arr: NavArrItem[], num?: number): string[] => {
41
+ if (num === 1) {
42
+ tempArr = [];
43
+ }
44
+ for (const item of arr) {
45
+ tempArr.push(item.id + '');
46
+ if (item.children && isArray(item.children) && item.children.length > 0) {
47
+ getFatherAndSon(deepClone(item.children));
48
+ }
49
+ }
50
+ return tempArr;
51
+ };
52
+ const expandChange = (...args: any) => {
53
+ const [row, isOpen] = args as [NavArrItem, boolean];
54
+ const autoid = row.id;
55
+ if (isOpen) {
56
+ expandKeys.value.add(autoid + '');
57
+ } else {
58
+ const allSub = getFatherAndSon([row], 1);
59
+ allSub.forEach((v) => expandKeys.value.delete(v));
60
+ }
61
+ };
62
+ const tableData = ref<NavArrItem[]>([]);
63
+
64
+ const topMenuOption = ref<Partial<NavArrItem>[]>([]);
65
+
66
+ /**
67
+ * 生成节点映射
68
+ */
69
+ const nodeMap = new Map<string | number, NavArrItem>();
70
+ const generateNodeMap = (list: NavArrItem[]) => {
71
+ for (const node of list) {
72
+ nodeMap.set(node.id, node);
73
+ if (node.children && node.children?.length > 0) {
74
+ generateNodeMap(node.children);
75
+ }
76
+ }
77
+ };
78
+
79
+ /**
80
+ * 获取节点列表
81
+ */
82
+ const getList = async () => {
83
+ const list = Object.values(await apis.get.getMenuList());
84
+ generateNodeMap(list);
85
+
86
+ tableData.value = list;
87
+
88
+ topMenuOption.value = [
89
+ { title: '顶级目录', children: list, id: 0 }
90
+ ];
91
+ };
92
+
93
+ const {
94
+ isAdd,
95
+ isEdit,
96
+ form,
97
+ edit,
98
+ del,
99
+ sure,
100
+ clear,
101
+ formRef,
102
+ validForm
103
+ } = useFormMode<NavArrItem>(FORM_TEMP, {
104
+ addCbk: async () => {
105
+ const res = await validForm() as boolean;
106
+ if (res) {
107
+ const v = deepClone(form.value);
108
+ await apis.post.addMenuItem(v);
109
+ ElMessage.success('菜单添加成功!');
110
+ }
111
+ return res;
112
+ },
113
+ modCbk: async () => {
114
+ const res = await validForm() as boolean;
115
+ if (res) {
116
+ const v = deepClone(form.value);
117
+ await apis.patch.changeMenuItem(v);
118
+ ElMessage.success('菜单修改成功!');
119
+ }
120
+ return res;
121
+ },
122
+ delCbk: async (nav: NavArrItem) => {
123
+ apis.delete.deleteMenu(nav.id.toString());
124
+ expandKeys.value.delete(nav.id.toString());
125
+ ElMessage.success('节点删除成功!');
126
+ // 框架有 bug,视图更新不及时
127
+ location.reload();
128
+ },
129
+ cgEffect: async () => {
130
+ await getList();
131
+ }
132
+ });
133
+
134
+ /**
135
+ * 添加节点
136
+ */
137
+ const add = () => {
138
+ form.value.parentId = 0;
139
+ isAdd.value = true;
140
+ };
141
+
142
+ const tableHead: TableHeadItem<NavArrItem>[] = [
143
+ {
144
+ prop: 'title', label: '标题', render: (row) => h('div', { class: 'inline-flex items-center justify-center' }, [
145
+ h('div', { class: row.icon }),
146
+ h('div', row.title)
147
+ ])
148
+ },
149
+ {
150
+ prop: 'sort', label: '同级排序', aligin: 'center', render: (row, i) => h('div', {
151
+ class: 'flex text-2xl justify-center'
152
+ }, [
153
+ h('div', {
154
+ class: 'i-ic-round-keyboard-arrow-up cursor-pointer',
155
+ title: '向上',
156
+ onClick: async () => {
157
+ const arr = nodeMap.get(row.parentId.toString())?.children || [];
158
+ const index = arr.findIndex((m) => m.id === row.id);
159
+ if (index <= 0) {
160
+ return ElMessage.warning('已经到最前面啦!');
161
+ } else {
162
+ const pre = arr[index - 1];
163
+
164
+ const num = row.sort;
165
+ const preNum = pre.sort;
166
+
167
+ if (num !== preNum) {
168
+ row.sort = preNum;
169
+ pre.sort = num;
170
+ } else {
171
+ row.sort = num - 1;
172
+ }
173
+ await Promise.all([
174
+ apis.patch.changeMenuItem(row),
175
+ apis.patch.changeMenuItem(pre),
176
+ ]);
177
+ ElMessage.success('顺序修改成功!');
178
+ getList();
179
+ }
180
+ }
181
+ }),
182
+ h('div', {
183
+ class: 'i-ic-round-keyboard-arrow-down cursor-pointer',
184
+ title: '向下',
185
+ onClick: async () => {
186
+ const arr = nodeMap.get(row.parentId.toString())?.children || [];
187
+ const index = arr.findIndex((m) => m.id === row.id);
188
+ if (index === arr.length - 1) {
189
+ return ElMessage.warning('已经到最后面啦!');
190
+ } else {
191
+ const next = arr[index + 1];
192
+
193
+ const num = row.sort;
194
+ const nextNum = next.sort;
195
+
196
+ if (num !== nextNum) {
197
+ row.sort = nextNum;
198
+ next.sort = num;
199
+ } else {
200
+ row.sort = num + 1;
201
+ }
202
+ await Promise.all([
203
+ apis.patch.changeMenuItem(row),
204
+ apis.patch.changeMenuItem(next),
205
+ ]);
206
+ ElMessage.success('顺序修改成功!');
207
+ getList();
208
+ }
209
+ }
210
+ }),
211
+ ])
212
+ },
213
+ { prop: 'component', label: '页面路径' },
214
+ {
215
+ prop: 'visible', label: '隐藏/显示', render: (row) => h(YoungSelect, {
216
+ modelValue: row.visible,
217
+ options: [{ label: '显示', value: 1 }, { label: '隐藏', value: 0 }],
218
+ 'onUpdate:modelValue': async (e: number) => {
219
+ // 状态未修改
220
+ if (e === row.visible) {
221
+ return;
222
+ }
223
+
224
+ row.visible = e;
225
+ await apis.patch.changeMenuItem({ ...row, visible: e });
226
+ ElMessage.success('菜单修改成功!');
227
+ }
228
+ })
229
+ },
230
+ { prop: 'creator', label: '创建时间' },
231
+ {
232
+ prop: 'id', label: '操作', fixed: 'right', width: '180', render: (row) => h('div', [
233
+ h(ElButton, {
234
+ text: true,
235
+ class: '!p-0',
236
+ type: 'primary',
237
+ onClick: () => edit(row),
238
+ }, {
239
+ default: () => h('div', '编辑')
240
+ }),
241
+ h(ElButton, {
242
+ type: 'primary',
243
+ class: '!p-0',
244
+ text: true,
245
+ onClick: () => {
246
+ form.value.parentId = row.id;
247
+ isAdd.value = true;
248
+ },
249
+ }, {
250
+ default: () => h('div', '添加子节点')
251
+ }),
252
+ h(ElButton, {
253
+ text: true,
254
+ class: '!p-0',
255
+ type: 'danger',
256
+ onClick: () => del(row),
257
+ }, {
258
+ default: () => h('div', '删除')
259
+ })
260
+ ])
261
+ }
262
+ ];
263
+
264
+ getList();
265
+ </script>
266
+
267
+ <template>
268
+ <ElCard>
269
+ <div>
270
+ <ElButton type="primary" plain @click="add">新建菜单</ElButton>
271
+ </div>
272
+ <br />
273
+ <!-- 节点列表 -->
274
+ <YoungTablePro :table-data="tableData" :table-head="tableHead" :tree-props="{ children: 'children' }" row-key="id"
275
+ :expand-row-keys="[...expandKeys]" @expand-change="expandChange" />
276
+ <!-- 添加 / 编辑 -->
277
+ <YoungDialog width="370px" :is-add="isAdd" :diff-form="form" :is-edit="isEdit" @sure="sure" @clear="clear">
278
+ <template #body>
279
+ <ElForm ref="formRef" :model="form" label-width="80px" :label-position="WindowSize['lt-lg'] ? 'top' : 'left'">
280
+ <ElFormItem label="上级目录">
281
+ <ElCascader :model-value="form.parentId" :props="{
282
+ label: 'title',
283
+ value: 'id',
284
+ checkStrictly: true,
285
+ multiple: false
286
+ }" :options="topMenuOption" :show-all-levels="false"
287
+ @update:model-value="(e: any) => form.parentId = e[e.length - 1]" />
288
+ </ElFormItem>
289
+ <ElFormItem label="英文名称" prop="name" :rules="{ required: true, message: '请输入英文名', trigger: 'blur' }">
290
+ <ElInput v-model="form.name" placeholder="请输入英文名" />
291
+ </ElFormItem>
292
+ <ElFormItem label="标题" prop="title" :rules="{ required: true, message: '请输入页面标题', trigger: 'blur' }">
293
+ <ElInput v-model="form.title" placeholder="请输入页面标题" />
294
+ </ElFormItem>
295
+ <ElFormItem label="图标">
296
+ <ElSelect v-model="form.icon" class="select_icon" placeholder="请选择图标">
297
+ <template #prefix>
298
+ <div :class="form.icon" />
299
+ </template>
300
+ <ElOption v-for="(item, index) in MenuIconList" :key="index + 'sfjhaeoir'" :value="item.label">
301
+ <div :class="item.label" />
302
+ </ElOption>
303
+ </ElSelect>
304
+ </ElFormItem>
305
+ <ElFormItem label="排序">
306
+ <ElInput v-model.number="form.sort" />
307
+ </ElFormItem>
308
+ <ElFormItem label="是否显示">
309
+ <ElRadioGroup v-model="form.visible">
310
+ <ElRadio :label="1">显示</ElRadio>
311
+ <ElRadio :label="0">隐藏</ElRadio>
312
+ </ElRadioGroup>
313
+ </ElFormItem>
314
+ <ElFormItem label="路径" prop="component" :rules="form.parentId === 0 ? {} :
315
+ { message: '请输入合法的路径, eg: /path/page', trigger: 'blur', validator: (_: any, v: string) => v.trim() === '' || /\/(.*)\/(.*)/.test(v) }
316
+ ">
317
+ <ElInput v-model="form.component" />
318
+ </ElFormItem>
319
+ </ElForm>
320
+ </template>
321
+ </YoungDialog>
322
+ </ElCard>
323
+ </template>
324
+
325
+ <style lang="scss" scoped>
326
+ :deep(.select_icon .el-input__inner) {
327
+ opacity: 0 !important;
328
+ }
329
+ </style>
@@ -0,0 +1,117 @@
1
+ <!--
2
+ * @Author: zhangyang
3
+ * @Date: 2023-07-25 16:45:39
4
+ * @LastEditTime: 2023-07-31 11:05:49
5
+ * @Description:
6
+ -->
7
+ <script lang="ts" setup>
8
+ import { YoungTablePro, YoungDialog, YoungPagination, YoungSearchForm } from '@bluesyoung/ui-vue3-element-plus';
9
+ import { useRoleApi, useRoleBase, useRoleMenu } from './hooks/useRole';
10
+
11
+ definePageMeta({
12
+ title: '角色管理'
13
+ });
14
+
15
+ const {
16
+ query,
17
+ queryScheme,
18
+ reset,
19
+ getList,
20
+ tableHead,
21
+ tableData,
22
+ baseFormRef,
23
+ base
24
+ } = useRoleBase();
25
+
26
+ const { showPriority, menu } = useRoleMenu();
27
+
28
+ const { showApi, api } = useRoleApi();
29
+
30
+ getList();
31
+ </script>
32
+
33
+ <template>
34
+ <ElCard>
35
+ <YoungSearchForm v-model="query" :search-scheme="queryScheme" :on-search="getList" :on-reset="reset">
36
+ <template #btns>
37
+ <ElButton type="success" @click="base.isAdd = true">添加角色</ElButton>
38
+ </template>
39
+ </YoungSearchForm>
40
+ </ElCard>
41
+
42
+ <br />
43
+
44
+ <ElCard>
45
+ <YoungTablePro :table-data="tableData" :table-head="tableHead">
46
+ <template #operate>
47
+ <ElTableColumn label="操作" width="300px" fixed="right">
48
+ <template #default="scope">
49
+ <ElButton type="primary" link @click="base.edit(scope.row)">信息编辑</ElButton>
50
+ <ElButton type="primary" link @click="menu.edit(scope.row)">菜单编辑</ElButton>
51
+ <ElButton type="primary" link @click="api.edit(scope.row)">接口编辑</ElButton>
52
+ <ElButton type="danger" link @click="base.del(scope.row)">删除</ElButton>
53
+ </template>
54
+ </ElTableColumn>
55
+ </template>
56
+ </YoungTablePro>
57
+ <YoungPagination v-model:page="query.pageNum" v-model:limit="query.pageSize" :total="query.total"
58
+ @page-change="getList" />
59
+ </ElCard>
60
+ <!-- 基础信息编辑 -->
61
+ <YoungDialog :is-add="base.isAdd" :diff-form="base.form" :is-edit="base.isEdit" width="520px" @sure="base.sure"
62
+ @clear="base.clear">
63
+ <template #body>
64
+ <ElForm ref="baseFormRef" :model="base.form" label-width="120px"
65
+ :label-position="WindowSize['lt-lg'] ? 'top' : 'left'">
66
+ <ElFormItem label="角色名称(中文)" prop="name" :rules="[{ required: true, message: '请填写角色名称', trigger: 'blur' }]">
67
+ <ElInput v-model="base.form.name" class="!w-300px" />
68
+ </ElFormItem>
69
+ <ElFormItem label="关键字(英文)" prop="keyword" :rules="[{ required: true, message: '请填写关键字', trigger: 'blur' }]">
70
+ <ElInput v-model="base.form.keyword" class="!w-300px" />
71
+ </ElFormItem>
72
+ <ElFormItem label="角色描述">
73
+ <ElInput v-model="base.form.desc" class="!w-300px" />
74
+ </ElFormItem>
75
+ </ElForm>
76
+ </template>
77
+ </YoungDialog>
78
+ <!-- 菜单编辑 -->
79
+ <YoungDialog real-title="菜单编辑" :diff-form="menu.checkMap" :is-edit="showPriority" top="5vh" width="1200px"
80
+ @sure="menu.sure" @clear="menu.clear">
81
+ <template #body>
82
+ <ElTable :data="menu.tableData" row-key="id" :default-expand-all="false" class="max-h-600px !overflow-y-auto">
83
+ <ElTableColumn prop="name" label="节点名称" width="320px">
84
+ <template #default="scope">
85
+ <ElCheckbox v-model="menu.checkMap[scope.row.id]" @change="menu.selectChange(scope.row)">{{
86
+ scope.row.title
87
+ }}</ElCheckbox>
88
+ </template>
89
+ </ElTableColumn>
90
+ <ElTableColumn v-for="(item, index) in menu.tableHead" v-bind="item" :key="index + 'fsdjhfaer'" />
91
+ <ElTableColumn prop="name" label="隐藏/显示">
92
+ <template #default="scope">
93
+ <ElSwitch v-model="scope.row.visible" :active-value="1" :inactive-value="0" active-color="#409EFF"
94
+ inactive-color="#909399" disabled />
95
+ </template>
96
+ </ElTableColumn>
97
+ </ElTable>
98
+ </template>
99
+ </YoungDialog>
100
+ <!-- 接口编辑 -->
101
+ <YoungDialog real-title="接口编辑" :diff-form="api.checkMap" :is-edit="showApi" top="5vh" width="1200px" @sure="api.sure"
102
+ @clear="api.clear">
103
+ <template #body>
104
+ <ElTable :data="api.tableData" row-key="id" :default-expand-all="false" class="max-h-600px !overflow-y-auto">
105
+ <ElTableColumn prop="name" width="100px">
106
+ <template #header>
107
+ <ElCheckbox v-model="api.isAll" @change="api.changeAll">全选</ElCheckbox>
108
+ </template>
109
+ <template #default="scope">
110
+ <ElCheckbox v-model="api.checkMap[scope.row.id]">{{ scope.row.title }}</ElCheckbox>
111
+ </template>
112
+ </ElTableColumn>
113
+ <ElTableColumn v-for="(item, index) in api.tableHead" v-bind="item" :key="index + 'fsdjhfaer'" />
114
+ </ElTable>
115
+ </template>
116
+ </YoungDialog>
117
+ </template>
@@ -0,0 +1,214 @@
1
+ <!--
2
+ * @Author: zhangyang
3
+ * @Date: 2023-07-25 16:46:00
4
+ * @LastEditTime: 2023-07-31 14:39:16
5
+ * @Description:
6
+ -->
7
+ <script lang="ts" setup>
8
+ import { useFormMode, YoungDialog, YoungTablePro, YoungPagination, YoungSelect, useQuery, YoungSearchForm } from '@bluesyoung/ui-vue3-element-plus';
9
+ import type { TableDataItem, TableHeadItem, SelectOptionItem, YoungSearchScheme } from '@bluesyoung/ui-vue3-element-plus';
10
+ import { deepClone } from '@bluesyoung/utils';
11
+ import { ElButton } from 'element-plus';
12
+
13
+ definePageMeta({
14
+ title: '用户管理'
15
+ });
16
+
17
+ interface Form extends UserItem { op?: any; };
18
+ const FORM_TEMP: Form = {
19
+ id: 0,
20
+ username: '',
21
+ nickname: '',
22
+ mobile: '',
23
+ roleId: 1,
24
+ status: 1,
25
+ initPassword: ''
26
+ };
27
+ const {
28
+ isAdd,
29
+ isEdit,
30
+ edit,
31
+ del,
32
+ sure,
33
+ clear,
34
+ form,
35
+ formRef,
36
+ validForm
37
+ } = useFormMode<Form>(FORM_TEMP, {
38
+ addCbk: async () => {
39
+ const res = await validForm() as boolean;
40
+ if (res) {
41
+ const v = deepClone(form.value);
42
+ await apis.post.addUserItem(v);
43
+ ElMessage.success('新增成功!');
44
+ }
45
+ return res;
46
+ },
47
+ modCbk: async () => {
48
+ const res = await validForm() as boolean;
49
+ if (res) {
50
+ const v = deepClone(form.value);
51
+ await apis.patch.changeUserItem(v);
52
+ ElMessage.success('修改成功!');
53
+ }
54
+ return res;
55
+ },
56
+ delCbk: async (row) => {
57
+ await apis.delete.deleteUser(row.id.toString());
58
+ ElMessage.success('删除成功!');
59
+ query.value.pageNum = 1;
60
+ },
61
+ cgEffect: () => getList(),
62
+ });
63
+
64
+ const tableHead: TableHeadItem<Form>[] = [
65
+ { prop: 'id', label: '用户ID' },
66
+ { prop: 'username', label: '用户名' },
67
+ { prop: 'mobile', label: '手机号' },
68
+ { prop: 'role_name', label: '角色名称' },
69
+ { prop: 'creator', label: '创建信息' },
70
+ {
71
+ prop: 'op', label: '操作', width: '300px', fixed: 'right', render: (row) => h('div', [
72
+ hasPermission('/access/system/update/user') && h(ElButton, {
73
+ type: 'primary',
74
+ text: true,
75
+ class: '!p-0',
76
+ onClick: () => edit(row)
77
+ }, {
78
+ default: () => '编辑'
79
+ }),
80
+ hasPermission('/access/system/update/user') && h(ElButton, {
81
+ type: 'warning',
82
+ text: true,
83
+ class: '!p-0',
84
+ onClick: () => changePwd(row)
85
+ }, {
86
+ default: () => '修改密码'
87
+ }),
88
+ hasPermission('/access/system/del/user') && h(ElButton, {
89
+ type: 'danger',
90
+ text: true,
91
+ class: '!p-0',
92
+ onClick: () => del(row)
93
+ }, {
94
+ default: () => '删除'
95
+ })
96
+ ])
97
+ }
98
+ ];
99
+ const tableData = ref<TableDataItem<Form>[]>([]);
100
+
101
+ const getList = async () => {
102
+ const { list: role_list } = await apis.get.getRoleList({ noPagination: true });
103
+ roleList.value = (role_list || []).map((item: RoleItem) => {
104
+ return {
105
+ label: item.name,
106
+ value: item.id
107
+ };
108
+ });
109
+
110
+ const { list, pageNum, pageSize, total } = await apis.get.getUserList(query.value);
111
+ tableData.value = deepClone(list || []);
112
+
113
+ query.value.pageNum = +pageNum || 1;
114
+ query.value.pageSize = +pageSize || 50;
115
+ query.value.total = +total || 0;
116
+ };
117
+
118
+ const changePwd = (e: UserItem) => {
119
+ ElMessageBox.prompt('请输入新密码').then(async ({ value }) => {
120
+ value = value.trim()
121
+ if (value) {
122
+ await apis.patch.changeUserItem({ id: e.id, newPassword: value });
123
+ ElMessage.success('修改成功!');
124
+ }
125
+ }).catch(() => null);
126
+ };
127
+
128
+ interface Query extends BaseQuery {
129
+ username: string;
130
+ mobile: string;
131
+ status: 0 | 1;
132
+ }
133
+
134
+ const { query, reset } = useQuery<Query>(
135
+ {
136
+ pageNum: 1,
137
+ pageSize: 10,
138
+ total: 0,
139
+ username: '',
140
+ mobile: '',
141
+ status: 1
142
+ },
143
+ getList,
144
+ );
145
+ const queryScheme: YoungSearchScheme<Query> = {
146
+ username: {
147
+ type: 'input',
148
+ tip: '用户名',
149
+ attrs: {
150
+ placeholder: '请输入用户名',
151
+ },
152
+ },
153
+ mobile: {
154
+ type: 'input',
155
+ tip: '手机号',
156
+ attrs: {
157
+ placeholder: '请输入手机号',
158
+ maxlength: 11
159
+ }
160
+ },
161
+ status: {
162
+ type: 'select',
163
+ tip: '状态',
164
+ attrs: {
165
+ placeholder: '用户状态',
166
+ },
167
+ options: [{ label: '禁用', value: 0 }, { label: '启用', value: 1 }],
168
+ }
169
+ };
170
+
171
+ const roleList = ref<SelectOptionItem<number>[]>([]);
172
+
173
+ getList();
174
+ </script>
175
+ <template>
176
+ <ElCard>
177
+ <YoungSearchForm v-model="query" :search-scheme="queryScheme" :on-search="getList" :on-reset="reset">
178
+ <template #btns>
179
+ <ElButton v-permission="'/access/system/create/user'" type="success" @click="isAdd = true">添加用户</ElButton>
180
+ </template>
181
+ </YoungSearchForm>
182
+ </ElCard>
183
+
184
+ <br />
185
+
186
+ <ElCard>
187
+ <YoungTablePro :table-data="tableData" :table-head="tableHead" />
188
+ <YoungPagination v-model:page="query.pageNum" v-model:limit="query.pageSize" :total="query.total"
189
+ @page-change="getList" />
190
+ </ElCard>
191
+
192
+ <YoungDialog :is-add="isAdd" :diff-form="form" :is-edit="isEdit" width="520px" @sure="sure" @clear="clear">
193
+ <template #body>
194
+ <ElForm ref="formRef" :model="form" label-width="100px" :label-position="WindowSize['lt-lg'] ? 'top' : 'left'">
195
+ <ElFormItem label="用户名" prop="username" :rules="{ required: true, message: '请输用户名', trigger: 'blur' }">
196
+ <ElInput v-model="form.username" class="!w-300px" />
197
+ </ElFormItem>
198
+ <ElFormItem label="昵称" prop="nickname" :rules="{ required: true, message: '请输昵称(用于右上角展示)', trigger: 'blur' }">
199
+ <ElInput v-model="form.nickname" class="!w-300px" />
200
+ </ElFormItem>
201
+ <ElFormItem label="手机号" prop="mobile" :rules="{ required: true, message: '请输手机号', trigger: 'blur' }">
202
+ <ElInput v-model="form.mobile" :maxlength="11" class="!w-300px" />
203
+ </ElFormItem>
204
+ <ElFormItem label="角色">
205
+ <YoungSelect v-model="form.roleId" placeholder="请选择角色" :options="roleList" />
206
+ </ElFormItem>
207
+ <ElFormItem v-if="isAdd" label="初始密码" prop="initPassword"
208
+ :rules="{ required: true, message: '请输初始密码', trigger: 'blur' }">
209
+ <ElInput v-model="form.initPassword" class="!w-300px" />
210
+ </ElFormItem>
211
+ </ElForm>
212
+ </template>
213
+ </YoungDialog>
214
+ </template>
@@ -0,0 +1,26 @@
1
+ /*
2
+ * @Author: zhangyang
3
+ * @Date: 2023-07-31 14:04:44
4
+ * @LastEditTime: 2023-07-31 14:33:10
5
+ * @Description:
6
+ */
7
+ import { isString } from '@bluesyoung/utils';
8
+
9
+ export default defineNuxtPlugin((nuxtApp) => {
10
+ /**
11
+ * 鉴自指令定义的zdy
12
+ */
13
+ nuxtApp.vueApp.directive('permission', {
14
+ mounted(el, binding) {
15
+ const { value } = binding;
16
+
17
+ if (isString(value) && value) {
18
+ if (!hasPermission(value as unknown as string)) {
19
+ el.parentNode && el.parentNode.removeChild(el);
20
+ }
21
+ } else {
22
+ throw new Error(`need value! Like v-permission="'/access/get/list'"`);
23
+ }
24
+ },
25
+ });
26
+ });
@@ -0,0 +1 @@
1
+ <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M28.717511 509.428622c0.068267 263.850667 213.8112 477.684622 477.411556 477.639111 263.600356-0.045511 477.275022-213.970489 477.229511-477.843911 0-263.850667-213.720178-477.752889-477.320534-477.752889-126.611911 0-248.035556 50.3808-337.555911 140.014934a478.094222 478.094222 0 0 0-139.764622 337.92z" fill="#FFFFFF" /><path d="M856.814933 833.9456c-12.811378-67.1744-130.753422-131.390578-236.612266-166.479644-5.165511-1.729422-37.387378-16.201956-17.2032-77.459912 52.747378-54.0672 92.728889-141.175467 92.728889-226.941155 0-131.754667-87.699911-200.886044-189.622045-200.886045-101.944889 0-189.394489 69.063111-189.394489 200.886045 0 86.038756 40.004267 173.488356 92.842667 227.487289 20.593778 53.976178-16.224711 74.092089-23.984356 76.777244-100.693333 36.340622-217.679644 100.784356-230.559289 166.479645a478.685867 478.685867 0 0 1-55.591822-574.350223 477.639111 477.639111 0 0 1 536.9856-209.942755C842.183111 107.633778 984.314311 295.5264 984.450844 509.5424a475.363556 475.363556 0 0 1-127.635911 324.4032" fill="#B8D4FF" /></svg>