create-young-proj 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,179 @@
1
+ <!--
2
+ * @Author: zhangyang
3
+ * @Date: 2023-07-21 10:03:11
4
+ * @LastEditTime: 2023-07-28 11:06:30
5
+ * @Description:
6
+ -->
7
+ <script lang="ts" setup>
8
+ import { isMobile } from '@bluesyoung/utils';
9
+ import type { FormInstance } from 'element-plus';
10
+
11
+ definePageMeta({
12
+ auth: false,
13
+ title: '用户登录',
14
+ noCache: true,
15
+ layout: 'blank'
16
+ });
17
+
18
+ const {
19
+ NUXT_PUBLIC_LOGIN_LOGO: LoginLogo,
20
+ NUXT_PUBLIC_LOGIN_BG: LoginBg,
21
+ NUXT_PUBLIC_TITLE: Title,
22
+ NUXT_PUBLIC_SUB_TITLE: SubTitle,
23
+ NUXT_PUBLIC_SLOGAN: SloGan,
24
+ NUXT_PUBLIC_CURRENT_VERSION: Version
25
+ } = window.__YOUNG_ENV__;
26
+
27
+ const { SaveFlag, hasLogin, cookie } = storeToRefs(useUserStore());
28
+
29
+ const loginType = ref<'account' | 'code'>('account');
30
+
31
+ const form = reactive<LoginForm>({
32
+ mobile: '',
33
+ vercode: '',
34
+ password: '',
35
+ });
36
+
37
+ const formRef = ref<FormInstance>();
38
+
39
+ const loginHandler = () => {
40
+ formRef.value?.validate(async (valid: boolean) => {
41
+ try {
42
+ if (valid) {
43
+ const { enter } = useFullscreen();
44
+ const { width, height } = useWindowSize();
45
+
46
+ const data = await apis.post.login(form);
47
+ if (data) {
48
+ cookie.value = data;
49
+ height.value > width.value && enter();
50
+ showSuccessToast('登录成功!');
51
+ navigateTo('/');
52
+ }
53
+ } else {
54
+ ElMessage.error('请仔细检查输入内容');
55
+ return;
56
+ }
57
+ } catch (error) {
58
+ console.error(error);
59
+ }
60
+ });
61
+ };
62
+
63
+ onMounted(() => {
64
+ if (SaveFlag.value && hasLogin.value) {
65
+ navigateTo({
66
+ path: '/',
67
+ replace: true
68
+ });
69
+ }
70
+ });
71
+ </script>
72
+
73
+ <template>
74
+ <img :src="LoginBg" class="absolute object-center object-cover w-100vw h-100vh blur-6 brightness-50" />
75
+ <div class="flex flex-col items-center login-container pt-6%">
76
+ <div class="w-80px h-80px">
77
+ <img class="h-full" :src="LoginLogo" referrerpolicy="no-referrer" />
78
+ </div>
79
+ <div class="title">{{ Title }}</div>
80
+ <div v-if="SubTitle" v-html="SubTitle" class="sub-title" />
81
+
82
+ <ElCard class="w-430px mt-3%" lt-sm="w-[96%] mx-[2%]">
83
+ <ElForm ref="formRef" :model="form">
84
+ <VanTabs v-model:active="loginType" size="large" @change="formRef?.resetFields?.()">
85
+ <VanTab name="account" title="账密登录">
86
+ <ElFormItem prop="mobile" :rules="[
87
+ { required: true, trigger: 'blur', message: '请输入手机号' },
88
+ { message: '请输入合法的手机号', trigger: 'blur', validator: (_: any, v: string) => isMobile(v) }
89
+ ]" class="mt-20px">
90
+ <ElInput v-model="form.mobile" placeholder="请输入手机号" maxlength="11" class="!h-52px" clearable />
91
+ </ElFormItem>
92
+ <ElFormItem v-if="loginType === 'account'" prop="password" :rules="[
93
+ { required: true, trigger: 'blur', message: '请输入密码' },
94
+ { min: 8, max: 16, trigger: 'blur', message: '请输入8-16位字符!' }
95
+ ]">
96
+ <ElInput type="password" v-model="form.password" minlength="8" maxlength="16" placeholder="请输入密码"
97
+ class="!h-52px" clearable show-password @keyup.enter="loginHandler" />
98
+ </ElFormItem>
99
+ </VanTab>
100
+ <VanTab name="code" title="短信登录">
101
+ <ElFormItem prop="mobile" :rules="[
102
+ { required: true, trigger: 'blur', message: '请输入手机号' },
103
+ { message: '请输入合法的手机号', trigger: 'blur', validator: (_: any, v: string) => isMobile(v) }
104
+ ]" class="mt-20px">
105
+ <ElInput v-model="form.mobile" placeholder="请输入手机号" maxlength="11" class="!h-52px" clearable />
106
+ </ElFormItem>
107
+ <ElFormItem v-if="loginType === 'code'" prop="vercode"
108
+ :rules="[{ required: true, trigger: 'blur', message: '请输入验证码' }]">
109
+ <YoungCodeInput v-model="form.vercode" :tel="form.mobile" @enter="loginHandler" />
110
+ </ElFormItem>
111
+ </VanTab>
112
+ </VanTabs>
113
+
114
+ <ElFormItem>
115
+ <div class="flex justify-between w-full">
116
+ <ElCheckbox v-model="SaveFlag">三天之内免登录</ElCheckbox>
117
+ <ElButton v-show="loginType === 'account'" type="warning" @click="useChangePassword" link>忘记密码?</ElButton>
118
+ </div>
119
+ </ElFormItem>
120
+ </ElForm>
121
+
122
+ <VanButton type="primary" class="w-full" @click="loginHandler">
123
+ 登录
124
+ </VanButton>
125
+ </ElCard>
126
+
127
+ <div v-if="SloGan" class="slogan">
128
+ {{ SloGan }}
129
+ </div>
130
+ <div class="content-wrapper">
131
+ <div class="mb-5% text-[#c0c0c0] text-16px">{{ Version }}</div>
132
+ </div>
133
+ </div>
134
+ </template>
135
+
136
+ <style lang="scss" scoped>
137
+ @-webkit-keyframes left-to-right {
138
+ from {
139
+ -webkit-transform: translateX(-100%);
140
+ transform: translateX(-100%);
141
+ }
142
+
143
+ to {
144
+ -webkit-transform: translateX(0);
145
+ transform: translateX(0);
146
+ }
147
+ }
148
+
149
+ @keyframes left-to-right {
150
+ from {
151
+ -webkit-transform: translateX(-100%);
152
+ transform: translateX(-100%);
153
+ }
154
+
155
+ to {
156
+ -webkit-transform: translateX(0);
157
+ transform: translateX(0);
158
+ }
159
+ }
160
+
161
+ .login-container {
162
+ @apply relative overflow-hidden h-100vh w-full;
163
+
164
+ .title {
165
+ @apply mt-10px text-[#fff] text-24px font-bold;
166
+ }
167
+
168
+ .sub-title {
169
+ @apply mt-10px text-[#f5f5f5] text-16px mb-30px;
170
+ }
171
+
172
+ .slogan {
173
+ @apply flex flex-1 justify-center items-center text-[#fff] font-500 text-26px text-shadow;
174
+ -webkit-animation: left-to-right 1s cubic-bezier(0.175, 0.885, 0.32, 1.275);
175
+ animation: left-to-right 1s cubic-bezier(0.175, 0.885, 0.32, 1.275);
176
+ }
177
+ }
178
+ </style>
179
+
@@ -0,0 +1,166 @@
1
+ <!--
2
+ * @Author: zhangyang
3
+ * @Date: 2023-07-25 16:44:56
4
+ * @LastEditTime: 2023-07-31 11:36:37
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, ElTag } from 'element-plus';
12
+
13
+ definePageMeta({
14
+ title: '接口管理'
15
+ });
16
+
17
+ interface Form extends ApiItem { op?: any; };
18
+ const FORM_TEMP: Form = {
19
+ id: 0,
20
+ path: '',
21
+ desc: '',
22
+ method: 'POST',
23
+ category: '',
24
+ roleIds: []
25
+ };
26
+ const {
27
+ isAdd,
28
+ isEdit,
29
+ edit,
30
+ del,
31
+ sure,
32
+ clear,
33
+ form,
34
+ formRef,
35
+ validForm
36
+ } = useFormMode<Form>(FORM_TEMP, {
37
+ addCbk: async () => {
38
+ const res = await validForm() as boolean;
39
+ if (res) {
40
+ const v = deepClone(form.value);
41
+ await apis.post.addApiItem(v);
42
+ ElMessage.success('新增成功!');
43
+ }
44
+ return res;
45
+ },
46
+ modCbk: async () => {
47
+ const res = await validForm() as boolean;
48
+ if (res) {
49
+ const v = deepClone(form.value);
50
+ await apis.patch.changeApiItem(v);
51
+ ElMessage.success('修改成功!');
52
+ }
53
+ return res;
54
+ },
55
+ delCbk: async (row) => {
56
+ await apis.delete.deleteApi(row.id.toString());
57
+ ElMessage.success('删除成功!');
58
+ query.value.pageNum = 1;
59
+ },
60
+ cgEffect: () => getList(),
61
+ });
62
+ const tableHead: TableHeadItem<Form>[] = [
63
+ { prop: 'id', label: '接口ID' },
64
+ { prop: 'desc', label: '接口描述' },
65
+ { prop: 'category', label: '接口分组' },
66
+ { prop: 'path', label: '接口路径' },
67
+ { prop: 'creator', label: '创建信息' },
68
+ {
69
+ prop: 'method', label: '接口方法', render: (row) => h(ElTag, {
70
+ effect: 'dark',
71
+ type: MethodObj[row.method]
72
+ },
73
+ {
74
+ default: () => row.method
75
+ }
76
+ )
77
+ },
78
+ {
79
+ prop: 'op', label: '操作', render: (row) => h('div', [
80
+ h(ElButton, { type: 'primary', link: true, onClick: () => edit(row) }, { default: () => '编辑' }),
81
+ h(ElButton, { type: 'danger', link: true, onClick: () => del(row) }, { default: () => '删除' })
82
+ ])
83
+ }
84
+ ];
85
+ const tableData = ref<TableDataItem<Form>[]>([]);
86
+
87
+ const getList = async () => {
88
+ const { list: role_list } = await apis.get.getRoleList({ noPagination: true });
89
+ roleList.value = (role_list || []).map((item: RoleItem) => {
90
+ return {
91
+ label: item.name,
92
+ value: item.id
93
+ };
94
+ });
95
+
96
+ const { list, pageNum, pageSize, total } = await apis.get.getApiList(query.value);
97
+ tableData.value = deepClone(list || []);
98
+ query.value.pageNum = +pageNum || 1;
99
+ query.value.pageSize = +pageSize || 50;
100
+ query.value.total = +total || 0;
101
+ };
102
+
103
+ type Query = BaseQuery & Partial<Form>;
104
+ const { query, reset } = useQuery<Query>(
105
+ {
106
+ pageNum: 1,
107
+ pageSize: 10,
108
+ total: 0,
109
+ path: '',
110
+ },
111
+ getList,
112
+ );
113
+ const queryScheme: YoungSearchScheme<Query> = {
114
+ path: {
115
+ type: 'input',
116
+ tip: '接口路径',
117
+ attrs: {
118
+ placeholder: '请输入接口路径',
119
+ },
120
+ },
121
+ };
122
+
123
+ const roleList = ref<SelectOptionItem<number>[]>([]);
124
+
125
+ getList();
126
+ </script>
127
+ <template>
128
+ <ElCard>
129
+ <YoungSearchForm v-model="query" :search-scheme="queryScheme" :on-search="getList" :on-reset="reset">
130
+ <template #btns>
131
+ <ElButton type="success" @click="isAdd = true">添加接口</ElButton>
132
+ </template>
133
+ </YoungSearchForm>
134
+ </ElCard>
135
+
136
+ <br />
137
+
138
+ <ElCard>
139
+ <YoungTablePro :table-data="tableData" :table-head="tableHead" />
140
+ <YoungPagination v-model:page="query.pageNum" v-model:limit="query.pageSize" :total="query.total"
141
+ @page-change="getList" />
142
+ </ElCard>
143
+ <YoungDialog :is-add="isAdd" :is-edit="isEdit" :diff-form="form" width="520px" @sure="sure" @clear="clear">
144
+ <template #body>
145
+ <ElForm ref="formRef" :model="form" label-width="100px" :label-position="WindowSize['lt-lg'] ? 'top' : 'left'">
146
+ <ElFormItem label="分组名称" prop="category" :rules="{ required: true, message: '请填写分组名称', trigger: 'blur' }">
147
+ <ElInput v-model="form.category" class="!w-300px" />
148
+ </ElFormItem>
149
+ <ElFormItem label="接口描述" prop="desc" :rules="{ required: true, message: '请填写接口描述', trigger: 'blur' }">
150
+ <ElInput v-model="form.desc" class="!w-300px" />
151
+ </ElFormItem>
152
+ <ElFormItem label="请求方法">
153
+ <YoungSelect v-model="form.method" class="!w-300px"
154
+ :options="Object.keys(MethodObj).map((item) => ({ label: item, value: item }))" />
155
+ </ElFormItem>
156
+ <ElFormItem label="接口路径" prop="path"
157
+ :rules="{ message: '请填写合法的接口路径, eg: /user/list', trigger: 'blur', validator: (_: any, v: string) => /\/(.*)\/(.*)/.test(v) }">
158
+ <ElInput v-model="form.path" class="!w-300px" />
159
+ </ElFormItem>
160
+ <ElFormItem label="关联角色">
161
+ <YoungSelect v-model="form.roleIds" multiple placeholder="请选择角色" class="!w-300px" :options="roleList" />
162
+ </ElFormItem>
163
+ </ElForm>
164
+ </template>
165
+ </YoungDialog>
166
+ </template>
@@ -0,0 +1,336 @@
1
+ /*
2
+ * @Author: zhangyang
3
+ * @Date: 2023-07-26 15:51:14
4
+ * @LastEditTime: 2023-07-28 11:22:35
5
+ * @Description:
6
+ */
7
+ import type {
8
+ SelectOptionItem,
9
+ TableDataItem,
10
+ TableHeadItem,
11
+ YoungSearchScheme,
12
+ } from '@bluesyoung/ui-vue3-element-plus';
13
+ import { YoungSelect, useFormMode, useQuery } from '@bluesyoung/ui-vue3-element-plus';
14
+ import { deepClone } from '@bluesyoung/utils';
15
+
16
+ export const useRoleBase = () => {
17
+ const FORM_TEMP: RoleItem = {
18
+ id: 0,
19
+ name: '',
20
+ keyword: '',
21
+ desc: '',
22
+ status: 1,
23
+ sort: 0,
24
+ };
25
+
26
+ const { isAdd, isEdit, edit, del, sure, clear, form, formRef, validForm } = useFormMode(
27
+ FORM_TEMP,
28
+ {
29
+ addCbk: async () => {
30
+ const res = (await validForm()) as boolean;
31
+ if (res) {
32
+ const v = deepClone(form.value);
33
+ await apis.post.addRoleItem(v);
34
+ ElMessage.success('新增成功!');
35
+ }
36
+ return res;
37
+ },
38
+ modCbk: async () => {
39
+ const res = (await validForm()) as boolean;
40
+ if (res) {
41
+ const v = deepClone(form.value);
42
+ await apis.patch.changeRoleItem(v);
43
+ ElMessage.success('修改成功!');
44
+ }
45
+ return res;
46
+ },
47
+ delCbk: async (row) => {
48
+ await apis.delete.deleteRole(row.id.toString());
49
+ ElMessage.success('删除成功!');
50
+ query.value.pageNum = 1;
51
+ },
52
+ cgEffect: () => getList(),
53
+ },
54
+ );
55
+
56
+ const options: SelectOptionItem[] = [
57
+ { label: '禁用', value: 0 },
58
+ { label: '启用', value: 1 },
59
+ ];
60
+
61
+ const tableHead: TableHeadItem<RoleItem>[] = [
62
+ { label: '角色ID', prop: 'id' },
63
+ { label: '角色关键字', prop: 'keyword' },
64
+ { label: '角色名称', prop: 'name' },
65
+ { label: '角色描述', prop: 'desc' },
66
+ { label: '创建信息', prop: 'creator' },
67
+ {
68
+ label: '启用状态',
69
+ prop: 'status',
70
+ render: (row) =>
71
+ h(YoungSelect, {
72
+ modelValue: row.status,
73
+ options,
74
+ 'onUpdate:modelValue': async (status) => {
75
+ // 状态未修改
76
+ if (status === row.status) {
77
+ return;
78
+ }
79
+
80
+ row.status = status as 0 | 1;
81
+ await apis.patch.changeRoleItem(row);
82
+ ElMessage.success('修改成功!');
83
+ },
84
+ }),
85
+ },
86
+ ];
87
+ const tableData = ref<TableDataItem<RoleItem>[]>([]);
88
+ const getList = async () => {
89
+ const { list, pageNum, pageSize, total } = await apis.get.getRoleList(query.value);
90
+ tableData.value = deepClone(list || []);
91
+ query.value.pageNum = +pageNum || 1;
92
+ query.value.pageSize = +pageSize || 50;
93
+ query.value.total = +total || 0;
94
+ };
95
+
96
+ interface Query extends BaseQuery {
97
+ name: string;
98
+ keyword: string;
99
+ status: 0 | 1;
100
+ }
101
+
102
+ const { query, reset } = useQuery<Query>(
103
+ {
104
+ pageNum: 1,
105
+ pageSize: 10,
106
+ total: 0,
107
+ name: '',
108
+ keyword: '',
109
+ status: 1,
110
+ },
111
+ getList,
112
+ );
113
+
114
+ const queryScheme: YoungSearchScheme<Query> = {
115
+ name: {
116
+ type: 'input',
117
+ tip: '角色名称',
118
+ attrs: {
119
+ placeholder: '请输入角色名称(中文)',
120
+ },
121
+ },
122
+ keyword: {
123
+ type: 'input',
124
+ tip: '角色关键字',
125
+ attrs: {
126
+ placeholder: '请输入角色关键字(英文)',
127
+ },
128
+ },
129
+ status: {
130
+ type: 'select',
131
+ tip: '状态',
132
+ attrs: {
133
+ placeholder: '角色状态',
134
+ },
135
+ options,
136
+ },
137
+ };
138
+
139
+ return {
140
+ query,
141
+ queryScheme,
142
+ getList,
143
+ reset,
144
+ tableHead,
145
+ tableData,
146
+ baseFormRef: formRef,
147
+ base: reactive({
148
+ form,
149
+ isAdd,
150
+ isEdit,
151
+ edit,
152
+ del,
153
+ sure,
154
+ clear,
155
+ }),
156
+ };
157
+ };
158
+
159
+ export const useRoleMenu = () => {
160
+ const showPriority = ref(false);
161
+ const currRole = ref(0);
162
+ const access = ref<number[]>([]);
163
+ const origin = ref<number[]>([]);
164
+
165
+ const tableHead = ref<TableHeadItem<NavArrItem>[]>([
166
+ { label: '菜单名称', prop: 'title' },
167
+ { label: '菜单id', prop: 'id' },
168
+ { label: '父节点', prop: 'parentId' },
169
+ { label: '页面路径', prop: 'component' },
170
+ ]);
171
+ const tableData = ref<TableDataItem<NavArrItem>[]>([]);
172
+
173
+ const checkMap = ref<Record<number, boolean>>({});
174
+
175
+ /**
176
+ * 生成节点映射
177
+ */
178
+ const nodeMap = new Map<number, NavArrItem>();
179
+ const generateNodeMap = (list: NavArrItem[]) => {
180
+ for (const node of list) {
181
+ nodeMap.set(node.id, node);
182
+ if (node.children && node.children?.length > 0) {
183
+ generateNodeMap(node.children);
184
+ }
185
+ }
186
+ };
187
+
188
+ /**
189
+ * 多级联动选择
190
+ */
191
+ const selectChange = (item: NavArrItem) => {
192
+ if (item.children && item.children?.length !== 0) {
193
+ item.children.forEach((v) => {
194
+ checkMap.value[v.id] = checkMap.value[item.id];
195
+ selectChange(v);
196
+ });
197
+ }
198
+ if (checkMap.value[item.id]) {
199
+ while (item.parentId) {
200
+ const tp = nodeMap.get(item.parentId);
201
+ if (tp) {
202
+ item = tp;
203
+ checkMap.value[item.id] = true;
204
+ } else {
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ };
210
+
211
+ const edit = async (row: TableDataItem<RoleItem>) => {
212
+ currRole.value = row.id;
213
+ checkMap.value = {};
214
+ nodeMap.clear();
215
+
216
+ const { list, accessIds } = await apis.get.getRoleMenuTree(row.id);
217
+ generateNodeMap(list);
218
+ (Array.from(accessIds) as number[]).forEach((v) => {
219
+ checkMap.value[v] = true;
220
+ });
221
+
222
+ tableData.value = deepClone(list);
223
+ access.value = accessIds;
224
+ origin.value = accessIds;
225
+ showPriority.value = true;
226
+ };
227
+ const clear = () => {
228
+ showPriority.value = false;
229
+ currRole.value = 0;
230
+ tableData.value.length = 0;
231
+ };
232
+
233
+ const sure = async () => {
234
+ const before = origin.value.slice();
235
+ const now = Object.entries(checkMap.value)
236
+ .filter(([k, v]) => v)
237
+ .map(([k, v]) => +k);
238
+
239
+ const add = now.filter((v) => !before.includes(v));
240
+ const del = before.filter((v) => !now.includes(v));
241
+ await apis.patch.changeRoleMenu(currRole.value, add, del);
242
+ ElMessage.success('修改成功!');
243
+ clear();
244
+ };
245
+
246
+ return {
247
+ showPriority,
248
+ menu: reactive({
249
+ checkMap,
250
+ tableHead,
251
+ tableData,
252
+ edit,
253
+ clear,
254
+ sure,
255
+ selectChange,
256
+ }),
257
+ };
258
+ };
259
+
260
+ export const useRoleApi = () => {
261
+ const showApi = ref(false);
262
+ const currRole = ref(0);
263
+ const access = ref<number[]>([]);
264
+ const origin = ref<number[]>([]);
265
+
266
+ const tableHead = ref<TableHeadItem<ApiItem>[]>([
267
+ { prop: 'desc', label: '接口描述' },
268
+ { prop: 'id', label: '接口ID' },
269
+ { prop: 'method', label: '接口方法' },
270
+ { prop: 'path', label: '请求地址' },
271
+ ]);
272
+ const tableData = ref<TableDataItem<ApiItem>[]>([]);
273
+
274
+ const checkMap = ref<Record<number, boolean>>({});
275
+
276
+ const edit = async (row: TableDataItem<RoleItem>) => {
277
+ currRole.value = row.id;
278
+ checkMap.value = {};
279
+
280
+ const { list, accessIds } = await apis.get.getRoleApis(row.id);
281
+ (Array.from(accessIds) as number[]).forEach((v) => {
282
+ checkMap.value[v] = true;
283
+ });
284
+
285
+ tableData.value = deepClone(
286
+ Array.from(list)
287
+ .map((item: any) => item.children)
288
+ .flat(),
289
+ );
290
+
291
+ access.value = accessIds;
292
+ origin.value = accessIds;
293
+ showApi.value = true;
294
+
295
+ isAll.value = tableData.value.length === access.value.length;
296
+ };
297
+ const clear = () => {
298
+ showApi.value = false;
299
+ currRole.value = 0;
300
+ tableData.value.length = 0;
301
+ };
302
+
303
+ const sure = async () => {
304
+ const before = origin.value.slice();
305
+ const now = Object.entries(checkMap.value)
306
+ .filter(([k, v]) => v)
307
+ .map(([k, v]) => +k);
308
+
309
+ const add = now.filter((v) => !before.includes(v));
310
+ const del = before.filter((v) => !now.includes(v));
311
+ await apis.patch.changeRoleApi(currRole.value, add, del);
312
+ ElMessage.success('修改成功!');
313
+ clear();
314
+ };
315
+
316
+ const isAll = ref(false);
317
+ const changeAll = () => {
318
+ tableData.value.forEach((i) => {
319
+ checkMap.value[i.id] = isAll.value;
320
+ });
321
+ };
322
+
323
+ return {
324
+ showApi,
325
+ api: reactive({
326
+ checkMap,
327
+ tableHead,
328
+ tableData,
329
+ edit,
330
+ clear,
331
+ sure,
332
+ isAll,
333
+ changeAll,
334
+ }),
335
+ };
336
+ };