feat: 优化菜单管理页面
parent
b490b6c9c5
commit
5b9c14184e
2
.env
2
.env
|
|
@ -2,7 +2,7 @@
|
|||
# 应用配置
|
||||
# =====================================================================================
|
||||
# 网站标题
|
||||
VITE_TITLE = 绝弹管理后台
|
||||
VITE_TITLE = 绝弹项目管理
|
||||
# 网站副标题
|
||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||
# 接口前缀 说明:参见 axios 的 baseURL
|
||||
|
|
|
|||
|
|
@ -166,43 +166,6 @@ export interface AuthUserDto {
|
|||
password: string;
|
||||
}
|
||||
|
||||
export interface LoginedUserVo {
|
||||
/** 用户ID */
|
||||
id: number;
|
||||
/**
|
||||
* 访问令牌
|
||||
* @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjIsInVzZXJuYW1lIjoianVldGFuIiwiaWF0IjoxNjkxMTM5MjI3LCJleHAiOjE2OTExOTkyMjd9.6z7f-xfsHABbsyg401o2boKeqNQ1epPDYfEdavIcfYc"
|
||||
*/
|
||||
token: string;
|
||||
/**
|
||||
* 登录账号
|
||||
* @example "juetan"
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* 用户昵称
|
||||
* @example "绝弹"
|
||||
*/
|
||||
nickname: string;
|
||||
/**
|
||||
* 用户介绍
|
||||
* @example "这个人很懒, 什么也没有留下!"
|
||||
*/
|
||||
description: string;
|
||||
/**
|
||||
* 用户头像
|
||||
* @example "/upload/assets/222421415123.png "
|
||||
*/
|
||||
avatar: string;
|
||||
/**
|
||||
* 用户邮箱
|
||||
* @example "example@mail.com"
|
||||
*/
|
||||
email: string;
|
||||
/** 用户角色ID */
|
||||
roleIds: number[];
|
||||
}
|
||||
|
||||
export interface CreateLogDto {
|
||||
/**
|
||||
* 字段描述(Swagger用途)
|
||||
|
|
@ -545,7 +508,6 @@ export interface Menu {
|
|||
}
|
||||
|
||||
export interface UpdateMenuDto {
|
||||
id: number;
|
||||
/**
|
||||
* 父级ID
|
||||
* @example 0
|
||||
|
|
@ -950,7 +912,7 @@ export namespace Role {
|
|||
};
|
||||
}
|
||||
/**
|
||||
* @description 批量查询角色
|
||||
* @description 分页查询角色
|
||||
* @tags role
|
||||
* @name GetRoles
|
||||
* @request GET:/api/v1/roles
|
||||
|
|
@ -1037,7 +999,7 @@ export namespace Role {
|
|||
|
||||
export namespace Auth {
|
||||
/**
|
||||
* @description 账号登陆
|
||||
* @description 登陆
|
||||
* @tags auth
|
||||
* @name Login
|
||||
* @request POST:/api/v1/auth/login
|
||||
|
|
@ -1059,7 +1021,33 @@ export namespace Auth {
|
|||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: LoginedUserVo;
|
||||
data?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 获取登陆用户信息
|
||||
* @tags auth
|
||||
* @name GetUserInfo
|
||||
* @request POST:/api/v1/auth/info
|
||||
*/
|
||||
export namespace GetUserInfo {
|
||||
export type RequestParams = {};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = never;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = {
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: User;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1889,7 +1877,7 @@ export namespace Menu {
|
|||
*/
|
||||
export namespace GetMenu {
|
||||
export type RequestParams = {
|
||||
id: string;
|
||||
id: number;
|
||||
};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = never;
|
||||
|
|
@ -1917,7 +1905,7 @@ export namespace Menu {
|
|||
*/
|
||||
export namespace SetMenu {
|
||||
export type RequestParams = {
|
||||
id: string;
|
||||
id: number;
|
||||
};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = UpdateMenuDto;
|
||||
|
|
@ -2250,7 +2238,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
},
|
||||
|
||||
/**
|
||||
* 批量查询角色
|
||||
* 分页查询角色
|
||||
*
|
||||
* @tags role
|
||||
* @name GetRoles
|
||||
|
|
@ -2349,7 +2337,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
};
|
||||
auth = {
|
||||
/**
|
||||
* 账号登陆
|
||||
* 登陆
|
||||
*
|
||||
* @tags auth
|
||||
* @name Login
|
||||
|
|
@ -2369,7 +2357,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: LoginedUserVo;
|
||||
data?: string;
|
||||
},
|
||||
any
|
||||
>({
|
||||
|
|
@ -2381,6 +2369,38 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
...params,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取登陆用户信息
|
||||
*
|
||||
* @tags auth
|
||||
* @name GetUserInfo
|
||||
* @request POST:/api/v1/auth/info
|
||||
*/
|
||||
getUserInfo: (params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: User;
|
||||
},
|
||||
any
|
||||
>({
|
||||
path: `/api/v1/auth/info`,
|
||||
method: "POST",
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
},
|
||||
};
|
||||
log = {
|
||||
/**
|
||||
|
|
@ -3188,7 +3208,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
* @name GetMenu
|
||||
* @request GET:/api/v1/menus/{id}
|
||||
*/
|
||||
getMenu: (id: string, params: RequestParams = {}) => {
|
||||
getMenu: (id: number, params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
|
|
@ -3220,7 +3240,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
* @name SetMenu
|
||||
* @request PATCH:/api/v1/menus/{id}
|
||||
*/
|
||||
setMenu: (id: string, data: UpdateMenuDto, params: RequestParams = {}) => {
|
||||
setMenu: (id: number, data: UpdateMenuDto, params: RequestParams = {}) => {
|
||||
return this.request<Response, any>({
|
||||
path: `/api/v1/menus/${id}`,
|
||||
method: "PATCH",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const defineColumn = <T extends TableColumn>(column: T) => {
|
|||
export const updateColumn = defineColumn({
|
||||
title: "更新者",
|
||||
dataIndex: "createdAt",
|
||||
width: 200,
|
||||
width: 190,
|
||||
render({ record }) {
|
||||
return (
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
|
|
@ -24,7 +24,7 @@ export const updateColumn = defineColumn({
|
|||
export const createColumn = defineColumn({
|
||||
title: "创建者",
|
||||
dataIndex: "createdAt",
|
||||
width: 200,
|
||||
width: 190,
|
||||
render({ record }) {
|
||||
return (
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export const Table = defineComponent({
|
|||
},
|
||||
setup(props) {
|
||||
const loading = ref(false);
|
||||
const tableRef = ref<InstanceType<typeof BaseTable>>()
|
||||
const searchRef = ref<FormInstance>();
|
||||
const createRef = ref<FormModalInstance>();
|
||||
const modifyRef = ref<FormModalInstance>();
|
||||
|
|
@ -142,6 +143,7 @@ export const Table = defineComponent({
|
|||
const state = {
|
||||
loading,
|
||||
inlined,
|
||||
tableRef,
|
||||
searchRef,
|
||||
createRef,
|
||||
modifyRef,
|
||||
|
|
@ -184,6 +186,7 @@ export const Table = defineComponent({
|
|||
</div>
|
||||
|
||||
<BaseTable
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
bordered={false}
|
||||
{...this.$attrs}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { delConfirm } from "@/utils";
|
||||
import { Doption, Dropdown, Link, Message, TableColumnData } from "@arco-design/web-vue";
|
||||
import { Divider, Doption, Dropdown, Link, Message, TableColumnData } from "@arco-design/web-vue";
|
||||
import { isArray, merge } from "lodash-es";
|
||||
import { Component, Ref, reactive } from "vue";
|
||||
import { useFormModal } from "../form";
|
||||
|
|
@ -75,18 +75,21 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
|||
buttons.push(merge({}, config.columnButtonBase));
|
||||
}
|
||||
column.render = (columnData) => {
|
||||
return column.buttons?.map((btn) => {
|
||||
return column.buttons?.map((btn, index) => {
|
||||
if (btn.visible?.(columnData) === false) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
{...btn.buttonProps}
|
||||
onClick={() => onClick(btn, columnData, getTable)}
|
||||
disabled={btn.disabled?.(columnData)}
|
||||
>
|
||||
{btn.text}
|
||||
</Link>
|
||||
<>
|
||||
{index !== 0 ? <Divider direction="vertical" margin={2} class="!border-gray-300"></Divider> : null}
|
||||
<Link
|
||||
{...btn.buttonProps}
|
||||
onClick={() => onClick(btn, columnData, getTable)}
|
||||
disabled={btn.disabled?.(columnData)}
|
||||
>
|
||||
{btn.text}
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -101,10 +101,10 @@ const onSubmitForm = async () => {
|
|||
try {
|
||||
loading.value = true;
|
||||
const res = await api.auth.login(model);
|
||||
userStore.setUser(res.data.data);
|
||||
userStore.setAccessToken(res.data.data as unknown as string);
|
||||
Notification.success({
|
||||
title: "提示",
|
||||
content: `欢迎回来,${res.data.data.nickname}!`,
|
||||
content: `登陆成功!`,
|
||||
});
|
||||
router.push({ path: (route.query.redirect as string) || "/" });
|
||||
} catch (error: any) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<div>
|
||||
<i class="icon-park-outline-folder-close"></i>
|
||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
||||
{{ item.title }}
|
||||
<span class="text-xs text-gray-500"> ({{ item.count }}) </span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<template>
|
||||
<a-modal v-model:visible="modal.visible" title="上传文件" title-align="start" :footer="false">
|
||||
<a-modal v-model:visible="modal.visible" title="上传文件" title-align="start" :footer="false" :width="732">
|
||||
<a-alert class="mb-4">
|
||||
提示:支持大小在 1G 以内,格式为.png、.jpg、.webp、.mp4、.ogg的文件。
|
||||
</a-alert>
|
||||
<a-upload :custom-request="upload" draggable action="/api/v1/upload"></a-upload>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
<script setup lang="tsx">
|
||||
import { api } from "@/api";
|
||||
import { Table, useAniFormModal, useTable } from "@/components";
|
||||
import { Table, createColumn, useAniFormModal, useTable } from "@/components";
|
||||
import { dayjs } from "@/libs/dayjs";
|
||||
import numeral from "numeral";
|
||||
import AniGroup from './components/group.vue';
|
||||
|
|
@ -77,7 +77,7 @@ const table = useTable({
|
|||
<i class={`${getIcon(record.mimetype)} text-3xl mr-2`}></i>
|
||||
</div>
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<span>{record.name}</span>
|
||||
<span class="hover:text-brand-500 cursor-pointer">{record.name}</span>
|
||||
<span class="text-gray-400 text-xs truncate">
|
||||
{numeral(record.size).format("0 b")}
|
||||
</span>
|
||||
|
|
@ -86,12 +86,7 @@ const table = useTable({
|
|||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "上传时间",
|
||||
dataIndex: "createdAt",
|
||||
width: 200,
|
||||
render: ({ record }) => dayjs(record.createdAt).format(),
|
||||
},
|
||||
createColumn,
|
||||
{
|
||||
type: "button",
|
||||
title: "操作",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<div class="w-[210px] h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
||||
<div class="flex gap-2">
|
||||
<a-input-search allow-clear placeholder="字典名称..." class="mb-2"></a-input-search>
|
||||
<a-button @click="onCreateRow">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-add"></i>
|
||||
</template>
|
||||
</a-button>
|
||||
<form-modal></form-modal>
|
||||
</div>
|
||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto">
|
||||
<ul class="pl-0 mt-0">
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<div>
|
||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div>
|
||||
<a-dropdown>
|
||||
<a-button size="small" type="text">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-more-one text-gray-400 hover:text-gray-700"></i>
|
||||
</template>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption @click="onModifyRow(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-edit"></i>
|
||||
</template>
|
||||
修改
|
||||
</a-doption>
|
||||
<a-doption class="!text-red-500" @click="onDeleteRow">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-delete"></i>
|
||||
</template>
|
||||
删除
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAniFormModal } from "@/components";
|
||||
import { delConfirm } from "@/utils";
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
title: "用户性别",
|
||||
count: 23,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "微信头像",
|
||||
count: 52,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "文章封面",
|
||||
count: 19,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "山水诗画",
|
||||
count: 81,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "虾米沙雕",
|
||||
count: 12,
|
||||
},
|
||||
];
|
||||
|
||||
const list = ref(data);
|
||||
|
||||
const onModifyRow = (row: any) => {
|
||||
formCtx.props.title = "修改字典";
|
||||
formCtx.open(row);
|
||||
};
|
||||
|
||||
const onCreateRow = () => {
|
||||
formCtx.props.title = "新建字典";
|
||||
formCtx.open();
|
||||
};
|
||||
|
||||
const onDeleteRow = async () => {
|
||||
await delConfirm();
|
||||
};
|
||||
|
||||
const [formModal, formCtx] = useAniFormModal({
|
||||
title: "修改分组",
|
||||
trigger: false,
|
||||
modalProps: {
|
||||
width: 432,
|
||||
},
|
||||
model: {
|
||||
id: undefined,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: "title",
|
||||
label: "分组名称",
|
||||
type: "input",
|
||||
},
|
||||
],
|
||||
submit: async ({ model }) => {
|
||||
if (model.id) {
|
||||
const item = list.value.find((i) => i.id === model.id);
|
||||
if (item) {
|
||||
item.title = model.title;
|
||||
}
|
||||
} else {
|
||||
const ids = list.value.map((i) => i.id);
|
||||
const maxId = Math.max.apply(null, ids);
|
||||
list.value.push({
|
||||
id: maxId,
|
||||
title: model.title,
|
||||
count: 0,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<div class="h-full w-full grid grid-rows-[auto_1fr] overflow-hidden">
|
||||
<div class="py-2 px-4 bg-white">
|
||||
<bread-crumb></bread-crumb>
|
||||
</div>
|
||||
<div class="grid grid-cols-[auto_auto_1fr] h-full overflow-hidden bg-white p-4 m-4 rounded">
|
||||
<div>
|
||||
<ani-group></ani-group>
|
||||
</div>
|
||||
<a-divider direction="vertical"></a-divider>
|
||||
<div>
|
||||
<dict-table></dict-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import aniGroup from "./components/group.vue";
|
||||
import { useAniTable, useForm, Form } from "@/components";
|
||||
|
||||
const form = useForm({
|
||||
items: [
|
||||
{
|
||||
field: "字典名",
|
||||
label: "字典名",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "字典名",
|
||||
label: "",
|
||||
type: "submit",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const [dictTable, dict] = useAniTable({
|
||||
columns: [
|
||||
{
|
||||
title: "字典名",
|
||||
dataIndex: "name",
|
||||
},
|
||||
{
|
||||
title: "字典值",
|
||||
dataIndex: "name",
|
||||
},
|
||||
],
|
||||
create: {
|
||||
items: [
|
||||
{
|
||||
field: "字典名",
|
||||
label: "字典名",
|
||||
type: "input",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"sort": 20010,
|
||||
"title": "字典管理",
|
||||
"icon": "icon-park-outline-spanner"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
|
@ -57,21 +57,46 @@ const table = useTable({
|
|||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "登陆地址",
|
||||
dataIndex: "ip",
|
||||
width: 200,
|
||||
render({ record }) {
|
||||
return (
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<span>{record.addr || "未知"}</span>
|
||||
<span class="text-gray-400 text-xs truncate">{record.ip}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作系统",
|
||||
dataIndex: "os",
|
||||
width: 160,
|
||||
width: 200,
|
||||
render({ record }) {
|
||||
const [os, version] = record.os.split(" ");
|
||||
return (
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<span>{os || "未知"}</span>
|
||||
<span class="text-gray-400 text-xs truncate">{version}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "浏览器",
|
||||
dataIndex: "browser",
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: "登陆地址",
|
||||
dataIndex: "ip",
|
||||
width: 200,
|
||||
render: ({ record }) => `${record.addr || "未知"}(${record.ip})`,
|
||||
render({ record }) {
|
||||
const [browser, version] = record.browser.split(" ");
|
||||
return (
|
||||
<div class="flex flex-col overflow-hidden">
|
||||
<span>{browser || "未知"}</span>
|
||||
<span class="text-gray-400 text-xs truncate">v{version}</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
search: {
|
||||
|
|
|
|||
|
|
@ -1,69 +1,93 @@
|
|||
<template>
|
||||
<bread-page class="">
|
||||
<Table v-bind="table">
|
||||
<menu-table>
|
||||
<template #action>
|
||||
<a-button type="outline">展开/折叠</a-button>
|
||||
</template>
|
||||
</Table>
|
||||
</menu-table>
|
||||
</bread-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { api } from "@/api";
|
||||
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||
import { MenuTypes, MenuType } from "@/constants/menu";
|
||||
import { flatedMenus } from "@/router";
|
||||
|
||||
const menuArr = flatedMenus.map((i) => ({ label: i.title, value: i.id }));
|
||||
|
||||
const table = useTable({
|
||||
const expanded = ref(false);
|
||||
const toggleExpand = () => {
|
||||
console.log(menu.tableRef.value);
|
||||
expanded.value = !expanded.value;
|
||||
menu.tableRef.value?.tableRef?.expandAll(expanded.value);
|
||||
};
|
||||
|
||||
const [menuTable, menu] = useAniTable({
|
||||
data: (search, paging) => {
|
||||
return api.menu.getMenus({ ...search, ...paging, tree: true });
|
||||
},
|
||||
tableProps: {
|
||||
defaultExpandAllRows: true,
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: "菜单名称",
|
||||
title: () => {
|
||||
return (
|
||||
<span>
|
||||
菜单名称
|
||||
<a-link class="ml-1 select-none" onClick={toggleExpand}>
|
||||
{expanded.value ? "收起全部" : "展开全部"}
|
||||
</a-link>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
dataIndex: "name",
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "类型",
|
||||
dataIndex: "description",
|
||||
align: "center",
|
||||
width: 120,
|
||||
render: ({ record }) => (
|
||||
<a-tag color={MenuTypes.fmt(record.type, "color")}>
|
||||
{{
|
||||
icon: <i class={record.icon}></i>,
|
||||
default: () => MenuTypes.fmt(record.type),
|
||||
}}
|
||||
</a-tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "访问路径",
|
||||
dataIndex: "path",
|
||||
},
|
||||
{
|
||||
title: "启用",
|
||||
dataIndex: "createdAt",
|
||||
width: 80,
|
||||
align: "center",
|
||||
render: ({ record }) => <a-switch size="small" checked-color="#3c9"></a-switch>,
|
||||
render({ record }) {
|
||||
let id = "";
|
||||
if (record.type === MenuType.PAGE) {
|
||||
id = ` => ${record.path}`;
|
||||
}
|
||||
if (record.type === MenuType.BUTTON) {
|
||||
id = ` => ${record.code}`;
|
||||
}
|
||||
return (
|
||||
<div class="flex items-center gap-1">
|
||||
<a-tag bordered color={MenuTypes.fmt(record.type, "color")}>
|
||||
{{
|
||||
default: () => MenuTypes.fmt(record.type),
|
||||
}}
|
||||
</a-tag>
|
||||
<div class="flex-1 flex overflow-hidden ml-1">
|
||||
<div class="flex-1">
|
||||
<i class={`${record.icon} mr-1`}></i>
|
||||
<span>{record.name ?? "无"}</span>
|
||||
<span class="text-gray-400 text-xs truncate">{id}</span>
|
||||
</div>
|
||||
<a-switch checked-color="#3c9" size="small">
|
||||
{{
|
||||
"checked-icon": () => <i class="icon-park-outline-check"></i>,
|
||||
"unchecked-icon": () => <i class="icon-park-outline-close"></i>,
|
||||
}}
|
||||
</a-switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
createColumn,
|
||||
updateColumn,
|
||||
{
|
||||
title: "操作",
|
||||
type: "button",
|
||||
width: 184,
|
||||
width: 200,
|
||||
buttons: [
|
||||
{
|
||||
type: "modify",
|
||||
text: "修改",
|
||||
},
|
||||
{
|
||||
text: "新增下级",
|
||||
text: "新增子项",
|
||||
disabled: ({ record }) => record.type === MenuType.BUTTON,
|
||||
onClick: ({ record }) => {
|
||||
console.log(record);
|
||||
|
|
@ -78,10 +102,14 @@ const table = useTable({
|
|||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// title: "启用",
|
||||
// dataIndex: "createdAt",
|
||||
// width: 80,
|
||||
// align: "center",
|
||||
// render: ({ record }) => <a-switch checked-color="#3c9"></a-switch>,
|
||||
// },
|
||||
],
|
||||
pagination: {
|
||||
visible: false,
|
||||
},
|
||||
search: {
|
||||
items: [
|
||||
{
|
||||
|
|
@ -104,17 +132,6 @@ const table = useTable({
|
|||
class: "!grid grid-cols-2 gap-x-4",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: "type",
|
||||
initial: 1,
|
||||
label: "类型",
|
||||
type: "radio",
|
||||
options: MenuTypes.raw,
|
||||
nodeProps: {
|
||||
type: "button",
|
||||
class: "w-full",
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "parentId",
|
||||
initial: 0,
|
||||
|
|
@ -140,6 +157,17 @@ const table = useTable({
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "type",
|
||||
initial: 1,
|
||||
label: "类型",
|
||||
type: "radio",
|
||||
options: MenuTypes.raw,
|
||||
nodeProps: {
|
||||
type: "button",
|
||||
class: "w-full",
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "name",
|
||||
label: "名称",
|
||||
|
|
@ -209,12 +237,18 @@ const table = useTable({
|
|||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
<style lang="less">
|
||||
.arco-table-cell-expand-icon {
|
||||
span.arco-table-cell-inline-icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"sort": 10201,
|
||||
"sort": 10302,
|
||||
"title": "菜单管理",
|
||||
"icon": "icon-park-outline-add-subtract"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const [roleTable, roleCtx] = useAniTable({
|
|||
{
|
||||
title: "操作",
|
||||
type: "button",
|
||||
width: 184,
|
||||
width: 200,
|
||||
buttons: [
|
||||
{
|
||||
type: "modify",
|
||||
|
|
@ -74,7 +74,7 @@ const [roleTable, roleCtx] = useAniTable({
|
|||
create: {
|
||||
title: "新建角色",
|
||||
modalProps: {
|
||||
width: 580,
|
||||
width: 1080,
|
||||
maskClosable: false,
|
||||
},
|
||||
formProps: {
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@
|
|||
@progress="onProgress"
|
||||
>
|
||||
<template #upload-button>
|
||||
<a-link>选择文件...</a-link>
|
||||
<a-link>选择文件</a-link>
|
||||
<a-divider direction="vertical" :margin="4"></a-divider>
|
||||
<a-link>上传文件</a-link>
|
||||
</template>
|
||||
</a-upload>
|
||||
<div class="text-gray-400 text-xs">请选择不超过5MB,.png, .jpg, .webp格式的图片</div>
|
||||
<div class="text-gray-400 text-xs">请选择大小不超过5MB,.png, .jpg, .webp格式的图片</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -17,8 +17,7 @@ export const usePassworModal = () => {
|
|||
field: "password",
|
||||
label: ({ model }) => (
|
||||
<span>
|
||||
设置 <span class="text-brand-500 font-semibold">{model.nickname}</span>
|
||||
的新密码
|
||||
<span class="text-brand-500 font-semibold">{model.nickname}</span> 的新密码:
|
||||
</span>
|
||||
),
|
||||
type: "input",
|
||||
|
|
@ -8,9 +8,8 @@
|
|||
<script setup lang="tsx">
|
||||
import { api } from "@/api";
|
||||
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
||||
import InputAvatar from "./avatar.vue";
|
||||
import { usePassworModal } from "./password";
|
||||
import { MenuType } from "@/constants/menu";
|
||||
import InputAvatar from "./components/avatar.vue";
|
||||
import { usePassworModal } from "./components/password";
|
||||
|
||||
const [passModal, passCtx] = usePassworModal();
|
||||
|
||||
|
|
@ -26,7 +25,7 @@ const table = useTable({
|
|||
render: ({ record }) => (
|
||||
<div class="flex items-center">
|
||||
<a-avatar size={32}>
|
||||
<img src={record.avatar} alt="" />
|
||||
{record.avatar?.startsWith("/") ? <img src={record.avatar} alt="" /> : record.nickname?.[0]}
|
||||
</a-avatar>
|
||||
<span class="ml-2 flex-1 flex flex-col overflow-hidden">
|
||||
<span>{record.nickname}</span>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { LoginedUserVo } from "@/api";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
|
|
@ -17,7 +16,7 @@ export const useUserStore = defineStore({
|
|||
* 用户昵称
|
||||
*/
|
||||
nickname: "绝弹",
|
||||
/** `
|
||||
/** `
|
||||
* 用户头像地址
|
||||
*/
|
||||
avatar: "https://github.com/juetan.png",
|
||||
|
|
@ -39,17 +38,21 @@ export const useUserStore = defineStore({
|
|||
this.accessToken = token;
|
||||
},
|
||||
|
||||
setAccessToken(token: string) {
|
||||
this.accessToken = token;
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除用户信息
|
||||
*/
|
||||
clearUser() {
|
||||
this.$reset()
|
||||
this.$reset();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 设置用户信息
|
||||
*/
|
||||
setUser(user: LoginedUserVo) {
|
||||
setUser(user: any) {
|
||||
this.id = user.id;
|
||||
this.username = user.username;
|
||||
this.nickname = user.nickname;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
[class*=" icon-"],
|
||||
[class^="icon-"] {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
.table .arco-form-item-layout-inline {
|
||||
|
|
|
|||
Loading…
Reference in New Issue