Compare commits

..

2 Commits

  1. 2
      .env
  2. 3
      src/api/Core/CoreDict/index.js
  3. 73
      src/api/Core/CoreMenu/index.js
  4. 3
      src/api/index.js
  5. 28
      src/components/AntDesignVue/CustomAntDesignVue/TablePagination/index.vue
  6. 11
      src/components/AntDesignVue/CustomAntDesignVue/index.js
  7. 2
      src/main.js
  8. 2
      src/router/index.js
  9. 91
      src/stores/baseData.js
  10. 2
      src/views/Auth/Dict/index.vue
  11. 18
      src/views/Auth/Env/DataModal.js
  12. 20
      src/views/Auth/Env/EnvForm.vue
  13. 45
      src/views/Auth/Env/EnvPage.vue
  14. 212
      src/views/Auth/Menu/DataModal.js
  15. 185
      src/views/Auth/Menu/MenuForm.vue
  16. 244
      src/views/Auth/Menu/MenuPage.vue
  17. 59
      src/views/Auth/Menu/MenuTable.vue
  18. 34
      src/views/Auth/Menu/MenuTree.vue
  19. 16
      src/views/Auth/Menu/dataModal.js
  20. 64
      src/views/Auth/Menu/index.vue

@ -1,4 +1,4 @@
VITE_HOME_REDIRECT = '/env' # 默认路由 VITE_HOME_REDIRECT = '/menu' # 默认路由
VITE_TITLE = '星撰玉衡' # 项目名称 VITE_TITLE = '星撰玉衡' # 项目名称
VITE_BASE_URL = '/api' # 请求默认前缀 VITE_BASE_URL = '/api' # 请求默认前缀
VITE_HTTP_TIMEOUT = 30000 VITE_HTTP_TIMEOUT = 30000

@ -67,10 +67,9 @@ export const CoreDict = {
isTree: true, isTree: true,
dictKey: undefined dictKey: undefined
}) => { }) => {
if(data.tree)
return HTTP({ return HTTP({
method: 'get', method: 'get',
url: `/coredict/details/${data.dictKey}`, url: `/coredict/list/${data.dictKey}`,
params: { params: {
isTree: data.isTree isTree: data.isTree
} }

@ -0,0 +1,73 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: index.js -
// | @创建时间: 2024-07-11 14:26
// | @更新时间: 2024-07-11 14:26
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import HTTP from '@/api/http.js';
import { PageData } from '@utils/DefaultData.js';
export const CoreMenu = {
/**
* Name: createMenu
* Desc: 新增菜单
* Time: 2024-07-11 14:33:50 -
* */
createMenu: async (data) => {
return HTTP({
method: 'post',
url: '/coremenu',
data,
});
},
/**
* Name: getMenu
* Desc: 获取菜单的分页或列表
* Time: 2024-07-11 14:33:54 -
* */
getMenu: async (
params = {
...PageData,
serviceInfo: undefined,
},
) => {
return HTTP({
method: 'get',
url: '/coremenu',
params,
});
},
/**
* Name: removeMenu
* Desc: 删除菜单
* Time: 2024-07-11 14:33:59 -
* */
removeMenu: async (menuId) => {
return HTTP({
method: 'delete',
url: `/coremenu/${menuId}`,
});
},
/**
* Name: updateMenu
* Desc: 编辑菜单
* Time: 2024-07-11 14:34:03 -
* */
updateMenu: async (menuId, data) => {
return HTTP({
method: 'patch',
url: `/coremenu/${menuId}`,
data,
});
},
};

@ -15,5 +15,6 @@ import { DefaultSign } from '@/api/Auth/AuthUser/index.js';
import { CoreService } from '@/api/Core/CoreService/index.js'; import { CoreService } from '@/api/Core/CoreService/index.js';
import { CoreDict } from '@/api/Core/CoreDict/index.js'; import { CoreDict } from '@/api/Core/CoreDict/index.js';
import { CoreEnv } from '@/api/Core/CoreEnv/index.js'; import { CoreEnv } from '@/api/Core/CoreEnv/index.js';
import { CoreMenu } from '@/api/Core/CoreMenu/index.js';
export { Sign, DefaultSign, CoreService, CoreDict, CoreEnv }; export { Sign, DefaultSign, CoreService, CoreDict, CoreEnv, CoreMenu };

@ -0,0 +1,28 @@
<script setup name="TablePagination">
defineOptions({
name: 'TablePagination',
});
const props = defineProps({
data: {
type: Object,
required: true,
},
});
</script>
<template>
<APagination
v-model:current="props.data.pageInfo.pageNumber"
:total="props.data.total"
:pageSize="props.data.pageInfo.pageSize"
:pageSizeOptions="[10, 20, 50, 100, 200]"
:showSizeChanger="true"
:showQuickJumper="true"
:showTotal="(total) => `共 ${total} 条`"
@change="props.data.handlePageChange"
/>
</template>
<style scoped>
</style>

@ -13,12 +13,13 @@
import CreateAntdButton from './Button/CreateAntdButton.vue'; import CreateAntdButton from './Button/CreateAntdButton.vue';
import AckCreateAntdButton from './Button/AckCreateAntdButton.vue'; import AckCreateAntdButton from './Button/AckCreateAntdButton.vue';
import AntdModalTemplate from './AntdModalTemplate/index.vue'; import TableRemoveButton from './Button/TableRemoveButton.vue';
import TableUpdateButton from './Button/TableUpdateButton.vue';
import IconSelect from './IconSelect/index.vue'; import IconSelect from './IconSelect/index.vue';
import AntdModalTemplate from './AntdModalTemplate/index.vue';
import CustomIconSelect from './IconSelect/customIconSelect.vue'; import CustomIconSelect from './IconSelect/customIconSelect.vue';
import TableColumChoose from '@/components/AntDesignVue/CustomAntDesignVue/TableColumChoose/index.vue'; import TableColumChoose from './TableColumChoose/index.vue';
import TableRemoveButton from '@/components/AntDesignVue/CustomAntDesignVue/Button/TableRemoveButton.vue'; import TablePagination from './TablePagination/index.vue';
import TableUpdateButton from '@/components/AntDesignVue/CustomAntDesignVue/Button/TableUpdateButton.vue';
// console.log(AntdModalTemplate); // console.log(AntdModalTemplate);
@ -30,7 +31,7 @@ export default function setupCustomAntdComponents(app) {
AntdModalTemplate, AntdModalTemplate,
IconSelect, IconSelect,
CustomIconSelect, CustomIconSelect,
TableColumChoose,TableRemoveButton,TableUpdateButton TableColumChoose,TableRemoveButton,TableUpdateButton,TablePagination
]; ];
for (let component of customComponentList) { for (let component of customComponentList) {
app.component(component.name, component); app.component(component.name, component);

@ -6,7 +6,7 @@ window.pino = Pino({
deb: 20, deb: 20,
}, },
browser: { browser: {
asObject: true, asObject: false,
serialize: true serialize: true
}, },
}); });

@ -45,7 +45,7 @@ export default function createRoutering() {
title: '菜单管理', title: '菜单管理',
icon: '' icon: ''
}, },
component: () => import('@/views/Auth/Menu/index.vue') component: () => import('@/views/Auth/Menu/MenuPage.vue')
}, },
{ {
path: '/env', path: '/env',

@ -13,7 +13,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import { CoreDict, CoreEnv, CoreService } from '@/api/index.js'; import { CoreDict, CoreEnv, CoreMenu, CoreService } from '@/api/index.js';
export const useBaseDataStore = defineStore('baseData', () => { export const useBaseDataStore = defineStore('baseData', () => {
const state = reactive({ const state = reactive({
@ -38,6 +38,15 @@ export const useBaseDataStore = defineStore('baseData', () => {
envList: [], envList: [],
// ! 环境变量树 // ! 环境变量树
envTree: [], envTree: [],
// ! 菜单列表
menuList: [],
// ! 菜单树
menuTree: [],
// ! 菜单类型列表
menuTypeList: [],
// ! 菜单类型树
menuTypeTree: [],
// ! 防抖 // ! 防抖
// 防抖获取服务列表 // 防抖获取服务列表
canGetServiceList: true, canGetServiceList: true,
@ -45,9 +54,13 @@ export const useBaseDataStore = defineStore('baseData', () => {
canGetDictList: true, canGetDictList: true,
// 防抖获取env列表 // 防抖获取env列表
canGetEnvList: true, canGetEnvList: true,
// 防抖获取menu列表
canGetMenuList: true,
// 防抖获取菜单Menu类型列表
canGetMenuTypeList: true,
}); });
// 获取服务列表 // ! 获取服务列表
async function getServiceList(serviceInfo = undefined) { async function getServiceList(serviceInfo = undefined) {
if (!state.canGetServiceList) return; if (!state.canGetServiceList) return;
state.canGetServiceList = false; state.canGetServiceList = false;
@ -61,7 +74,7 @@ export const useBaseDataStore = defineStore('baseData', () => {
state.serviceList = resd; state.serviceList = resd;
} }
// 获取字典列表 // ! 获取字典列表
const getDictList = async (dictInfo = undefined) => { const getDictList = async (dictInfo = undefined) => {
if (!state.canGetDictList) return; if (!state.canGetDictList) return;
state.canGetDictList = false; state.canGetDictList = false;
@ -76,6 +89,7 @@ export const useBaseDataStore = defineStore('baseData', () => {
const [list] = getDictTree(resd); const [list] = getDictTree(resd);
state.dictTree = list; state.dictTree = list;
}; };
// ! 获取Env列表 // ! 获取Env列表
const getEnvList = async (envInfo = undefined) => { const getEnvList = async (envInfo = undefined) => {
if (!state.canGetEnvList) return; if (!state.canGetEnvList) return;
@ -86,17 +100,59 @@ export const useBaseDataStore = defineStore('baseData', () => {
const resd = await CoreEnv.getEnv({ const resd = await CoreEnv.getEnv({
...envInfo, ...envInfo,
isList: true, isList: true,
}) });
state.envList = resd; state.envList = resd;
const [list] = getEnvTree(resd); const [list] = getEnvTree(resd);
state.envTree = list; state.envTree = list;
}; };
// ! 获取菜单列表
const getMenuList = async (menuInfo = undefined) => {
if (!state.canGetMenuList) return;
state.canGetMenuList = false;
setTimeout(() => {
state.canGetMenuList = true;
}, 2000);
const resd = await CoreMenu.getMenu({
...menuInfo,
isList: true,
});
state.menuList = resd;
const [list, otherList] = getMenuTree(resd);
list.push({
meta: {},
label: '其他菜单',
value: '-1',
id: '-1',
disabled: true,
children: otherList,
});
state.menuTree = list;
};
// ! 获取菜单类型
const getMenuTypeList = async () => {
if (!state.canGetMenuTypeList) return;
state.canGetMenuTypeList = false;
setTimeout(() => {
state.canGetMenuTypeList = true;
}, 2000);
// ! todo MenuType时固定字段,初始字典
const resd = await CoreDict.getDictTree({
dictKey: 'MenuType',
isTree: true,
});
state.menuTypeList = resd;
const [list] = getDictTree(resd);
state.menuTypeTree = Array.isArray(list) && list.length > 0 ? list[0].children : [];
};
return { return {
state, state,
getServiceList, getServiceList,
getDictList, getDictList,
getEnvList getEnvList,
getMenuList,
getMenuTypeList,
}; };
}); });
@ -149,3 +205,28 @@ function getEnvTree(list, pid = 0) {
} }
return [newList, noList]; return [newList, noList];
} }
// ! 格式化Menu树
function getMenuTree(list, pid = 0) {
const newList = [];
let noList = [];
for (let i in list) {
const obj = list[i];
if (obj.pid == pid) {
newList.push({
meta: { ...obj },
label: obj.menuName,
value: obj.menuId,
id: obj.menuId,
});
} else {
noList.push(obj);
}
}
for (let obj of newList) {
const [children, ls] = getMenuTree(noList, obj.id);
obj.children = children;
noList = ls;
}
return [newList, noList];
}

@ -187,7 +187,7 @@ function clearFormData() {
formData.formData.dictDesc = ''; formData.formData.dictDesc = '';
formData.formData.dictName = ''; formData.formData.dictName = '';
formData.formData.dictIcon = ''; formData.formData.dictIcon = '';
formData.formData.dictType = 1; formData.formData.dictType = '1';
formData.formData.serviceKey = undefined; formData.formData.serviceKey = undefined;
formData.formData.root = false; formData.formData.root = false;
formData.formData.orderNum = 0; formData.formData.orderNum = 0;

@ -18,14 +18,23 @@ import { h } from 'vue';
export class EnvFormType { export class EnvFormType {
constructor() { constructor() {
return new Object({ return new Object({
// 上级变量
pid: 0, pid: 0,
// 变量名
envName: '', envName: '',
// 变量标识
envKey: '', envKey: '',
// 变量值
envVal: '', envVal: '',
// 变量值来源于字典
valIsDict: 0, valIsDict: 0,
// 变量描述
envDesc: '', envDesc: '',
// 是否是原始数据
root: 0, root: 0,
// 排序
orderNum: 0, orderNum: 0,
// 所属服务
serviceKey: undefined, serviceKey: undefined,
}); });
} }
@ -35,14 +44,23 @@ export class EnvFormType {
export class EnvSearchType { export class EnvSearchType {
constructor() { constructor() {
return new Object({ return new Object({
// 每页数量
pageSize: 10, pageSize: 10,
// 页码
pageNumber: 1, pageNumber: 1,
// 是否是列表
isList: false, isList: false,
// 排序方式
isAsc: false, isAsc: false,
// 变量信息
envInfo: undefined, envInfo: undefined,
// 是否是原始数据
root: undefined, root: undefined,
// 所属服务
serviceKey: undefined, serviceKey: undefined,
// 状态
status: undefined, status: undefined,
// 根据pid查
hierarchy: [], hierarchy: [],
}); });
} }

@ -44,29 +44,16 @@ const handleAck = () => {
if (Array.isArray(value.envVal) && value.envVal.length > 0) { if (Array.isArray(value.envVal) && value.envVal.length > 0) {
data.envVal = value.envVal.slice(-1)[0]; data.envVal = value.envVal.slice(-1)[0];
} }
props.data.handleAck(data).then(() => { props.data.handleAck(data).catch(e => e);
clearInput();
}).catch(e => e);
}) })
.catch((err) => { .catch((err) => {
formRef.value.scrollToField(err?.errorFields[0]?.name); formRef.value.scrollToField(err?.errorFields[0]?.name);
}); });
}; };
// !
const handleCancel = () => {
props.data.handleCancel();
clearInput();
};
// ! // !
function clearValidate() { function clearValidate() {
formRef.value?.clearValidate(); formRef.value?.clearValidate();
} }
// !
function clearInput() {
formRef.value.resetFields();
clearValidate();
}
// ! pid // ! pid
watch(props.data.formData, (newVal, oldVal) => { watch(props.data.formData, (newVal, oldVal) => {
if (newVal.pid != '') { if (newVal.pid != '') {
@ -106,6 +93,7 @@ onMounted(() => {
<AFlex gap="small"> <AFlex gap="small">
<ACascader <ACascader
flex="1" flex="1"
:disabled="props.data.isUpdate"
allowClear allowClear
showSearch showSearch
v-model:value="props.data.formData.pid" v-model:value="props.data.formData.pid"
@ -123,7 +111,7 @@ onMounted(() => {
<ASelect <ASelect
placeholder="请选择所属服务" placeholder="请选择所属服务"
ref="serviceKey" ref="serviceKey"
:disabled="serviceKeyDisabled" :disabled="serviceKeyDisabled || props.data.isUpdate"
v-model:value="props.data.formData.serviceKey" v-model:value="props.data.formData.serviceKey"
:options="baseDataStore.state.serviceList" :options="baseDataStore.state.serviceList"
:field-names="{ label: 'serviceName', value: 'serviceKey', options: 'children' }" :field-names="{ label: 'serviceName', value: 'serviceKey', options: 'children' }"
@ -182,7 +170,7 @@ onMounted(() => {
</AFormItem> </AFormItem>
</AForm> </AForm>
<template #footer> <template #footer>
<AButton type="dashed" @click="handleCancel">取消</AButton> <AButton type="dashed" @click="props.data.handleCancel">取消</AButton>
<AckCreateAntdButton @click="handleAck" /> <AckCreateAntdButton @click="handleAck" />
</template> </template>
</AntdModalTemplate> </AntdModalTemplate>

@ -7,7 +7,6 @@ import EnvTree from './EnvTree.vue';
import { CoreEnv } from '@/api/index.js'; import { CoreEnv } from '@/api/index.js';
import { useMessage } from 'naive-ui'; import { useMessage } from 'naive-ui';
import { useBaseDataStore } from '@/stores/baseData.js'; import { useBaseDataStore } from '@/stores/baseData.js';
import DictTable from '@/views/Auth/Dict/DictTable.vue';
const baseDataStore = useBaseDataStore(); const baseDataStore = useBaseDataStore();
const Message = useMessage(); const Message = useMessage();
defineOptions({ defineOptions({
@ -15,14 +14,7 @@ defineOptions({
}); });
onMounted(() => { onMounted(() => {
window.pino.info('@1 EnvPage Mounted!'); window.pino.deb('@1 EnvPage Mounted!');
window.pino.deb('debug@1 EnvPage Mounted!');
window.pino.error('error@1 EnvPage Mounted!');
window.pino.fatal('fatal@1 EnvPage Mounted!');
window.pino.info('info@1 EnvPage Mounted!');
window.pino.warn('warn@1 EnvPage Mounted!');
window.pino.trace('trace@1 EnvPage Mounted!');
console.log(window.pino);
getPage(); getPage();
if(baseDataStore.state.serviceList.length == 0){ if(baseDataStore.state.serviceList.length == 0){
baseDataStore.getServiceList(); baseDataStore.getServiceList();
@ -47,7 +39,7 @@ const formData = reactive({
// //
handleAck: handleCreateAck, handleAck: handleCreateAck,
// //
handleCancel: clearFormData, handleCancel: () => clearFormData(),
}); });
// //
const tableData = reactive({ const tableData = reactive({
@ -59,6 +51,7 @@ const tableData = reactive({
handleRemoveAck, handleRemoveAck,
handleUpdate, handleUpdate,
}, },
handlePageChange,
columnList: new EnvTableColumnChooseType(), columnList: new EnvTableColumnChooseType(),
}); });
// @1 // @1
@ -93,6 +86,9 @@ async function handleUpdate(data) {
} }
// @4 todo // @4 todo
async function handleUpdateAck(data) { async function handleUpdateAck(data) {
if(!data.pid){
data.pid = 0;
}
await CoreEnv.updateEnv(formData.envId, data); await CoreEnv.updateEnv(formData.envId, data);
Message.success('更新变量成功!'); Message.success('更新变量成功!');
clearFormData(); clearFormData();
@ -101,30 +97,30 @@ async function handleUpdateAck(data) {
} }
// @5 todo // @5 todo
watch(tableData.pageInfo, () => { watch(tableData.pageInfo, () => {
getPage();
});
// @6
async function getPage() {
if (searchCount === 0) { if (searchCount === 0) {
searchCount++; searchCount++;
getPage();
setTimeout(() => { setTimeout(() => {
searchCount = 0; searchCount = 0;
}, 750); }, 500);
} else { } else {
searchCount++; searchCount++;
return;
} }
});
// @6
async function getPage() {
const pageInfo = {...tableData.pageInfo}; const pageInfo = {...tableData.pageInfo};
pageInfo.hierarchy = pageInfo.hierarchy.length > 0 ? pageInfo.hierarchy[0] : undefined pageInfo.hierarchy = pageInfo.hierarchy.length > 0 ? pageInfo.hierarchy[0] : undefined;
const resd = await CoreEnv.getEnv(pageInfo); const resd = await CoreEnv.getEnv(pageInfo);
tableData.dataSource = resd.rowData; tableData.dataSource = resd.rowData;
tableData.total = Number(resd.total); tableData.total = Number(resd.total);
} }
// @7 // @7
const handlePageChange = (page, pageSize) => { function handlePageChange(page, pageSize) {
tableData.pageInfo.pageSize = pageSize; tableData.pageInfo.pageSize = pageSize;
tableData.pageInfo.pageNumber = page; tableData.pageInfo.pageNumber = page;
getPage(); }
};
// @7 // @7
function handleTableChange(page, filter, sorter) { function handleTableChange(page, filter, sorter) {
if (sorter.order == 'ascend') { if (sorter.order == 'ascend') {
@ -194,16 +190,7 @@ function clearFormData() {
</div> </div>
</template> </template>
<template #footer> <template #footer>
<APagination <TablePagination v-model:data="tableData"/>
v-model:current="tableData.pageInfo.pageNumber"
:total="tableData.total"
:pageSize="tableData.pageInfo.pageSize"
:pageSizeOptions="[10, 20, 50, 100, 200]"
:showSizeChanger="true"
:showQuickJumper="true"
:showTotal="(total) => `共 ${total} 条`"
@change="handlePageChange"
/>
</template> </template>
<template #modal> <template #modal>
<AModal v-model:open="formData.status" :title="formData.modelName" :centered="true" :maskClosable="false" :destroyOnClose="true" :onCancel="clearFormData"> <AModal v-model:open="formData.status" :title="formData.modelName" :centered="true" :maskClosable="false" :destroyOnClose="true" :onCancel="clearFormData">

@ -0,0 +1,212 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: dataModal.js -
// | @创建时间: 2024-07-09 14:47
// | @更新时间: 2024-07-09 14:47
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
import { IconPool } from '@/components/AntDesignVue/Icon/index.js';
import { h } from 'vue';
export class MenuFormType {
constructor() {
return new Object({
// 上级菜单
pid: undefined,
// 接口路径
apiPath: '',
// 页面路由
webPath: '',
// 页面组件路径
webComponentPath: '',
// 菜单名称
menuName: '',
// 菜单描述
menuDesc: '',
// 菜单类型
menuType: undefined,
// 菜单图标
menuIcon: undefined,
// 是否链接到外部
isFrame: 0,
// 是否可见
isVisible: 1,
// 是否激活
isActivate: 1,
// 排序
orderNum: 0,
// 所属服务
serviceOf: undefined,
});
}
}
// 查询数据
export class MenuSearchType {
constructor() {
return new Object({
// 每页数量
pageSize: 10,
// 页码
pageNumber: 1,
// 是否是列表
isList: false,
// 排序方式
isAsc: false,
// 菜单信息
menuInfo: undefined,
// 菜单类型
menuType: undefined,
// 是否链接到外部
isFrame: undefined,
// 是否可见
isVisible: undefined,
// 是否激活
isActivate: undefined,
// 是否是原始数据
root: undefined,
// 所属服务
serviceKey: undefined,
// 状态
status: undefined,
// 根据pid查
hierarchy: [],
});
}
}
// 表单规则
export class MenuFormRulesType {
constructor() {
return new Object({});
}
}
// Env表格列数据
export class MenuTableColumnType {
constructor() {
return new Object({
index: {
title: '序号',
key: 'index',
width: 70, // 你可以根据需要设置列宽
customRender: ({ text, record, index, column }) => `${index + 1}`, // 使用索引 + 1 来显示序号
},
menuName: {
title: '菜单名称',
dataIndex: 'menuName',
key: 'menuName',
},
menuIcon: {
title: '图标',
dataIndex: 'menuIcon',
customRender: ({ text, record, index, column }) => (text && IconPool[text] ? h(IconPool[text], { style: { fontSize: '20px' } }) : h('div', text)),
key: 'menuIcon',
},
menuType: {
title: '类型',
dataIndex: 'menuType',
key: 'menuType',
customRender: ({ text, record, index, column }) => record.menuTypeName,
},
apiPath: {
title: '接口路径',
dataIndex: 'apiPath',
key: 'apiPath',
},
// 页面路由
webPath: {
title: '页面路由',
dataIndex: 'webPath',
key: 'webPath',
},
// 页面组件路径
webComponentPath: {
title: '组件路径',
dataIndex: 'webComponentPath',
key: 'webComponentPath',
},
isFrame: {
title: '外链',
dataIndex: 'isFrame',
key: 'isFrame',
customRender: ({ text, record, index, column }) => (text == '1' ? '是' : '否'),
},
isVisible: {
title: '可见',
dataIndex: 'isVisible',
key: 'isVisible',
customRender: ({ text, record, index, column }) => (text == '1' ? '是' : '否'),
},
isActivate: {
title: '激活',
dataIndex: 'isActivate',
key: 'isActivate',
customRender: ({ text, record, index, column }) => (text == '1' ? '是' : '否'),
},
serviceKey: {
title: '所属服务',
dataIndex: 'serviceKey',
key: 'serviceKey',
customRender: ({ text, record, index, column }) => record.serviceName,
},
haveChildren: {
title: '子项',
dataIndex: 'haveChildren',
key: 'haveChildren',
width: '50px',
},
createtime: {
title: '创建时间',
dataIndex: 'createtime',
key: 'createtime',
width: '140px',
sorter: true,
},
createName: {
title: '创建人',
dataIndex: 'createName',
key: 'createName',
},
updatetime: {
title: '更新时间',
dataIndex: 'updatetime',
key: 'updatetime',
maxWidth: '140px',
minWidth: '140px',
},
updateName: {
title: '更新人',
dataIndex: 'updateName',
key: 'updateName',
},
action: {
title: '操作',
dataIndex: 'action',
key: 'action',
width: 100,
align: 'center',
},
});
}
}
// Env可选表格列
export class MenuTableColumnChooseType {
constructor() {
const excludeColumn = ['updateName', 'updatetime', 'createName'];
const tableColumn = new MenuTableColumnType();
return Object.keys(tableColumn).map((i) => ({
title: tableColumn[i].title,
key: tableColumn[i].key,
status: excludeColumn.includes(i) ? false : true,
}));
}
}

@ -1,9 +1,194 @@
<script setup name="MenuForm"> <script setup name="MenuForm">
import { MenuFormRulesType, MenuFormType } from './DataModal.js';
import { h, onMounted, ref, watch } from 'vue';
import {useBaseDataStore} from '@/stores/index.js';
import { ReloadOutlined, SettingOutlined } from '@ant-design/icons-vue';
const baseDataStore = useBaseDataStore();
defineOptions({
name: 'MenuForm',
});
//
const props = defineProps({
data: {
type: Object,
required: true,
default: () => ({
//
status: false,
//
formData: new MenuFormType(),
//
handleAck: (data) => data,
//
handleCancel: (data) => data,
})
}
});
// ! ref
const formRef = ref(null);
// !
const serviceKeyDisabled = ref(false);
// !
const rules = new MenuFormRulesType();
// !
const handleAck = () => {
formRef.value
.validate()
.then((value) => {
const data = { ...value };
if (Array.isArray(value.pid) && value.pid.length > 0) {
data.pid = value.pid.slice(-1)[0];
}
if (Array.isArray(value.menuType) && value.menuType.length > 0) {
data.menuType = value.menuType.slice(-1)[0];
}
props.data.handleAck(data).catch(e => e);
})
.catch((err) => {
formRef.value.scrollToField(err?.errorFields[0]?.name);
});
};
// ! pid
watch(props.data.formData, (newVal, oldVal) => {
if (newVal.pid != '') {
const menuList = baseDataStore.state.menuList.filter((item) => {
return item.menuId == newVal.pid;
});
if (menuList.length > 0) {
newVal.serviceKey = menuList[0].serviceKey;
serviceKeyDisabled.value = true;
} else {
serviceKeyDisabled.value = false;
}
}
});
onMounted(() => {
window.pino.info('@3 EnvForm Mounted!');
// ! Menu
if (baseDataStore.state.menuList.length == 0) {
baseDataStore.getMenuList();
}
// !
if (baseDataStore.state.serviceList?.length == 0) {
baseDataStore.getServiceList();
}
// !
if (baseDataStore.state.menuTypeList?.length == 0) {
baseDataStore.getMenuTypeList();
}
});
</script> </script>
<template> <template>
<AntdModalTemplate>
<AForm ref="formRef" :model="props.data.formData" :rules="rules" name="DictForm" :label-col="{ span: 8 }" layout="vertical">
<AFormItem label="上级菜单" name="pid">
<AFlex gap="small">
<ACascader
flex="1"
:disabled="props.data.isUpdate"
allowClear
showSearch
v-model:value="props.data.formData.pid"
:options="baseDataStore.state.menuTree"
:multiple="false"
placeholder="请选择上级菜单"
change-on-select
/>
<AButton flex="0" :icon="h(ReloadOutlined)" @click="baseDataStore.getMenuList" shape="circle" />
</AFlex>
</AFormItem>
<AFormItem label="所属服务" name="serviceKey">
<AFlex gap="small">
<ASelect
placeholder="请选择所属服务"
ref="serviceKey"
:disabled="serviceKeyDisabled || props.data.isUpdate"
v-model:value="props.data.formData.serviceKey"
:options="baseDataStore.state.serviceList"
:field-names="{ label: 'serviceName', value: 'serviceKey', options: 'children' }"
/>
<AButton flex="0" :icon="h(ReloadOutlined)" @click="baseDataStore.getServiceList()" shape="circle" />
</AFlex>
</AFormItem>
<AFormItem label="菜单名称" name="menuName">
<AInput style="width: 100%" v-model:value="props.data.formData.menuName" placeholder="请输入菜单名称" :maxlength="32" showCount />
</AFormItem>
<AFormItem label="菜单类型" name="menuType">
<AFlex gap="small">
<ASelect
placeholder="请选择菜单类型"
v-model:value="props.data.formData.menuType"
:options="baseDataStore.state.menuTypeTree"
/>
<AButton flex="0" :icon="h(ReloadOutlined)" @click="baseDataStore.getDictList()" shape="circle" />
</AFlex>
</AFormItem>
<AFormItem label="接口路径" name="apiPath">
<AInput style="width: 100%" v-model:value="props.data.formData.apiPath" placeholder="请输入接口路径" :maxlength="32" showCount />
</AFormItem>
<AFormItem label="页面路由" name="webPath">
<AInput style="width: 100%" v-model:value="props.data.formData.webPath" placeholder="请输入页面路由" :maxlength="32" showCount />
</AFormItem>
<AFormItem label="页面组件路径" name="webComponentPath">
<AInput style="width: 100%" v-model:value="props.data.formData.webComponentPath" placeholder="请输入组件路径" :maxlength="32" showCount />
</AFormItem>
<AFormItem label="菜单图标" name="menuIcon">
<CustomIconSelect v-model:value="props.data.formData.menuIcon" />
</AFormItem>
<AFormItem label="是否链接到外部" name="isFrame">
<ARadioGroup v-model:value="props.data.formData.isFrame" name="radioGroup">
<ARadio :value="1"></ARadio>
<ARadio :value="0"></ARadio>
</ARadioGroup>
</AFormItem>
<AFormItem label="是否可见" name="isVisible">
<ARadioGroup v-model:value="props.data.formData.isVisible" name="radioGroup">
<ARadio :value="1"></ARadio>
<ARadio :value="0"></ARadio>
</ARadioGroup>
</AFormItem>
<AFormItem label="是否激活" name="isActivate">
<ARadioGroup v-model:value="props.data.formData.isActivate" name="radioGroup">
<ARadio :value="1"></ARadio>
<ARadio :value="0"></ARadio>
</ARadioGroup>
</AFormItem>
<AFormItem label="菜单描述" name="menuDesc">
<ATextarea style="width: 100%" v-model:value="props.data.formData.menuDesc" :rows="3" placeholder="请输入菜单描述" :maxlength="255" showCount />
</AFormItem>
<AFormItem label="排序" name="orderNum">
<AInputNumber v-model:value="props.data.formData.orderNum">
<template #addonAfter><SettingOutlined /></template>
</AInputNumber>
</AFormItem>
</AForm>
<template #footer>
<AButton type="dashed" @click="props.data.handleCancel">取消</AButton>
<AckCreateAntdButton @click="handleAck" />
</template>
</AntdModalTemplate>
</template> </template>
<style scoped> <style scoped>

@ -0,0 +1,244 @@
<script setup name="MenuPage">
import { onMounted, reactive, watch } from 'vue';
import { MenuFormType, MenuSearchType, MenuTableColumnChooseType } from './DataModal.js';
import MenuForm from './MenuForm.vue';
import MenuTable from './MenuTable.vue';
import { useMessage } from 'naive-ui';
import { useBaseDataStore } from '@/stores/baseData.js';
import TablePagination from '@/components/AntDesignVue/CustomAntDesignVue/TablePagination/index.vue';
import { CoreMenu } from '@/api/index.js';
import MenuTree from '@/views/Auth/Menu/MenuTree.vue';
const baseDataStore = useBaseDataStore();
const Message = useMessage();
defineOptions({
name: 'MenuPage',
});
//
let searchCount = 0;
// !
const formData = reactive({
//
modelName: '创建菜单',
//
status: false,
//
isUpdate: false,
// ID
menuId: undefined,
formData: new MenuFormType(),
//
handleAck: handleCreateAck,
//
handleCancel: () => clearFormData()
});
//
const tableData = reactive({
dataSource: [],
pageInfo: new MenuSearchType(),
searchBase:{
menuType: []
},
total: 0,
methods: {
handleTableChange,
handleRemoveAck,
handleUpdate,
},
handlePageChange,
columnList: new MenuTableColumnChooseType(),
});
// @1
async function handleCreateAck(data) {
await CoreMenu.createMenu(data);
Message.success('添加菜单成功!');
clearSearchData();
clearFormData();
getPage();
baseDataStore.getMenuList();
}
// @2
async function handleRemoveAck(data) {
await CoreMenu.removeMenu(data.menuId);
Message.success('删除菜单成功!');
if (tableData.dataSource.length == 1 && tableData.pageInfo.pageNumber > 1) {
tableData.pageInfo.pageNumber--;
}
getPage();
baseDataStore.getMenuList();
}
// @3 todo
async function handleUpdate(data) {
formData.modelName = '更新变量';
formData.status = true;
formData.isUpdate = true;
formData.menuId = data.menuId;
formData.handleAck = handleUpdateAck;
Object.keys(formData.formData).forEach(key => {
formData.formData[key] = data[key];
});
}
// @4 todo
async function handleUpdateAck(data) {
if(!data.pid){
data.pid = 0;
}
await CoreMenu.updateMenu(formData.menuId, data);
Message.success('更新变量成功!');
clearFormData();
getPage();
baseDataStore.getMenuList();
}
// @5 todo
watch(tableData.pageInfo, () => {
getPage();
});
// @6
async function getPage() {
if (searchCount === 0) {
searchCount++;
setTimeout(() => {
searchCount = 0;
}, 500);
} else {
searchCount++;
return;
}
const pageInfo = {...tableData.pageInfo};
pageInfo.hierarchy = pageInfo.hierarchy.length > 0 ? pageInfo.hierarchy[0] : undefined;
const resd = await CoreMenu.getMenu(pageInfo);
tableData.dataSource = resd.rowData;
tableData.total = Number(resd.total);
}
// @7
function handlePageChange(page, pageSize) {
tableData.pageInfo.pageSize = pageSize;
tableData.pageInfo.pageNumber = page;
}
// @7
function handleTableChange(page, filter, sorter) {
if (sorter.order == 'ascend') {
tableData.pageInfo.isAsc = true;
} else if (sorter.order == 'descend') {
tableData.pageInfo.isAsc = false;
} else {
tableData.pageInfo.isAsc = undefined;
}
}
// @8
function clearSearchData() {
const newPageInfo = new MenuSearchType();
Object.keys(tableData.pageInfo).forEach((key) => {
tableData.pageInfo[key] = newPageInfo[key];
});
}
// @9
function clearFormData() {
formData.modelName = '创建变量';
formData.isUpdate = false;
formData.status = false;
formData.menuId = undefined;
formData.handleAck = handleCreateAck;
const newFormData = new MenuFormType();
Object.keys(formData.formData).forEach((key) => {
formData.formData[key] = newFormData[key];
});
}
onMounted(() => {
window.pino.deb('@1 MenuPage Mounted!');
getPage();
// !
if (baseDataStore.state.menuTypeList?.length == 0) {
baseDataStore.getMenuTypeList();
}
// !
if (baseDataStore.state.serviceList?.length == 0) {
baseDataStore.getServiceList();
}
});
</script>
<template>
<WorkContainer>
<template #header>
<ASpace>
<CreateAntdButton name="菜单" @click="formData.status = true" />
<TableColumChoose v-model:columnList="tableData.columnList" />
<AInputSearch v-model:value="tableData.pageInfo.menuInfo" placeholder="菜单名称/路径" style="width: 200px" @search="getPage" />
<ASelect
style="width: 150px"
placeholder="请选择所属服务"
allowClear
v-model:value="tableData.pageInfo.serviceKey"
:options="baseDataStore.state.serviceList"
:field-names="{ label: 'serviceName', value: 'serviceKey', options: 'children' }"
/>
<ASelect
style="width: 150px"
placeholder="请选择菜单类型"
allowClear
v-model:value="tableData.pageInfo.menuType"
:options="baseDataStore.state.menuTypeTree"
/>
<ASelect style="width: 150px" placeholder="请选择可见状态" allowClear v-model:value="tableData.pageInfo.isVisible">
<ASelectOption :value="true">可见</ASelectOption>
<ASelectOption :value="false">不可见</ASelectOption>
</ASelect>
<ASelect style="width: 150px" placeholder="请选择激活状态" allowClear v-model:value="tableData.pageInfo.isActivate">
<ASelectOption :value="true">激活</ASelectOption>
<ASelectOption :value="false">未激活</ASelectOption>
</ASelect>
<ASelect style="width: 170px" placeholder="请选择是否是外部链接" allowClear v-model:value="tableData.pageInfo.isFrame">
<ASelectOption :value="true">外部链接</ASelectOption>
<ASelectOption :value="false">非外部链接</ASelectOption>
</ASelect>
</ASpace>
</template>
<template #main>
<div class="menuBody">
<div class="tree">
<MenuTree v-model:selectedKeys="tableData.pageInfo.hierarchy"/>
</div>
<div class="table">
<MenuTable :tableData="tableData"/>
</div>
</div>
</template>
<template #footer>
<TablePagination :data="tableData"/>
</template>
<template #modal>
<AModal v-model:open="formData.status" :title="formData.modelName" :centered="true" :maskClosable="false" :destroyOnClose="true" :onCancel="clearFormData">
<MenuForm :data="formData"/>
<template #footer></template>
</AModal>
</template>
</WorkContainer>
</template>
<style scoped>
.menuBody {
position: relative;
height: 100%;
width: 100%;
display: flex;
& > div.tree {
position: relative;
flex-shrink: 0;
max-height: 100%;
width: 300px;
border-right: 1px solid #cdcdcd;
padding-right: 5px;
}
& > div.table {
position: relative;
flex: 1;
max-height: 100%;
padding-left: 5px;
}
}
</style>

@ -1,19 +1,64 @@
<script setup name="MenuTable"> <script setup name="MenuTable">
import { computed, onMounted, ref } from 'vue';
import { MenuTableColumnType } from './DataModal.js';
// !
defineOptions({ defineOptions({
name: 'MenuTable', name: 'MenuTable',
}); });
const props = defineProps({
tableData: {
type: Object,
required: true,
},
});
const isLoading = ref(true);
// !
const tableColumnObject = new MenuTableColumnType();
const tableColumns = computed(() => {
// !
setTableAuto();
if (props.tableData.columnList && props.tableData.columnList.length > 0) {
// !
const existColumn = props.tableData.columnList.filter((i) => i.status === true);
if (existColumn.length == 0) {
// !
return Object.keys(tableColumnObject).map((column) => tableColumnObject[column]);
} else {
// !
return existColumn.map((column) => tableColumnObject[column.key]);
}
} else {
//
return Object.keys(tableColumnObject).map((column) => tableColumnObject[column]);
}
});
function setTableAuto() {
isLoading.value = false;
setTimeout(() => {
isLoading.value = true;
}, 100);
}
function handleResizeColumn(w, col) {
col.width = w;
}
onMounted(() => {
window.pino.deb('@2 MenuTable Mounted!');
});
</script> </script>
<template> <template>
<PacmanLoading v-if="!isLoading" loading="1" />
<ATable <ATable
v-if="isLoading"
style="height: 100%" style="height: 100%"
:dataSource="props.dataSource" :dataSource="props.tableData.dataSource"
:columns="tableColumns" :columns="tableColumns"
@change="props.methods.handleTableChange" @change="props.tableData.methods.handleTableChange"
:pagination="false" :pagination="false"
sticky sticky
tableLayout="auto"
@resizeColumn="handleResizeColumn" @resizeColumn="handleResizeColumn"
:row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)" :row-class-name="(_record, index) => (index % 2 === 1 ? 'table-striped' : null)"
:scroll="{ scrollToFirstRowOnChange: true }" :scroll="{ scrollToFirstRowOnChange: true }"
@ -21,14 +66,12 @@ defineOptions({
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column?.key === 'action'"> <template v-if="column?.key === 'action'">
<ASpace> <ASpace>
<TableRemoveButton :ackDelete="() => props.methods.handleRemoveAck(record)"/> <TableRemoveButton :ackDelete="() => props.tableData.methods.handleRemoveAck(record)" />
<TableUpdateButton @click="props.methods.handleUpdate(record)"/> <TableUpdateButton @click="props.tableData.methods.handleUpdate(record)" />
</ASpace> </ASpace>
</template> </template>
</template> </template>
</ATable> </ATable>
</template> </template>
<style scoped> <style scoped></style>
</style>

@ -0,0 +1,34 @@
<script setup name="MenuTree">
defineOptions({
name: 'MenuTree',
});
import { useBaseDataStore } from '@/stores/baseData.js';
import { onMounted, reactive } from 'vue';
const baseDataStore = useBaseDataStore();
// !
const fieldNames = reactive({
children:'children', title:'label', key:'id'
});
//
onMounted(() => {
window.pino.info('@4 MenuTree Mounted');
// !
if (baseDataStore.state.menuList?.length == 0) {
baseDataStore.getMenuList();
}
});
</script>
<template>
<ATree
:treeData="baseDataStore.state.menuTree"
:show-line="true"
show-icon
:fieldNames="fieldNames"
/>
</template>
<style scoped></style>

@ -1,16 +0,0 @@
// | ------------------------------------------------------------
// | @版本: version 0.1
// | @创建人: 【Nie-x7129】
// | @E-mail: x71291@outlook.com
// | @所在项目: hoto-auth-vue3
// | @文件描述: dataModal.js -
// | @创建时间: 2024-07-09 14:47
// | @更新时间: 2024-07-09 14:47
// | @修改记录:
// | -*-*-*- (时间--修改人--修改说明) -*-*-*-
// | =
// | ------------------------------------------------------------
export const formType = new Object({
})

@ -1,64 +0,0 @@
<script setup name="MenuPage">
import { onMounted, reactive } from 'vue';
defineOptions({
name: 'MenuPage',
});
onMounted(() => {
window.pino.info('MenuPage Mounted!');
});
// !
const formData = reactive({
//
modelName: '创建菜单',
//
status: false,
//
isUpdate: false,
// ID
dictId: undefined,
});
// !
function clearFormData() {}
</script>
<template>
<WorkContainer>
<template #header>
<ASpace>
<CreateAntdButton name="菜单" @click="formData.status = true" />
</ASpace>
</template>
<template #main></template>
<template #footer></template>
<template #modal>
<AModal v-model:open="formData.status" :title="formData.modelName" :centered="true" :maskClosable="false" :destroyOnClose="true" :onCancel="clearFormData">
<template #footer></template>
</AModal>
</template>
</WorkContainer>
</template>
<style scoped>
.menuBody {
position: relative;
height: 100%;
width: 100%;
display: flex;
& > div.tree {
position: relative;
flex-shrink: 0;
max-height: 100%;
width: 300px;
border-right: 1px solid #cdcdcd;
padding-right: 5px;
}
& > div.table {
position: relative;
flex: 1;
max-height: 100%;
padding-left: 5px;
}
}
</style>
Loading…
Cancel
Save