feat: 优化登陆跳转/退出跳转
parent
0e39ea474a
commit
89b1de9b02
2
.env
2
.env
|
|
@ -2,7 +2,7 @@
|
||||||
# 应用配置
|
# 应用配置
|
||||||
# =====================================================================================
|
# =====================================================================================
|
||||||
# 网站标题
|
# 网站标题
|
||||||
VITE_TITLE = 绝弹管理系统
|
VITE_TITLE = 绝弹中心
|
||||||
# 网站副标题
|
# 网站副标题
|
||||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||||
# API接口前缀:参见 axios 的 baseURL
|
# API接口前缀:参见 axios 的 baseURL
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,27 @@
|
||||||
import { IToastOptions, toast } from "@/components";
|
import { IToastOptions, toast } from "@/components";
|
||||||
import { store, useUserStore } from "@/store";
|
import { store, useUserStore } from "@/store";
|
||||||
import { Api } from "../service/Api";
|
import { Api } from "../service/Api";
|
||||||
|
import { Message } from "@arco-design/web-vue";
|
||||||
|
|
||||||
const userStore = useUserStore(store);
|
class Service extends Api<unknown> {
|
||||||
|
/**
|
||||||
|
* 登陆过期处理函数
|
||||||
|
*/
|
||||||
|
tokenExpiredHandler: () => void = () => {};
|
||||||
|
/**
|
||||||
|
* 设置登陆过期后的处理函数
|
||||||
|
* @param handler
|
||||||
|
*/
|
||||||
|
setTokenExpiredHandler(handler: () => void) {
|
||||||
|
this.tokenExpiredHandler = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API 接口实例
|
* API 接口实例
|
||||||
* @see src/api/instance/instance.ts
|
* @see src/api/instance/instance.ts
|
||||||
*/
|
*/
|
||||||
export const api = new Api({
|
export const api = new Service({
|
||||||
baseURL: import.meta.env.VITE_API_PREFIX,
|
baseURL: import.meta.env.VITE_API_PREFIX,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -17,6 +30,7 @@ export const api = new Api({
|
||||||
*/
|
*/
|
||||||
api.instance.interceptors.request.use(
|
api.instance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
const userStore = useUserStore(store);
|
||||||
if (userStore.accessToken) {
|
if (userStore.accessToken) {
|
||||||
config.headers.Authorization = `Bearer ${userStore.accessToken}`;
|
config.headers.Authorization = `Bearer ${userStore.accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
@ -50,11 +64,18 @@ api.instance.interceptors.response.use(
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
const userStore = useUserStore(store);
|
||||||
error.config.closeToast?.();
|
error.config.closeToast?.();
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
console.log("response error", error.response);
|
console.log("response error", error.response);
|
||||||
|
const code = error.response.data?.code;
|
||||||
|
if (code === 4050 || code === 4051) {
|
||||||
|
userStore.clearUser();
|
||||||
|
api.tokenExpiredHandler?.();
|
||||||
|
}
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
console.log("request error", error.request);
|
console.log("request error", error.request);
|
||||||
|
Message.error(`提示:请求失败,检查网络状态或参数格式!`);
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<BreadCrumb></BreadCrumb>
|
<BreadCrumb></BreadCrumb>
|
||||||
<div class="mx-4 mt-4 p-4 bg-white">
|
<slot name="content">
|
||||||
<slot></slot>
|
<div class="mx-4 mt-4 p-4 bg-white">
|
||||||
</div>
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ export const FormModal = defineComponent({
|
||||||
* @description 可返回`{ message }`类型,用于显示提示信息
|
* @description 可返回`{ message }`类型,用于显示提示信息
|
||||||
*/
|
*/
|
||||||
submit: {
|
submit: {
|
||||||
type: Function as PropType<(arg: { model: Record<string, any>; items: IFormItem[] }) => any | Promise<any>>,
|
type: Function as PropType<(args: { model: any; items: IFormItem[] }) => PromiseLike<any>>,
|
||||||
default: () => true,
|
default: () => true,
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|
@ -191,7 +191,5 @@ export const FormModal = defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormModalInstance = InstanceType<typeof FormModal>;
|
export type FormModalInstance = InstanceType<typeof FormModal>;
|
||||||
|
|
||||||
export type FormModalProps = FormModalInstance["$props"];
|
export type FormModalProps = FormModalInstance["$props"];
|
||||||
|
|
||||||
export default FormModal;
|
export default FormModal;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,12 @@ export const config = {
|
||||||
title: "序号",
|
title: "序号",
|
||||||
width: 60,
|
width: 60,
|
||||||
align: "center",
|
align: "center",
|
||||||
render: ({ rowIndex }: any) => rowIndex + 1,
|
render: ({ rowIndex }: any) => {
|
||||||
|
const table = inject<any>("ref:table");
|
||||||
|
const page = table.pagination.current;
|
||||||
|
const size = table.pagination.pageSize;
|
||||||
|
return size * (page - 1) + rowIndex + 1;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
columnButtonBase: {
|
columnButtonBase: {
|
||||||
buttonProps: {
|
buttonProps: {
|
||||||
|
|
@ -63,5 +68,5 @@ export const config = {
|
||||||
getApiErrorMessage(error: any): string {
|
getApiErrorMessage(error: any): string {
|
||||||
const message = error?.response?.data?.message || error?.message || "请求失败";
|
const message = error?.response?.data?.message || error?.message || "请求失败";
|
||||||
return message;
|
return message;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -23,28 +23,23 @@ export interface TableColumnButton {
|
||||||
* 按钮文本
|
* 按钮文本
|
||||||
*/
|
*/
|
||||||
text?: string;
|
text?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作类型
|
* 操作类型
|
||||||
* @description `delete` 需配置`onClick`属性,`modify` 需配置根对象下的 `modify` 属性
|
* @description `delete` 需配置`onClick`属性,`modify` 需配置根对象下的 `modify` 属性
|
||||||
*/
|
*/
|
||||||
type?: "delete" | "modify";
|
type?: "delete" | "modify";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理函数
|
* 处理函数
|
||||||
*/
|
*/
|
||||||
onClick?: (data: UseColumnRenderOptions, openModify?: (model: Record<string, any>) => void) => void;
|
onClick?: (data: UseColumnRenderOptions) => any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否禁用按钮
|
* 是否禁用按钮
|
||||||
*/
|
*/
|
||||||
disabled?: (data: UseColumnRenderOptions) => boolean;
|
disabled?: (data: UseColumnRenderOptions) => boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示按钮
|
* 是否显示按钮
|
||||||
*/
|
*/
|
||||||
visible?: (data: UseColumnRenderOptions) => boolean;
|
visible?: (data: UseColumnRenderOptions) => boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 传递给按钮的props
|
* 传递给按钮的props
|
||||||
*/
|
*/
|
||||||
|
|
@ -56,13 +51,41 @@ export interface UseTableColumn extends TableColumnData {
|
||||||
* 表格列类型
|
* 表格列类型
|
||||||
*/
|
*/
|
||||||
type?: "index" | "button";
|
type?: "index" | "button";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按钮配置列表
|
* 按钮配置列表
|
||||||
*/
|
*/
|
||||||
buttons?: TableColumnButton[];
|
buttons?: TableColumnButton[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExtendedFormItem = Partial<IFormItem> & {
|
||||||
|
/**
|
||||||
|
* 继承 `create.items` 中指定 `field` 值的项
|
||||||
|
*/
|
||||||
|
extend?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Search = Partial<
|
||||||
|
Omit<FormProps, "items"> & {
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
items?: ExtendedFormItem[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
type Modify = Partial<
|
||||||
|
Omit<FormModalProps, "items"> & {
|
||||||
|
/**
|
||||||
|
* 是否继承 `create` 弹窗配置
|
||||||
|
*/
|
||||||
|
extend: boolean;
|
||||||
|
/**
|
||||||
|
* 表单项
|
||||||
|
*/
|
||||||
|
items?: ExtendedFormItem[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
export interface UseTableOptions extends Omit<TableProps, "search" | "create" | "modify" | "columns"> {
|
export interface UseTableOptions extends Omit<TableProps, "search" | "create" | "modify" | "columns"> {
|
||||||
/**
|
/**
|
||||||
* 表格列配置
|
* 表格列配置
|
||||||
|
|
@ -73,24 +96,7 @@ export interface UseTableOptions extends Omit<TableProps, "search" | "create" |
|
||||||
* 搜索表单配置
|
* 搜索表单配置
|
||||||
* @see FormProps
|
* @see FormProps
|
||||||
*/
|
*/
|
||||||
search?: Partial<
|
search?: Search;
|
||||||
Omit<FormProps, "items"> & {
|
|
||||||
/**
|
|
||||||
* 表单项
|
|
||||||
*/
|
|
||||||
items?: (Partial<IFormItem> & {
|
|
||||||
/**
|
|
||||||
* 继承`create.items`中指定field值的项
|
|
||||||
*/
|
|
||||||
extend?: string;
|
|
||||||
})[];
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
/**
|
|
||||||
* common props for `create` and `modify` modal
|
|
||||||
* @see FormModalProps
|
|
||||||
*/
|
|
||||||
common?: Partial<FormModalProps>;
|
|
||||||
/**
|
/**
|
||||||
* 新建弹窗配置
|
* 新建弹窗配置
|
||||||
*/
|
*/
|
||||||
|
|
@ -98,20 +104,7 @@ export interface UseTableOptions extends Omit<TableProps, "search" | "create" |
|
||||||
/**
|
/**
|
||||||
* 新建弹窗配置
|
* 新建弹窗配置
|
||||||
*/
|
*/
|
||||||
modify?: Partial<
|
modify?: Modify;
|
||||||
Omit<FormModalProps, "items"> & {
|
|
||||||
/**
|
|
||||||
* 是否继承`create`弹窗配置
|
|
||||||
*/
|
|
||||||
extend: boolean;
|
|
||||||
items?: (FormModalProps["items"][number] & {
|
|
||||||
/**
|
|
||||||
* 继承`create.items`弹窗配置中指定field值的项
|
|
||||||
*/
|
|
||||||
extend?: string;
|
|
||||||
})[];
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
/**
|
/**
|
||||||
* 详情弹窗配置
|
* 详情弹窗配置
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,9 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
const modifyAction = column.buttons.find((i) => i.type === "modify");
|
const modifyAction = column.buttons.find((i) => i.type === "modify");
|
||||||
if (modifyAction) {
|
if (modifyAction) {
|
||||||
const { onClick } = modifyAction;
|
const { onClick } = modifyAction;
|
||||||
modifyAction.onClick = (columnData) => {
|
modifyAction.onClick = async (columnData) => {
|
||||||
const fn = (data: Record<string, any>) => getTable()?.openModifyModal(data);
|
const result = (await onClick?.(columnData)) || columnData;
|
||||||
if (isFunction(onClick)) {
|
getTable()?.openModifyModal(result);
|
||||||
onClick(columnData, fn);
|
|
||||||
} else {
|
|
||||||
fn(columnData);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
column.buttons.unshift({
|
column.buttons.unshift({
|
||||||
|
|
@ -53,7 +49,10 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
const resData: any = await action?.onClick?.(data);
|
const resData: any = await action?.onClick?.(data);
|
||||||
resData.msg && Message.success(resData?.msg || "");
|
const message = resData?.data?.message;
|
||||||
|
if (message) {
|
||||||
|
Message.success(`提示:${message}`);
|
||||||
|
}
|
||||||
getTable()?.loadData();
|
getTable()?.loadData();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const message = error.response?.data?.message;
|
const message = error.response?.data?.message;
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,13 @@
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i class="icon-park-outline-back"></i>
|
<i class="icon-park-outline-back"></i>
|
||||||
</template>
|
</template>
|
||||||
返回上页
|
返回
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button type="outline" @click="router.push('/')">
|
<a-button type="outline" @click="router.push('/')">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i class="icon-park-outline-home"></i>
|
<i class="icon-park-outline-home"></i>
|
||||||
</template>
|
</template>
|
||||||
返回首页
|
首页
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
<a-dropdown>
|
<a-dropdown>
|
||||||
<span class="cursor-pointer">
|
<span class="cursor-pointer">
|
||||||
<a-avatar :size="28">
|
<a-avatar :size="28">
|
||||||
<img :src="userStore.avatar" :alt="userStore.nickname">
|
<img :src="userStore.avatar" :alt="userStore.nickname" />
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
<span class="mx-2">
|
<span class="mx-2">
|
||||||
{{ userStore.nickname }}
|
{{ userStore.nickname }}
|
||||||
|
|
@ -80,6 +80,7 @@ import Menu from "./components/menu.vue";
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const isCollapsed = ref(false);
|
const isCollapsed = ref(false);
|
||||||
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const themeConfig = ref({ visible: false });
|
const themeConfig = ref({ visible: false });
|
||||||
const onCollapse = (val: boolean) => {
|
const onCollapse = (val: boolean) => {
|
||||||
|
|
@ -115,15 +116,9 @@ const userButtons = [
|
||||||
icon: "icon-park-outline-logout",
|
icon: "icon-park-outline-logout",
|
||||||
text: "退出登录",
|
text: "退出登录",
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
userStore.clearUser()
|
userStore.clearUser();
|
||||||
Message.loading({
|
Message.success("提示:已退出登陆!");
|
||||||
content: '提示: 正在退出,请稍后...',
|
router.push({ path: "/login", query: { redirect: route.path } });
|
||||||
duration: 2000,
|
|
||||||
onClose: () => {
|
|
||||||
Message.success(`提示: 已成功退出登录!`)
|
|
||||||
router.push({ name: "_login" });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ const meridiem = dayjs.localeData().meridiem(dayjs().hour(), dayjs().minute());
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const model = reactive({ username: "juetan", password: "juetan" });
|
const model = reactive({ username: "juetan", password: "juetan" });
|
||||||
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const formRef = ref<InstanceType<typeof Form>>();
|
const formRef = ref<InstanceType<typeof Form>>();
|
||||||
|
|
@ -102,9 +103,8 @@ const onSubmitClick = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await api.auth.login(model);
|
const res = await api.auth.login(model);
|
||||||
userStore.setUser(res.data.data);
|
userStore.setUser(res.data.data);
|
||||||
userStore.username = res.data.data.username;
|
Message.success(`欢迎回来,${res.data.data.username}!`);
|
||||||
Message.success(`欢迎回来,${res.data.data.username}!`)
|
router.push({ path: (route.query.redirect as string) || "/" });
|
||||||
router.push({ path: "/" });
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const message = error?.response?.data?.message;
|
const message = error?.response?.data?.message;
|
||||||
if (message) {
|
if (message) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,140 @@
|
||||||
<template>
|
<template>
|
||||||
<bread-page class="">Demo/test Page</bread-page>
|
<bread-page id="list-page">
|
||||||
|
<template #content>
|
||||||
|
<AList class="mx-5 mt-3 bg-white" :bordered="true">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex gap-2 items-center justify-between text-sm bg-gray-50 px-5 py-4">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<ACheckbox></ACheckbox>
|
||||||
|
<AInput class="inline-block w-80" placeholder="输入名称关键字"></AInput>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<ADropdown>
|
||||||
|
<span class="cursor-pointer">
|
||||||
|
上传者
|
||||||
|
<i class="icon-park-outline-down"></i>
|
||||||
|
</span>
|
||||||
|
<template #content>
|
||||||
|
<ADoption>
|
||||||
|
<AInput placeholder="用户名关键字" />
|
||||||
|
</ADoption>
|
||||||
|
<ADoption>
|
||||||
|
<AAvatar :size="20">
|
||||||
|
<img src="https://picsum.photos/seed/picsum/200/300" alt="" />
|
||||||
|
</AAvatar>
|
||||||
|
绝弹土豆
|
||||||
|
</ADoption>
|
||||||
|
</template>
|
||||||
|
</ADropdown>
|
||||||
|
<ADropdown>
|
||||||
|
<span class="cursor-pointer">
|
||||||
|
排序:默认
|
||||||
|
<i class="icon-park-outline-down"></i>
|
||||||
|
</span>
|
||||||
|
<template #content>
|
||||||
|
<ADoption>
|
||||||
|
<div class="w-40">默认</div>
|
||||||
|
</ADoption>
|
||||||
|
<ADoption>
|
||||||
|
<template #icon>
|
||||||
|
<i class="icon-park-outline-sort-amount-up"></i>
|
||||||
|
</template>
|
||||||
|
按创建时间升序
|
||||||
|
</ADoption>
|
||||||
|
<ADoption>
|
||||||
|
<template #icon>
|
||||||
|
<i class="icon-park-outline-sort-amount-down"></i>
|
||||||
|
</template>
|
||||||
|
按创建时间降序
|
||||||
|
</ADoption>
|
||||||
|
<ADoption>
|
||||||
|
<template #icon>
|
||||||
|
<i class="icon-park-outline-align-text-top"></i>
|
||||||
|
</template>
|
||||||
|
按文件大小升序
|
||||||
|
</ADoption>
|
||||||
|
<ADoption>
|
||||||
|
<template #icon>
|
||||||
|
<i class="icon-park-outline-align-text-bottom"></i>
|
||||||
|
</template>
|
||||||
|
按文件大小降序
|
||||||
|
</ADoption>
|
||||||
|
</template>
|
||||||
|
</ADropdown>
|
||||||
|
<div class="space-x-1">
|
||||||
|
<span
|
||||||
|
class="inline-flex p-1 hover:bg-slate-100 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
<i class="icon-park-outline-list"></i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="inline-flex p-1 hover:bg-slate-100 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
<i class="icon-park-outline-insert-table"></i>
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="inline-flex p-1 hover:bg-slate-100 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
<i class="icon-park-outline-refresh"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<AListItem v-for="i in 10">
|
||||||
|
<AListItemMeta title="测试图片.png" description="image/png 1.2MB">
|
||||||
|
<template #avatar>
|
||||||
|
<ACheckbox class="mr-3"></ACheckbox>
|
||||||
|
<AImage src="https://picsum.photos/seed/picsum/200/300" height="40">
|
||||||
|
<img src="https://picsum.photos/seed/picsum/200/300" alt="" />
|
||||||
|
</AImage>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<span class="hover:text-blue-500 cursor-pointer">测试图片.png</span>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
<div class="text-xs text-gray-400">image/png 1.2MB</div>
|
||||||
|
</template>
|
||||||
|
</AListItemMeta>
|
||||||
|
<template #actions>
|
||||||
|
<span class="text-xs text-gray-400">
|
||||||
|
<i class="icon-park-outline-user !w-[14px] !h-[14px]"></i>
|
||||||
|
绝弹
|
||||||
|
</span>
|
||||||
|
<span class="text-xs text-gray-400">2023-08-17 17:00:01</span>
|
||||||
|
<ADropdown>
|
||||||
|
<span class="inline-flex p-1 hover:bg-slate-100 rounded cursor-pointer">
|
||||||
|
<i class="icon-park-outline-more"></i>
|
||||||
|
</span>
|
||||||
|
<template #content>
|
||||||
|
<ADoption class="w-32"> <div class="w-32">详情</div> </ADoption>
|
||||||
|
<ADoption class="!text-red-500 !hover-bg-red-50">
|
||||||
|
<template #icon>
|
||||||
|
<i class="icon-park-outline-delete"></i>
|
||||||
|
</template>
|
||||||
|
删除
|
||||||
|
</ADoption>
|
||||||
|
</template>
|
||||||
|
</ADropdown>
|
||||||
|
</template>
|
||||||
|
</AListItem>
|
||||||
|
</AList>
|
||||||
|
</template>
|
||||||
|
</bread-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style lang="less">
|
||||||
|
#list-page {
|
||||||
|
.arco-list-header {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.arco-dropdown-list {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<route lang="json">
|
<route lang="json">
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ const table = useTable({
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
// {
|
{
|
||||||
// type: 'index'
|
type: "index",
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
title: "姓名",
|
title: "姓名",
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
|
|
@ -64,7 +64,17 @@ const table = useTable({
|
||||||
buttons: [],
|
buttons: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
common: {
|
search: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
field: "username",
|
||||||
|
label: "姓名",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
title: "新建用户",
|
||||||
modalProps: {
|
modalProps: {
|
||||||
width: 432,
|
width: 432,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
|
|
@ -124,18 +134,6 @@ const table = useTable({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
|
||||||
search: {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
field: "username",
|
|
||||||
label: "姓名",
|
|
||||||
type: "input",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
create: {
|
|
||||||
title: "新建用户",
|
|
||||||
submit: ({ model }) => {
|
submit: ({ model }) => {
|
||||||
return api.user.addUser(model as any, {
|
return api.user.addUser(model as any, {
|
||||||
type: ContentType.FormData,
|
type: ContentType.FormData,
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ const table = useTable({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
modalProps: {
|
modalProps: {
|
||||||
|
|
@ -106,7 +106,7 @@ const table = useTable({
|
||||||
layout: "vertical",
|
layout: "vertical",
|
||||||
},
|
},
|
||||||
submit: ({ model }) => {
|
submit: ({ model }) => {
|
||||||
return api.permission.addPermission(model as any);
|
return api.permission.addPermission(model);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modify: {
|
modify: {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,101 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="m-4 p-4 bg-white">
|
<div class="m-4 p-4 bg-white">
|
||||||
Post Page
|
<Table v-bind="table" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx" name="PostPage">
|
<script setup lang="tsx" name="PostPage">
|
||||||
|
import { api } from "@/api";
|
||||||
|
import { Table, useTable } from "@/components";
|
||||||
|
import { dayjs } from "@/plugins";
|
||||||
|
|
||||||
|
const table = useTable({
|
||||||
|
data: async (model, paging) => {
|
||||||
|
return api.post.getPosts({ ...model, ...paging });
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
type: "index",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文章名称",
|
||||||
|
dataIndex: "title",
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文章描述",
|
||||||
|
dataIndex: "description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "创建时间",
|
||||||
|
dataIndex: "createdAt",
|
||||||
|
width: 200,
|
||||||
|
render: ({ record }) => dayjs(record.createdAt).format(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
type: "button",
|
||||||
|
width: 70,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
type: "modify",
|
||||||
|
text: "修改",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
search: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
extend: "name",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
title: "新建文章",
|
||||||
|
modalProps: {
|
||||||
|
width: 580,
|
||||||
|
maskClosable: false,
|
||||||
|
},
|
||||||
|
formProps: {
|
||||||
|
layout: "vertical",
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
field: "title",
|
||||||
|
label: "文章名称",
|
||||||
|
type: "input",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "slug",
|
||||||
|
label: "文章标识",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "description",
|
||||||
|
label: "文章描述",
|
||||||
|
type: "input",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "content",
|
||||||
|
label: "文章内容",
|
||||||
|
type: "textarea",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
submit: ({ model }) => {
|
||||||
|
return api.role.addRole(model);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modify: {
|
||||||
|
extend: true,
|
||||||
|
title: "修改文章",
|
||||||
|
submit: ({ model }) => {
|
||||||
|
return api.post.updatePost(model.id, model);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ const table = useTable({
|
||||||
],
|
],
|
||||||
|
|
||||||
submit: ({ model }) => {
|
submit: ({ model }) => {
|
||||||
return api.role.addRole(model as any);
|
return api.role.addRole(model);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modify: {
|
modify: {
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ const table = useTable({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
submit: ({ model }) => {
|
submit: ({ model }) => {
|
||||||
console.log(model);
|
return api.user.addUser(model);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modify: {
|
modify: {
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,32 @@ import { store, useUserStore } from "@/store";
|
||||||
import { Message } from "@arco-design/web-vue";
|
import { Message } from "@arco-design/web-vue";
|
||||||
import { NavigationGuardWithThis } from "vue-router";
|
import { NavigationGuardWithThis } from "vue-router";
|
||||||
|
|
||||||
const whitelist = ["/404"];
|
const whitelist = ["/:all(.*)*"];
|
||||||
const signoutlist = ["/login"];
|
const signoutlist = ["/login"];
|
||||||
|
|
||||||
export const authGuard: NavigationGuardWithThis<undefined> = async function (to, from, next) {
|
export const authGuard: NavigationGuardWithThis<undefined> = async function (to) {
|
||||||
// 放在外面,pinia-plugin-peristedstate 插件会失效
|
// 放在外面,pinia-plugin-peristedstate 插件会失效
|
||||||
const userStore = useUserStore(store);
|
const userStore = useUserStore(store);
|
||||||
if (to.meta?.auth === false) {
|
if (to.meta?.auth === false) {
|
||||||
return next();
|
return true;
|
||||||
}
|
}
|
||||||
if (whitelist.includes(to.fullPath)) {
|
if (whitelist.includes(to.path) || to.name === "_all") {
|
||||||
return next();
|
return true;
|
||||||
}
|
}
|
||||||
if (signoutlist.includes(to.fullPath)) {
|
if (signoutlist.includes(to.path)) {
|
||||||
if (userStore.id) {
|
if (userStore.accessToken) {
|
||||||
Message.warning(`提示:您已登陆,如需重新请退出后再操作!`);
|
Message.warning(`提示:您已登陆,如需重新请退出后再操作!`);
|
||||||
return next(false);
|
return false;
|
||||||
}
|
}
|
||||||
return next();
|
return true;
|
||||||
}
|
}
|
||||||
if (!userStore.accessToken) {
|
if (!userStore.accessToken) {
|
||||||
return next("/login");
|
return {
|
||||||
|
path: "/login",
|
||||||
|
query: {
|
||||||
|
redirect: to.path,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
next();
|
return true;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { NProgress } from "@/plugins";
|
import { NProgress } from "@/plugins";
|
||||||
import { NavigationGuardWithThis, NavigationHookAfter } from "vue-router";
|
import { NavigationGuardWithThis, NavigationHookAfter } from "vue-router";
|
||||||
|
|
||||||
const before: NavigationGuardWithThis<undefined> = function (to, from, next) {
|
const before: NavigationGuardWithThis<undefined> = function () {
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
next();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const after: NavigationHookAfter = function () {
|
const after: NavigationHookAfter = function () {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import { store, useAppStore } from "@/store";
|
import { store, useAppStore } from "@/store";
|
||||||
import { NavigationGuardWithThis } from "vue-router";
|
import { NavigationHookAfter } from "vue-router";
|
||||||
|
|
||||||
export const titleGuard: NavigationGuardWithThis<undefined> = function (to, from, next) {
|
export const titleGuard: NavigationHookAfter = function (to) {
|
||||||
const appStore = useAppStore(store);
|
const appStore = useAppStore(store);
|
||||||
const title = to.meta.title || appStore.title;
|
const title = to.meta.title || appStore.title;
|
||||||
const subtitle = appStore.subtitle;
|
const subtitle = appStore.subtitle;
|
||||||
document.title = `${title} | ${subtitle}`;
|
document.title = `${title} | ${subtitle}`;
|
||||||
next();
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { authGuard } from "../guards/guard-auth";
|
||||||
import { nprogressGuard } from "../guards/guard-nprogress";
|
import { nprogressGuard } from "../guards/guard-nprogress";
|
||||||
import { titleGuard } from "../guards/guard-title";
|
import { titleGuard } from "../guards/guard-title";
|
||||||
import { routes } from "../routes";
|
import { routes } from "../routes";
|
||||||
|
import { api } from "@/api";
|
||||||
|
|
||||||
export const router = createRouter({
|
export const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
|
|
@ -17,5 +18,10 @@ export const router = createRouter({
|
||||||
|
|
||||||
router.beforeEach(nprogressGuard.before);
|
router.beforeEach(nprogressGuard.before);
|
||||||
router.afterEach(nprogressGuard.after);
|
router.afterEach(nprogressGuard.after);
|
||||||
router.beforeEach(titleGuard);
|
|
||||||
router.beforeEach(authGuard);
|
router.beforeEach(authGuard);
|
||||||
|
router.afterEach(titleGuard);
|
||||||
|
|
||||||
|
api.setTokenExpiredHandler(() => {
|
||||||
|
const redirect = router.currentRoute.value.path;
|
||||||
|
router.push({ path: "/login", query: { redirect } });
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ import { RouteRecordRaw } from "vue-router";
|
||||||
const APP_ROUTE_NAME = "_app";
|
const APP_ROUTE_NAME = "_app";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 转换一维路由为二维路由,其中以 _ 开头的路由为顶级路由,其余为应用路由
|
* 转换一维路由为二维路由,以 _ 开头的路由为顶级路由,其余为应用路由
|
||||||
* @param routes 路由配置
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
const transformRoutes = (routes: RouteRecordRaw[]) => {
|
const transformRoutes = (routes: RouteRecordRaw[]) => {
|
||||||
const topRoutes: RouteRecordRaw[] = [];
|
const topRoutes: RouteRecordRaw[] = [];
|
||||||
|
|
@ -33,26 +31,7 @@ const transformRoutes = (routes: RouteRecordRaw[]) => {
|
||||||
appRoute.children = appRoutes;
|
appRoute.children = appRoutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return topRoutes;
|
return [topRoutes, appRoutes];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const [routes, appRoutes] = transformRoutes(generatedRoutes);
|
||||||
* 获取应用路由
|
|
||||||
* @param routes 路由配置
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const getAppRoutes = (routes: RouteRecordRaw[]) => {
|
|
||||||
return routes.find((i) => i.name === APP_ROUTE_NAME)?.children || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所有路由
|
|
||||||
*/
|
|
||||||
const routes = transformRoutes(generatedRoutes);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 应用路由
|
|
||||||
*/
|
|
||||||
const appRoutes = getAppRoutes(routes);
|
|
||||||
|
|
||||||
export { routes, appRoutes };
|
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,12 @@ declare module '@vue/runtime-core' {
|
||||||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
||||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||||
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
||||||
|
ADot: typeof import('@arco-design/web-vue')['Dot']
|
||||||
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
||||||
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
|
||||||
AForm: typeof import('@arco-design/web-vue')['Form']
|
AForm: typeof import('@arco-design/web-vue')['Form']
|
||||||
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
|
||||||
|
AImage: typeof import('@arco-design/web-vue')['Image']
|
||||||
AInput: typeof import('@arco-design/web-vue')['Input']
|
AInput: typeof import('@arco-design/web-vue')['Input']
|
||||||
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
|
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
|
||||||
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
ALayout: typeof import('@arco-design/web-vue')['Layout']
|
||||||
|
|
@ -25,8 +27,12 @@ declare module '@vue/runtime-core' {
|
||||||
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
|
||||||
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
|
||||||
ALink: typeof import('@arco-design/web-vue')['Link']
|
ALink: typeof import('@arco-design/web-vue')['Link']
|
||||||
|
AList: typeof import('@arco-design/web-vue')['List']
|
||||||
|
AListItem: typeof import('@arco-design/web-vue')['ListItem']
|
||||||
|
AListItemMeta: typeof import('@arco-design/web-vue')['ListItemMeta']
|
||||||
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
AMenu: typeof import('@arco-design/web-vue')['Menu']
|
||||||
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
|
||||||
|
APopover: typeof import('@arco-design/web-vue')['Popover']
|
||||||
ASpace: typeof import('@arco-design/web-vue')['Space']
|
ASpace: typeof import('@arco-design/web-vue')['Space']
|
||||||
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
|
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
|
||||||
ATag: typeof import('@arco-design/web-vue')['Tag']
|
ATag: typeof import('@arco-design/web-vue')['Tag']
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue