feat: 完善登陆页面的部分功能

master
luoer 2023-08-04 17:46:03 +08:00
parent f7818bda5c
commit acd815736e
39 changed files with 1080 additions and 453 deletions

20
.env
View File

@ -2,29 +2,27 @@
# 应用配置
# =====================================================================================
# 网站标题
VITE_APP_TITLE = 绝弹管理系统
VITE_TITLE = 绝弹管理系统
# 网站副标题
VITE_APP_SUBTITLE = 快速开发web应用的模板工具
# Axios基本URL
VITE_APP_API_BASE_URL = /api
VITE_SUBTITLE = 快速开发web应用的模板工具
# API接口前缀
VITE_API_PREFIX = http://127.0.0.1:3030/
# API文档地址 备注:需为符合 OPENAPI 规范的json文件
VITE_API_SWAGGER = http://127.0.0.1:3030/openapi.json
# =====================================================================================
# 开发设置
# =====================================================================================
# API接口地址(开发环境)
VITE_API_BASE_URL = http://127.0.0.1:3030
# API代理地址(开发环境)
VITE_API_PROXY_URL = /api
# API文档地址(开发环境) 备注需为openapi规范的json文件
VITE_API_DOCS_URL = http://127.0.0.1:3030/openapi.json
# 端口号(开发环境)
VITE_DEV_PORT = 3020
VITE_PORT = 3020
# 主机地址(开发环境)
VITE_DEV_HOST = 0.0.0.0
VITE_HOST = 0.0.0.0
# =====================================================================================
# 构建设置
# =====================================================================================
# 构建时加载的文件后缀.
# 例如设置为todo则会首先尝试加载index.todo.vue文件不存在时再加载index.vue文件
VITE_BUILD_EXTENSION = todo
VITE_EXTENSION = todo

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%VITE_APP_TITLE% - %VITE_APP_SUBTITLE%</title>
<title>%VITE_TITLE% - %VITE_SUBTITLE%</title>
</head>
<body>
<div id="app" class="dark:bg-slate-900 dark:text-slate-200">
@ -51,7 +51,7 @@
</style>
<div class="loading">
<img src="/assets/loading.svg" alt="loading" class="loading-image" />
<h1 class="loading-title">欢迎访问%VITE_APP_TITLE%</h1>
<h1 class="loading-title">欢迎访问%VITE_TITLE%</h1>
<div class="loading-tip">正在加载中, 请稍后...</div>
</div>
</div>

View File

@ -34,6 +34,7 @@
"nprogress": "^0.2.0",
"numeral": "^2.0.6",
"pinia": "^2.0.33",
"pinia-plugin-persistedstate": "^3.2.0",
"plop": "^3.1.2",
"release-it": "^15.10.1",
"swagger-typescript-api": "^12.0.4",

View File

@ -46,6 +46,9 @@ devDependencies:
pinia:
specifier: ^2.0.33
version: 2.1.4(typescript@4.9.5)(vue@3.3.4)
pinia-plugin-persistedstate:
specifier: ^3.2.0
version: 3.2.0(pinia@2.1.4)
plop:
specifier: ^3.1.2
version: 3.1.2
@ -5833,6 +5836,14 @@ packages:
dev: true
optional: true
/pinia-plugin-persistedstate@3.2.0(pinia@2.1.4):
resolution: {integrity: sha512-tZbNGf2vjAQcIm7alK40sE51Qu/m9oWr+rEgNm/2AWr1huFxj72CjvpQcIQzMknDBJEkQznCLAGtJTIcLKrKdw==}
peerDependencies:
pinia: ^2.0.0
dependencies:
pinia: 2.1.4(typescript@4.9.5)(vue@3.3.4)
dev: true
/pinia@2.1.4(typescript@4.9.5)(vue@3.3.4):
resolution: {integrity: sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==}
peerDependencies:

View File

@ -1,4 +0,0 @@
## 修改
route-docs.ejs
- 移除 `@description` 关键字

View File

@ -8,17 +8,17 @@ const env = loadEnv("development", process.cwd());
const run = async () => {
const output = await generateApi({
url: env.VITE_API_DOCS_URL,
url: env.VITE_API_SWAGGER,
templates: path.resolve(__dirname, "./template"),
output: path.resolve(process.cwd(), "src/api/service"),
name: "index.ts",
name: "Api.ts",
singleHttpClient: false,
httpClientType: "axios",
unwrapResponseData: false,
moduleNameIndex: 1,
moduleNameFirstTag: true,
cleanOutput: true,
// generateRouteTypes: true,
generateRouteTypes: true,
extractRequestParams: true,
modular: false,
prettier: {
@ -61,3 +61,11 @@ const run = async () => {
};
run();
/**
*
*
* route-docs.ejs
* - `@description`
*/

View File

@ -57,7 +57,6 @@ module.exports = {
return order.indexOf(a.title) - order.indexOf(b.title);
},
commitPartial: loadTemplate("commit"),
// headerPartial: loadTemplate('header'),
mainTemplate: loadTemplate("main"),
},
},

View File

@ -8,13 +8,13 @@ import pkg from "../../package.json";
* @description APPTIFY
*/
const LOGO = `
________ ______ ______ _________ ________ ______ __ __
/_______/\\\\ /_____/\\\\ /_____/\\\\ /________/\\\\/_______/\\\\/_____/\\\\ /_/\\\\/_/\\\\
\\\\::: _ \\\\ \\\\\\\\:::_ \\\\ \\\\\\\\:::_ \\\\ \\\\\\\\__.::.__\\\\/\\\\__.::._\\\\/\\\\::::_\\\\/_\\\\ \\\\ \\\\ \\\\ \\\\
\\\\::(_) \\\\ \\\\\\\\:(_) \\\\ \\\\\\\\:(_) \\\\ \\\\ \\\\::\\\\ \\\\ \\\\::\\\\ \\\\ \\\\:\\\\/___/\\\\\\\\:\\\\_\\\\ \\\\ \\\\
\\\\:: __ \\\\ \\\\\\\\: ___\\\\/ \\\\: ___\\\\/ \\\\::\\\\ \\\\ _\\\\::\\\\ \\\\__\\\\:::._\\\\/ \\\\::::_\\\\/
\\\\:.\\\\ \\\\ \\\\ \\\\\\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\::\\\\ \\\\ /__\\\\::\\\\__/\\\\\\\\:\\\\ \\\\ \\\\::\\\\ \\\\
\\\\__\\\\/\\\\__\\\\/ \\\\_\\\\/ \\\\_\\\\/ \\\\__\\\\/ \\\\________\\\\/ \\\\_\\\\/ \\\\__\\\\/
________ ______ ______ _________ ________ ______ __ __
/_______/\\\\ /_____/\\\\ /_____/\\\\ /________/\\\\/_______/\\\\/_____/\\\\ /_/\\\\/_/\\\\
\\\\::: _ \\\\ \\\\\\\\:::_ \\\\ \\\\\\\\:::_ \\\\ \\\\\\\\__.::.__\\\\/\\\\__.::._\\\\/\\\\::::_\\\\/_\\\\ \\\\ \\\\ \\\\ \\\\
\\\\::(_) \\\\ \\\\\\\\:(_) \\\\ \\\\\\\\:(_) \\\\ \\\\ \\\\::\\\\ \\\\ \\\\::\\\\ \\\\ \\\\:\\\\/___/\\\\\\\\:\\\\_\\\\ \\\\ \\\\
\\\\:: __ \\\\ \\\\\\\\: ___\\\\/ \\\\: ___\\\\/ \\\\::\\\\ \\\\ _\\\\::\\\\ \\\\__\\\\:::._\\\\/ \\\\::::_\\\\/
\\\\:.\\\\ \\\\ \\\\ \\\\\\\\ \\\\ \\\\ \\\\ \\\\ \\\\ \\\\::\\\\ \\\\ /__\\\\::\\\\__/\\\\\\\\:\\\\ \\\\ \\\\::\\\\ \\\\
\\\\__\\\\/\\\\__\\\\/ \\\\_\\\\/ \\\\_\\\\/ \\\\__\\\\/ \\\\________\\\\/ \\\\_\\\\/ \\\\__\\\\/
`;
/**
@ -68,7 +68,7 @@ export default function plugin(): Plugin {
configResolved(resolvedConfig) {
config = resolvedConfig;
const defaultExt = config.mode === "development" ? "dev" : "prod";
extension = config.env.VITE_BUILD_EXTENTION || defaultExt;
extension = config.env.VITE_EXTENTION || defaultExt;
},
async transformIndexHtml(html) {

View File

@ -1,2 +1,3 @@
export * from "./instance";
export * from "./service";
export * from "./instance/api";
export * from "./service/Api";
export * from "./helper/useRequest";

View File

@ -1,34 +1,15 @@
import { Api } from "../service";
import { Api } from "../service/Api";
import { toast, IToastOptions } from "@/components";
import { useUserStore, store } from "@/store";
const userStore = useUserStore(store);
/**
* ,
*/
class Service extends Api<unknown> {
github = {
/**
*
*/
getRepoInfo: async () => {
const info: Record<string, any> = await this.request({
baseURL: "https://api.github.com",
path: "/repos/juetan/apptify-admin",
method: "GET",
});
return info;
},
};
}
/**
* api
* API
* @see src/api/instance/instance.ts
*/
const api = new Service({
baseURL: import.meta.env.VITE_API_BASE_URL,
export const api = new Api({
baseURL: import.meta.env.VITE_API_PREFIX,
});
/**
@ -42,19 +23,17 @@ api.instance.interceptors.request.use(
if (config.toast) {
let options: IToastOptions = {};
if (typeof config.toast === "string") {
options = {
message: config.toast,
};
options = { message: config.toast };
}
if (typeof config.toast === "object") {
options = config.toast;
}
config._closeToast = toast(options);
config.closeToast = toast(options);
}
return config;
},
(error) => {
error.config?._closeToast?.();
error.config?.closeToast?.();
return Promise.reject(error);
}
);
@ -64,14 +43,14 @@ api.instance.interceptors.request.use(
*/
api.instance.interceptors.response.use(
(res) => {
res.config?._closeToast?.();
res.config.closeToast?.();
if (res.data?.code && res.data.code !== 2000) {
return Promise.reject(res);
}
return res;
},
(error) => {
error.config?._closeToast?.();
error.config.closeToast?.();
if (error.request) {
console.log("request error", error.request);
}
@ -81,5 +60,3 @@ api.instance.interceptors.response.use(
return Promise.reject(error);
}
);
export { api };

View File

@ -4,14 +4,14 @@ import { IToastOptions } from "@/components";
declare module "axios" {
interface AxiosRequestConfig {
/**
* toast config
*
* @default false
*/
toast?: boolean | string | IToastOptions;
/**
* close toast(internal)
*
* @private
*/
_closeToast?: () => void;
closeToast?: () => void;
}
}

View File

@ -1,3 +0,0 @@
export * from "./instance";
export * from "./useRequest";

File diff suppressed because it is too large Load Diff

View File

@ -26,6 +26,7 @@ export interface TableColumnButton {
/**
*
* @description `delete` `onClick``modify` `modify`
*/
type?: "delete" | "modify";
@ -79,7 +80,7 @@ export interface UseTableOptions extends Omit<TableProps, "search" | "create" |
*/
items?: (Partial<IFormItem> & {
/**
* common.itemsfield
* `create.items`field
*/
extend?: string;
})[];
@ -93,7 +94,7 @@ export interface UseTableOptions extends Omit<TableProps, "search" | "create" |
/**
*
*/
create?: FormModalProps;
create?: Partial<FormModalProps>;
/**
*
*/
@ -105,7 +106,7 @@ export interface UseTableOptions extends Omit<TableProps, "search" | "create" |
extend: boolean;
items?: (FormModalProps["items"][number] & {
/**
* `create`field
* `create.items`field
*/
extend?: string;
})[];

View File

@ -8,7 +8,7 @@ import { UseTableOptions } from "./use-interface";
/**
* hook
* @see src/components/table/use-table.tsx
* @see `src/components/table/use-table.tsx`
*/
export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions)): any => {
const options: UseTableOptions = typeof optionsOrFn === "function" ? optionsOrFn() : optionsOrFn;
@ -51,9 +51,16 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
Modal.warning({
...config.columnButtonDelete,
onOk: async () => {
const resData: any = await action?.onClick?.(data);
resData.msg && Message.success(resData?.msg || "");
getTable()?.loadData();
try {
const resData: any = await action?.onClick?.(data);
resData.msg && Message.success(resData?.msg || "");
getTable()?.loadData();
} catch (error: any) {
const message = error.response?.data?.message;
if (message) {
Message.warning(`提示:${message}`);
}
}
},
});
};
@ -97,8 +104,7 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
if (item.extend) {
const createItem = createItems.find((i) => i.field === item.extend);
if (createItem) {
const mergedItem = merge({}, createItem, item);
searchItems.push(mergedItem);
searchItems.push(merge({}, createItem, item));
continue;
}
}

View File

@ -2,7 +2,7 @@ import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
import { store } from "./store";
import { style } from "./style";
import { style } from "./styles";
const run = async () => {
const app = createApp(App);

View File

@ -1,6 +1,6 @@
<template>
<div class="page-login w-full h-full grid grid-rows-[auto_1fr_auto]">
<div class=" top-0 m-0 h-20 w-full px-10 z-10">
<div class="top-0 m-0 h-20 w-full px-10 z-10">
<div class="md:hidden flex items-center justify-between h-13">
<div class="flex items-center">
<img src="/favicon.ico" alt="" width="20" height="20" class="mr-1" />
@ -55,13 +55,15 @@
</template>
<script lang="ts" setup>
import { api } from "@/api";
import { dayjs } from "@/plugins";
import { useAppStore } from "@/store";
import { useAppStore, useUserStore } from "@/store";
import { FieldRule, Form, Modal } from "@arco-design/web-vue";
import { reactive } from "vue";
const meridiem = dayjs.localeData().meridiem(dayjs().hour(), dayjs().minute());
const appStore = useAppStore();
const userStore = useUserStore();
const model = reactive({ username: "admin", password: "admin" });
const router = useRouter();
const loading = ref(false);
@ -93,11 +95,19 @@ const onForgetPasswordClick = () => {
const onSubmitClick = async () => {
const errors = await formRef.value?.validate();
if (errors) return;
loading.value = true;
await new Promise((resolve) => setTimeout(resolve, 2000));
loading.value = false;
router.push({ path: "/" });
if (errors) {
return;
}
try {
loading.value = true;
const res = await api.auth.login(model);
userStore.setUser(res.data.data);
router.push({ path: "/" });
} catch {
console.log(1);
} finally {
loading.value = false;
}
};
</script>

View File

@ -52,7 +52,23 @@ const table = useTable({
],
},
],
common: {
search: {
items: [
{
extend: "name",
required: false,
},
],
},
create: {
title: "新建角色",
modalProps: {
width: 580,
maskClosable: false,
},
formProps: {
layout: "vertical",
},
items: [
{
field: "name",
@ -77,26 +93,7 @@ const table = useTable({
options: () => api.permission.getPermissions(),
},
],
modalProps: {
width: 580,
maskClosable: false,
},
formProps: {
layout: "vertical",
},
},
search: {
items: [
{
field: "name",
label: "角色名称",
type: "input",
required: false,
},
],
},
create: {
title: "新建角色",
submit: ({ model }) => {
return api.role.addRole(model as any);
},

View File

@ -10,142 +10,136 @@ import { Table, useTable } from "@/components";
import { dayjs } from "@/plugins";
import { Avatar, Button } from "@arco-design/web-vue";
const table = useTable(() => {
const nicknameRender = ({ record }: any) => {
return (
<div class="flex items-center">
<Avatar size={32}>
<img src={record.avatar} alt="" />
</Avatar>
<span class="ml-2 flex-1 flex flex-col overflow-hidden">
<span>{record.nickname}</span>
<span class="text-gray-400 text-xs truncate">账号{record.username}</span>
</span>
</div>
);
};
return {
data: async (model, paging) => {
return api.user.getUsers({ ...model, ...paging });
const table = useTable({
data: async (model, paging) => {
return api.user.getUsers({ ...model, ...paging });
},
columns: [
{
type: "index",
},
columns: [
{
type: "index",
{
title: "用户昵称",
dataIndex: "username",
width: 200,
render: ({ record }) => {
return (
<div class="flex items-center">
<Avatar size={32}>
<img src={record.avatar} alt="" />
</Avatar>
<span class="ml-2 flex-1 flex flex-col overflow-hidden">
<span>{record.nickname}</span>
<span class="text-gray-400 text-xs truncate">账号{record.username}</span>
</span>
</div>
);
},
{
title: "用户昵称",
dataIndex: "username",
width: 200,
render: nicknameRender,
},
{
title: "用户描述",
dataIndex: "description",
},
{
title: "用户邮箱",
dataIndex: "email",
},
{
title: "创建时间",
dataIndex: "createdAt",
width: 200,
render: ({ record }) => dayjs(record.createdAt).format(),
},
{
title: "操作",
type: "button",
width: 148,
buttons: [
{
type: "modify",
text: "修改",
},
{
title: "用户描述",
dataIndex: "description",
},
{
title: "用户邮箱",
dataIndex: "email",
},
{
title: "创建时间",
dataIndex: "createdAt",
width: 200,
render: ({ record }) => dayjs(record.createdAt).format(),
},
{
title: "操作",
type: "button",
width: 148,
buttons: [
{
type: "modify",
text: "修改",
},
{
type: "delete",
text: "删除",
onClick: async ({ record }) => {
return api.user.delUser(record.id);
},
{
type: "delete",
text: "删除",
onClick: async (data) => {
return api.user.deleteUser(data.record.id);
},
},
],
},
],
},
],
search: {
items: [
{
extend: "username",
required: false,
},
],
search: {
items: [
{
extend: "username",
required: false,
},
],
},
create: {
title: "新建用户",
trigger: () => (
<Button type="primary">
{{
icon: () => <i class="icon-park-outline-people-plus-one" />,
default: () => "添加",
}}
</Button>
),
modalProps: {
width: 772,
maskClosable: false,
},
create: {
title: "新建用户",
trigger: () => (
<Button type="primary">
{{
icon: () => <i class="icon-park-outline-people-plus-one" />,
default: () => "添加",
}}
</Button>
),
modalProps: {
width: 772,
maskClosable: false,
},
formProps: {
layout: "vertical",
class: "!grid grid-cols-2 gap-x-3",
},
model: {
avatarUrl: "",
},
items: [
{
field: "username",
label: "登录账号",
type: "input",
required: true,
},
{
field: "nickname",
label: "用户昵称",
type: "input",
},
{
field: "description",
label: "个人描述",
type: "input",
},
{
field: "password",
label: "密码",
type: "password",
},
{
label: "头像",
field: "avatar?avatarUrl",
type: "select",
},
{
field: "startTime:endTime",
label: "日期范围",
type: "dateRange",
nodeProps: {},
},
],
submit: ({ model }) => {
console.log(model);
},
formProps: {
layout: "vertical",
class: "!grid grid-cols-2 gap-x-3",
},
modify: {
extend: true,
title: "修改用户",
submit: ({ model }) => {
return api.user.updateUser(model.id, model);
items: [
{
field: "username",
label: "登录账号",
type: "input",
required: true,
},
{
field: "nickname",
label: "用户昵称",
type: "input",
},
{
field: "description",
label: "个人描述",
type: "input",
},
{
field: "password",
label: "密码",
type: "password",
},
{
label: "头像",
field: "avatar",
type: "select",
},
{
field: "startTime:endTime",
label: "日期范围",
type: "dateRange",
nodeProps: {},
},
],
submit: ({ model }) => {
console.log(model);
},
};
},
modify: {
extend: true,
title: "修改用户",
submit: ({ model }) => {
return api.user.updateUser(model.id, model);
},
},
});
</script>

View File

@ -0,0 +1,34 @@
import { NavigationGuardWithThis } from "vue-router";
import { store, useUserStore } from "@/store";
import { Message } from "@arco-design/web-vue";
const userStore = useUserStore(store);
/**
*
* @description 访
*/
export function useAuthGuard() {
const whitelist = ["/404"];
const signoutlist = ["/login"];
const authGuard: NavigationGuardWithThis<undefined> = async function (to, from, next) {
if (to.meta.auth === false) {
return next();
}
if (whitelist.includes(to.fullPath)) {
return next();
}
if (signoutlist.includes(to.fullPath)) {
if (userStore.id) {
Message.warning(`提示:您已登陆,如需重新请退出后再操作!`);
return next(false);
}
return next();
}
if (!userStore.id) {
return next("/login");
}
next();
};
return authGuard;
}

View File

@ -0,0 +1,17 @@
import { NavigationGuardWithThis } from "vue-router";
import { NProgress } from "@/plugins";
/**
*
* @description
*/
export const useNprogressGuard = () => {
const before: NavigationGuardWithThis<undefined> = function (to, from, next) {
NProgress.start();
next();
};
const after: NavigationGuardWithThis<undefined> = function (to, from, next) {
NProgress.done();
};
return { before, after };
};

View File

@ -0,0 +1,15 @@
import { NavigationGuardWithThis } from "vue-router";
/**
*
* @description
*/
export const useTitleGuard = () => {
const titleGuard: NavigationGuardWithThis<undefined> = function (to, from, next) {
const title = to.meta.title || import.meta.env.VITE_APP_TITLE;
const subtitle = import.meta.env.VITE_APP_SUBTITLE;
document.title = `${title} | ${subtitle}`;
next();
};
return titleGuard;
};

View File

@ -1,42 +0,0 @@
import { Router } from 'vue-router';
import { NProgress } from '@/plugins';
/**
*
* @param router
*/
export const useNprogress = (router: Router) => {
router.beforeEach((to, from, next) => {
NProgress.start();
next();
});
router.afterEach(() => {
NProgress.done();
});
};
/**
*
* @param router
*/
export const useTitle = (router: Router) => {
router.beforeEach((to, from, next) => {
const title = to.meta.title || import.meta.env.VITE_APP_TITLE;
const subtitle = import.meta.env.VITE_APP_SUBTITLE;
document.title = `${title} | ${subtitle}`;
next();
});
};
/**
*
* @param router
*/
export const useAuth = (router: Router) => {
router.beforeEach((to, from, next) => {
if (to.meta.auth) {
}
next();
});
};

View File

@ -1,6 +1,12 @@
import { createRouter, createWebHashHistory } from "vue-router";
import { useAuth, useNprogress, useTitle } from "../guards";
import { routes } from "../routes";
import { useAuthGuard } from "../guards/guard-auth";
import { useNprogressGuard } from "../guards/guard-nprogress";
import { useTitleGuard } from "../guards/guard-title";
const nprogressGuard = useNprogressGuard();
const titleGuard = useTitleGuard();
const authGuard = useAuthGuard();
const router = createRouter({
history: createWebHashHistory(),
@ -13,8 +19,9 @@ const router = createRouter({
],
});
useNprogress(router);
useTitle(router);
useAuth(router);
router.beforeEach(nprogressGuard.before);
router.beforeEach(nprogressGuard.after);
router.beforeEach(titleGuard);
router.beforeEach(authGuard);
export { router };

View File

@ -1,38 +1,35 @@
import { useDark } from "@vueuse/core";
import { defineStore } from "pinia";
export const useAppStore = defineStore({
id: "app",
state: () => {
const isDark = useDark({
onChanged: (isDark) => {
if (isDark) {
document.body.setAttribute("arco-theme", "dark");
document.body.classList.add("dark");
return;
}
document.body.setAttribute("arco-theme", "light");
document.body.classList.remove("dark");
},
});
return {
count: 0,
isDark,
title: import.meta.env.VITE_APP_TITLE,
subtitle: import.meta.env.VITE_APP_SUBTITLE,
};
},
getters: {
doubleCount(state) {
return state.count * 2;
},
},
state: () => ({
isDarkMode: false,
title: import.meta.env.VITE_TITLE,
subtitle: import.meta.env.VITE_SUBTITLE,
}),
actions: {
increment() {
this.count++;
},
/**
* /
*/
toggleDark() {
this.isDark = !this.isDark;
this.isDarkMode ? this.setLight() : this.setDark();
},
/**
*
*/
setLight() {
document.body.setAttribute("arco-theme", "light");
document.body.classList.remove("dark");
this.isDarkMode = false;
},
/**
*
*/
setDark() {
document.body.setAttribute("arco-theme", "dark");
document.body.classList.add("dark");
this.isDarkMode = true;
},
},
persist: true,
});

View File

@ -1,3 +1,5 @@
import { createPinia } from "pinia";
import persistedstatePlugin from "pinia-plugin-persistedstate";
export const store = createPinia();
store.use(persistedstatePlugin);

View File

@ -1,14 +1,18 @@
import { useStorage } from "@vueuse/core";
import { LoginedUserVo } from "@/api";
import { defineStore } from "pinia";
export const useUserStore = defineStore({
id: "user",
state: () => {
const user = useStorage("APP_USER", {
return {
/**
* ID
*/
id: 0,
/**
*
*/
name: "绝弹",
username: "绝弹",
/**
*
*/
@ -17,22 +21,35 @@ export const useUserStore = defineStore({
* JWT
*/
accessToken: "",
});
return user;
/**
*
*/
refreshToken: "",
};
},
actions: {
/**
*
*/
setToken(token: string) {
this.accessToken = token;
},
/**
*
*/
clearUser() {
this.name = "";
this.username = "";
this.avatar = "";
this.accessToken = "";
},
setUser(user: { name: string; avatar: string; accessToken: string }) {
this.name = user.name;
/**
*
*/
setUser(user: LoginedUserVo) {
this.username = user.username;
this.avatar = user.avatar;
this.accessToken = user.accessToken;
this.accessToken = user.token;
},
},
persist: true,
});

View File

@ -11,16 +11,23 @@ declare module '@vue/runtime-core' {
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
AButton: typeof import('@arco-design/web-vue')['Button']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
ADoption: typeof import('@arco-design/web-vue')['Doption']
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
AForm: typeof import('@arco-design/web-vue')['Form']
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
AInput: typeof import('@arco-design/web-vue')['Input']
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
ALayout: typeof import('@arco-design/web-vue')['Layout']
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
ALink: typeof import('@arco-design/web-vue')['Link']
AMenu: typeof import('@arco-design/web-vue')['Menu']
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
ASpace: typeof import('@arco-design/web-vue')['Space']
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
ATag: typeof import('@arco-design/web-vue')['Tag']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip']

10
src/types/env.d.ts vendored
View File

@ -4,15 +4,15 @@ interface ImportMetaEnv {
/**
*
*/
VITE_APP_TITLE: string;
VITE_TITLE: string;
/**
*
*/
VITE_APP_SUBTITLE: string;
VITE_SUBTITLE: string;
/**
*
*/
VITE_BUILD_EXTENSION: string;
VITE_EXTENSION: string;
/**
* API
*/
@ -20,11 +20,11 @@ interface ImportMetaEnv {
/**
*
*/
VITE_DEV_HOST: string;
VITE_HOST: string;
/**
*
*/
VITE_DEV_PORT: number;
VITE_PORT: number;
}
interface ImportMeta {

7
src/utils/message.ts Normal file
View File

@ -0,0 +1,7 @@
import { Message } from "@arco-design/web-vue";
export const message = {
warning() {
Message.warning(``)
}
}

View File

@ -17,8 +17,8 @@ import plugin from "./scripts/vite/plugin";
export default defineConfig(({ mode }) => {
// 加载顺序,后者优先级高:.env .env.locale .env.[mode] .env.[mode].locale
const env = loadEnv(mode, process.cwd());
const host = env.VITE_DEV_HOST || "0.0.0.0";
const port = Number(env.VITE_DEV_PORT || 3020);
const host = env.VITE_HOST || "0.0.0.0";
const port = Number(env.VITE_PORT || 3020);
return {
base: "./",
@ -39,7 +39,7 @@ export default defineConfig(({ mode }) => {
less: {
javascriptEnabled: true,
modifyVars: {
hack: `true; @import (reference) "${resolve("src/style/css-arco.less")}";`,
hack: `true; @import (reference) "${resolve("src/styles/css-arco.less")}";`,
arcoblue: "#66f",
},
},