Commit 7ebe2e6e authored by 陈浩玮's avatar 陈浩玮

Merge branch 'feature/shuiluo' into 'master'

feat: 菜单管理页面大体完成

See merge request product/kim3-web-vue/starter-web-vue!2
parents 87594b7c 9df83c42
import { request, METHOD } from '@/utils'; import { request, METHOD } from '@/utils';
export * from './menu';
export function getUserDetailInfoApi() { export function getUserDetailInfoApi() {
return request('/api/v1/detail', METHOD.GET); return request('/api/v1/detail', METHOD.GET);
......
import { delReq, getReq, postReq } from '@/utils';
export function delMenuApi(id) {
return delReq(`/api/v1/menus/${id}`);
}
export function getMenuDataApi() {
return getReq('/api/v1/menus');
}
export function addMenuApi(data) {
return postReq('/api/v1/menus', data);
}
export function addRoleApi(data) {
return postReq('/api/v1/roles', data);
}
<template>
<a-tree
:checkedKeys="value"
:selectable="false"
checkable
:replaceFields="replaceFields"
:tree-data="treeData"
style="max-height:400px"
class="tw-overflow-y-auto"
v-bind="$attrs"
v-on="$listeners"
/>
</template>
<script>
import { getMenuDataApi } from '@/api';
import { convertListToTree } from '@/utils';
export default {
model: {
prop: 'value',
event: 'check',
},
props: {
value: [Object, Array],
showMenu: Boolean,
},
data() {
return {
treeData: [],
replaceFields: {
title: 'menuName',
key: 'menuId',
},
};
},
async mounted() {
this.rawData = await getMenuDataApi();
this.treeData = convertListToTree(this.rawData, !this.showMenu);
},
methods: {
get() {
if (Array.isArray(this.value)) return this.rawData.filter(m => this.value.includes(m.menuId));
return [];
},
},
};
</script>
<template>
<a-popconfirm title="确认是否删除" ok-text="确认" cancel-text="取消" @confirm="onOk">
<a>删除</a>
</a-popconfirm>
</template>
<script>
import { EMPTY_FUN } from '@/utils';
import { delReq } from '@/utils';
export default {
props: {
url: String,
cb: {
type: Function,
default: EMPTY_FUN,
},
},
methods: {
async onOk() {
await delReq(this.url);
this.cb();
},
},
};
</script>
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
</template> </template>
</a-space> </a-space>
<a-table :data-source="data" :loading="loading" v-bind="$attrs"> <a-table :data-source="data" :loading="loading" v-bind="$attrs" :pagination="!noPage">
<slot /> <slot />
</a-table> </a-table>
</div> </div>
...@@ -35,9 +35,23 @@ ...@@ -35,9 +35,23 @@
:visible="addVisible" :visible="addVisible"
@close="addDrawerClose" @close="addDrawerClose"
v-if="addBtn" v-if="addBtn"
:maskClosable="addBtn.maskClosable" :maskClosable="!!addBtn.maskClosable"
:closable="false"
:drawerStyle="drawerStyle"
:bodyStyle="bodyStyle"
:width="addBtn.width"
destroyOnClose
> >
<div class="tw-overflow-y-hidden">
<div class="tw-overflow-y-auto tw-h-full">
<slot name="add" /> <slot name="add" />
</div>
</div>
<a-divider />
<a-space>
<a-button @click="addVisible = false">取消</a-button>
<a-button type="primary" @click="submit" :loading="submitLoading">确认</a-button>
</a-space>
</a-drawer> </a-drawer>
</div> </div>
</template> </template>
...@@ -45,71 +59,6 @@ ...@@ -45,71 +59,6 @@
<script> <script>
import { request, METHOD } from '@/utils/requestUtil'; import { request, METHOD } from '@/utils/requestUtil';
const data = [
{
key: 1,
name: 'John Brown sr.',
age: 60,
address: 'New York No. 1 Lake Park',
children: [
{
key: 11,
name: 'John Brown',
age: 42,
address: 'New York No. 2 Lake Park',
},
{
key: 12,
name: 'John Brown jr.',
age: 30,
address: 'New York No. 3 Lake Park',
children: [
{
key: 121,
name: 'Jimmy Brown',
age: 16,
address: 'New York No. 3 Lake Park',
},
],
},
{
key: 13,
name: 'Jim Green sr.',
age: 72,
address: 'London No. 1 Lake Park',
children: [
{
key: 131,
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park',
children: [
{
key: 1311,
name: 'Jim Green jr.',
age: 25,
address: 'London No. 3 Lake Park',
},
{
key: 1312,
name: 'Jimmy Green sr.',
age: 18,
address: 'London No. 4 Lake Park',
},
],
},
],
},
],
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
];
export default { export default {
props: { props: {
url: String, url: String,
...@@ -121,6 +70,7 @@ export default { ...@@ -121,6 +70,7 @@ export default {
type: Object, type: Object,
}, },
formatData: Function, formatData: Function,
noPage: Boolean,
}, },
data() { data() {
return { return {
...@@ -128,8 +78,25 @@ export default { ...@@ -128,8 +78,25 @@ export default {
queryForm: {}, queryForm: {},
loading: false, loading: false,
addVisible: false, addVisible: false,
submitLoading: false,
drawerStyle: {
display: 'flex',
flexDirection: 'column',
overflowY: 'hidden',
},
bodyStyle: {
flex: 1,
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
},
}; };
}, },
watch: {
addVisible(val) {
if (!val && this.addBtn.onCancel) this.addBtn.onCancel();
},
},
mounted() { mounted() {
console.log(this.addBtn); console.log(this.addBtn);
this.getData(); this.getData();
...@@ -153,6 +120,20 @@ export default { ...@@ -153,6 +120,20 @@ export default {
addDrawerClose() { addDrawerClose() {
this.addVisible = false; this.addVisible = false;
}, },
async submit() {
this.submitLoading = true;
try {
await this.addBtn?.onOk();
this.getData();
} catch (error) {
// todo
}
this.submitLoading = false;
this.addVisible = false;
},
showAdd() {
this.addVisible = true;
},
}, },
}; };
</script> </script>
......
...@@ -34,7 +34,7 @@ const store = new Vuex.Store({ ...@@ -34,7 +34,7 @@ const store = new Vuex.Store({
Vue.use(Router); Vue.use(Router);
const isAsynRount = store.state.settingModule.asyncRoutes; const isAsynRount = store.state.settingModule.asyncRoutes;
const options = initRouter(isAsynRount); const options = initRouter(isAsynRount);
const router = new Router(options); const router = new Router({ mode: 'history', ...options });
//装载vue-i18n控件 如果语言优先级 请直接修改这里localeLang和fallbackLang //装载vue-i18n控件 如果语言优先级 请直接修改这里localeLang和fallbackLang
Vue.use(VueI18n); Vue.use(VueI18n);
......
<template> <template>
<my-table url="/api/v1/menus" :addBtn="addBtn" :formatData="formatData" rowKey="menuId"> <my-table
<template #search="{query}"> url="/api/v1/menus"
<a-form-model-item label="菜单、目录名称"> :addBtn="addBtn"
<a-input v-model="query.name" /> :formatData="formatData"
</a-form-model-item> rowKey="menuId"
</template> noPage
ref="table"
>
<template #add> <template #add>
<a-input v-model="add.name" /> <Form ref="addForm" />
</template> </template>
<a-table-column title="名称" data-index="menuName" /> <a-table-column title="名称" data-index="menuName" />
<a-table-column title="类型" data-index="menuTypeName" /> <a-table-column title="类型" data-index="menuTypeName" />
<a-table-column title="显示排序" data-index="viewIndex" :sorter="sorter" sortOrder="ascend" /> <a-table-column title="显示排序" data-index="viewIndex" :sorter="sorter" sortOrder="ascend" />
<a-table-column title="显示图标" data-index="menuIcon" /> <a-table-column title="显示图标" data-index="menuIcon" />
<a-table-column title="模块URL" data-index="menuUrl" /> <a-table-column title="模块URL" data-index="menuUrl" />
<a-table-column title="操作"> <a-table-column title="操作">
<template #default> <template #default="row">
<span> <a @click="() => edit(row)">编辑</a>
<a>编辑</a>
<a-divider type="vertical" /> <a-divider type="vertical" />
<a>删除</a> <PopconfirmDelete :onOk="() => delMenu(row.menuId)" />
</span>
</template> </template>
</a-table-column> </a-table-column>
</my-table> </my-table>
...@@ -27,19 +28,44 @@ ...@@ -27,19 +28,44 @@
<script> <script>
import { convertListToTree } from '@/utils'; import { convertListToTree } from '@/utils';
import PopconfirmDelete from '@/components/popconfirm_delete/index.vue';
import { delMenuApi } from '@/api';
import Form from './form.vue';
export default { export default {
data: () => ({ data: vm => ({
add: {}, addBtn: {
addBtn: { text: '新建', title: '菜单配置' }, text: '新建',
title: '菜单配置',
width: 400,
onOk() {
return vm.$refs['addForm']?.submit();
},
},
sortOrder: 'ascend', sortOrder: 'ascend',
}), }),
components: { PopconfirmDelete, Form },
methods: { methods: {
formatData: convertListToTree, formatData: convertListToTree,
refreshTable() {
this.$refs['table'].getData();
},
async delMenu(id) {
await delMenuApi(id);
this.refreshTable();
},
sorter(pre, next) { sorter(pre, next) {
return pre.viewIndex - next.viewIndex; return pre.viewIndex - next.viewIndex;
}, },
edit(data) {
console.log(data);
this.$refs['table']?.showAdd();
this.$nextTick(() => {
this.$refs['addForm'].setEdit(data);
});
},
}, },
}; };
</script> </script>
<template>
<a-form-model layout="vertical" :model="form" :rules="rules">
<a-form-model-item label="父级" v-if="isAdd">
<MenuTree @check="onCheck" :value="checkedKeys" checkStrictly />
</a-form-model-item>
<a-form-model-item label="类型">
<a-radio-group v-model="form.menuType" :disabled="isEdit">
<a-radio value="CATALOG">目录</a-radio>
<a-radio value="MENU">菜单</a-radio>
</a-radio-group>
</a-form-model-item>
<a-form-model-item label="中文名称">
<a-input v-model="form.menuName" />
</a-form-model-item>
<a-form-model-item label="英文名称">
<a-input />
</a-form-model-item>
<a-form-model-item label="模块URL" prop="menuUrl">
<a-input v-model="form.menuUrl" />
</a-form-model-item>
<a-form-model-item label="显示排序" prop="viewIndex">
<a-input-number v-model="form.viewIndex" />
</a-form-model-item>
<a-form-model-item label="显示图标" prop="menuIcon">
<a-input v-model="form.menuIcon" />
</a-form-model-item>
<a-form-model-item label="说明" prop="menuRemark">
<a-textarea v-model="form.menuRemark" />
</a-form-model-item>
</a-form-model>
</template>
<script>
import { addMenuApi } from '@/api';
import MenuTree from '@/components/menu_tree/index.vue';
export default {
components: { MenuTree },
data: () => ({
type: 0,
form: {
menuType: 'CATALOG',
parentMenuId: 0,
menuName: '',
},
rules: {
menuUrl: [{ required: true }],
},
checkedKeys: {
checked: [],
halfChecked: [],
},
}),
computed: {
isAdd() {
return this.type === 0;
},
isEdit() {
return this.type === 1;
},
},
methods: {
submit() {
return addMenuApi({ ...this.form, parentMenuId: this.checkedKeys.checked[0] ?? 0 });
},
onCheck(checkedKeys) {
const checked = checkedKeys.checked.slice(-1);
this.checkedKeys = { checked };
},
setEdit(data) {
this.type = 1;
this.form = data;
},
},
};
</script>
<template> <template>
<h1>Menu Management</h1> <my-table url="/api/v1/roles" rowKey="roleId" :addBtn="addBtn" ref="table">
<template #add>
<Form ref="form" />
</template>
<a-table-column title="名称" data-index="roleName" />
<a-table-column title="说明" data-index="remark" />
<a-table-column title="操作">
<template #default="row">
<a @click="() => view(row, 2)">查看</a>
<a-divider type="vertical" />
<a @click="() => view(row, 1)">编辑</a>
<a-divider type="vertical" />
<PopconfirmDelete :url="`/api/v1/roles/${row.roleId}`" :cb="refreshTable" />
</template>
</a-table-column>
</my-table>
</template> </template>
<script>
import Form from './form.vue';
import PopconfirmDelete from '@/components/popconfirm_delete/index.vue';
export default {
components: { Form, PopconfirmDelete },
data() {
return {
addBtn: { width: 400, onOk: () => this.$refs['form']?.submit() },
};
},
methods: {
refreshTable() {
this.$refs['table']?.getData();
},
view(data, type) {
this.$refs['table']?.showAdd();
this.$nextTick(() => {
this.$refs['form'].setData(data, type);
});
},
},
};
</script>
<template>
<a-form-model layout="vertical" :model="form" :rules="rules">
<a-form-model-item label="角色名称">
<a-input v-model="form.roleName" :disabled="isView" />
</a-form-model-item>
<a-form-model-item label="角色编码">
<a-input v-model="form.roleCode" :disabled="isView" />
</a-form-model-item>
<a-form-model-item label="角色说明" prop="remark">
<a-input v-model="form.remark" :disabled="isView" />
</a-form-model-item>
<a-form-model-item label="菜单权限">
<MenuTree v-model="checkedKeys" showMenu ref="menuTree" :disabled="isView" />
</a-form-model-item>
</a-form-model>
</template>
<script>
import MenuTree from '@/components/menu_tree/index.vue';
import { addRoleApi } from '@/api';
export default {
components: { MenuTree },
data() {
return {
type: 0,
form: {},
rules: {},
checkedKeys: [],
};
},
computed: {
isEdit() {
return this.type === 1;
},
isView() {
return this.type === 2;
},
},
methods: {
submit() {
return addRoleApi({ ...this.form, authorityList: this.$refs['menuTree'].get() });
},
setData(data, type) {
this.form = data;
this.type = type;
},
},
};
</script>
...@@ -11,7 +11,6 @@ import { getI18nKey } from '@/utils/routerUtil'; ...@@ -11,7 +11,6 @@ import { getI18nKey } from '@/utils/routerUtil';
function generateI18n(lang, routes, valueKey) { function generateI18n(lang, routes, valueKey) {
routes.forEach(route => { routes.forEach(route => {
let keys = getI18nKey(route.fullPath).split('.'); let keys = getI18nKey(route.fullPath).split('.');
console.log(keys);
let value = let value =
valueKey === 'path' valueKey === 'path'
? route[valueKey] ? route[valueKey]
......
...@@ -13,9 +13,13 @@ export function setUserId(val) { ...@@ -13,9 +13,13 @@ export function setUserId(val) {
/** /**
* 转变菜单列表为tree结构 * 转变菜单列表为tree结构
* @param {Array} menuList 菜单列表 * @param {Array} menuList 菜单列表
* @param {Boolean} filterMenu 是否过滤掉菜单,只保留目录
*/ */
export function convertListToTree(menuList) { export function convertListToTree(menuList, filterMenu = false) {
const tempMenu = [...menuList]; let tempMenu = [...menuList];
if (filterMenu) {
tempMenu = tempMenu.filter(m => m.menuType !== 'MENU');
}
for (const menu of menuList) { for (const menu of menuList) {
if (menu.parentMenuId === 0) continue; if (menu.parentMenuId === 0) continue;
const parent = menuList.find(m => m.menuId === menu.parentMenuId); const parent = menuList.find(m => m.menuId === menu.parentMenuId);
...@@ -23,3 +27,5 @@ export function convertListToTree(menuList) { ...@@ -23,3 +27,5 @@ export function convertListToTree(menuList) {
} }
return tempMenu.filter(m => m.parentMenuId === 0); return tempMenu.filter(m => m.parentMenuId === 0);
} }
export function EMPTY_FUN() {}
...@@ -133,4 +133,27 @@ function checkAuthorization() { ...@@ -133,4 +133,27 @@ function checkAuthorization() {
return !!getToken(); return !!getToken();
} }
export { METHOD, request, parseUrlParams, loadResponseInterceptor, setToken, checkAuthorization, clearToken }; function delReq(url, config) {
return request(url, METHOD.DELETE, config);
}
function getReq(url, params, config) {
return request(url, METHOD.GET, params, config);
}
function postReq(url, data, config) {
return request(url, METHOD.POST, data, config);
}
export {
METHOD,
request,
parseUrlParams,
loadResponseInterceptor,
setToken,
checkAuthorization,
clearToken,
delReq,
getReq,
postReq,
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment