mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-08 23:42:27 +08:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a469f1aca | ||
|
|
3bd0d28e05 | ||
|
|
447bdcc355 | ||
|
|
8bf849b803 | ||
|
|
02930e5208 | ||
|
|
ffca3ed61f | ||
|
|
0f92c953cb | ||
|
|
00247ee7b9 | ||
|
|
449761796c | ||
|
|
c96789f1ff | ||
|
|
301ca1a0df | ||
|
|
c5c28e958d | ||
|
|
2f97cbee06 | ||
|
|
764cb71f39 | ||
|
|
6d0aa46f20 | ||
|
|
f68ec16563 | ||
|
|
79c3cb5d4d | ||
|
|
7eb081ae87 | ||
|
|
cc2a911f2a | ||
|
|
b88c047643 | ||
|
|
01f9ba1046 | ||
|
|
f729a5b8ba | ||
|
|
7a62de39c2 | ||
|
|
1ae5372396 | ||
|
|
0bbc29023b | ||
|
|
84f55a8116 |
3
.env
3
.env
@@ -6,6 +6,3 @@ VITE_GLOB_APP_TITLE = AdminPro
|
|||||||
|
|
||||||
# spa shortname
|
# spa shortname
|
||||||
VITE_GLOB_APP_SHORT_NAME = AdminPro
|
VITE_GLOB_APP_SHORT_NAME = AdminPro
|
||||||
|
|
||||||
# 生产环境 开启mock
|
|
||||||
VITE_GLOB_PROD_MOCK = true
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ VITE_PORT = 8001
|
|||||||
# 网站根目录
|
# 网站根目录
|
||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
# 是否开启mock
|
# 是否开启 mock
|
||||||
VITE_USE_MOCK = true
|
VITE_USE_MOCK = true
|
||||||
|
|
||||||
# 网站前缀
|
# 是否开启控制台打印 mock 请求信息
|
||||||
VITE_BASE_URL = /
|
VITE_LOGGER_MOCK = true
|
||||||
|
|
||||||
# 是否删除console
|
# 是否删除console
|
||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
@@ -20,11 +20,11 @@ VITE_DROP_CONSOLE = true
|
|||||||
# API 接口地址
|
# API 接口地址
|
||||||
VITE_GLOB_API_URL =
|
VITE_GLOB_API_URL =
|
||||||
|
|
||||||
# 图片上传地址
|
|
||||||
VITE_GLOB_UPLOAD_URL=
|
|
||||||
|
|
||||||
# 图片前缀地址
|
|
||||||
VITE_GLOB_IMG_URL=
|
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀
|
||||||
VITE_GLOB_API_URL_PREFIX = /api
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
|
|
||||||
|
# 文件上传地址
|
||||||
|
VITE_GLOB_UPLOAD_URL=
|
||||||
|
|
||||||
|
# 文件前缀地址
|
||||||
|
VITE_GLOB_FILE_URL=
|
||||||
|
|||||||
@@ -4,24 +4,21 @@ VITE_USE_MOCK = true
|
|||||||
# 网站根目录
|
# 网站根目录
|
||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
# 网站前缀
|
|
||||||
VITE_BASE_URL = /
|
|
||||||
|
|
||||||
# 是否删除console
|
# 是否删除console
|
||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
|
|
||||||
# API
|
# API
|
||||||
VITE_GLOB_API_URL =
|
VITE_GLOB_API_URL =
|
||||||
|
|
||||||
|
# 接口前缀
|
||||||
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
|
|
||||||
# 图片上传地址
|
# 图片上传地址
|
||||||
VITE_GLOB_UPLOAD_URL=
|
VITE_GLOB_UPLOAD_URL=
|
||||||
|
|
||||||
# 图片前缀地址
|
# 图片前缀地址
|
||||||
VITE_GLOB_IMG_URL=
|
VITE_GLOB_IMG_URL=
|
||||||
|
|
||||||
# 接口前缀
|
|
||||||
VITE_GLOB_API_URL_PREFIX = /api
|
|
||||||
|
|
||||||
# 是否启用gzip压缩或brotli压缩
|
# 是否启用gzip压缩或brotli压缩
|
||||||
# 可选: gzip | brotli | none
|
# 可选: gzip | brotli | none
|
||||||
# 如果你需要多种形式,你可以用','来分隔
|
# 如果你需要多种形式,你可以用','来分隔
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ pnpm-debug.log*
|
|||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
!.vscode/extensions.json
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
|||||||
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"tu6ge.naive-ui-intelligence",
|
||||||
|
]
|
||||||
|
}
|
||||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,5 +1,26 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
- 优化 `登录页面` 排版
|
||||||
|
- 新增 `构建分包策略`
|
||||||
|
- 新增 `useLocalSetting` hook
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
- 新增 `alova` 请求库
|
||||||
|
- 新增 `@faker-js/faker` 可配合 `mock` 数据模拟
|
||||||
|
- 新增 `VITE_USE_MOCK` 环境变量-开启 `mock`
|
||||||
|
- 新增 `demo` 实例,新增/编辑角色
|
||||||
|
- 移除 `axios` 请求封装,使用 `alova` 代替
|
||||||
|
- 移除 `vite-plugin-mock` 使用 `@alova/mock` 代替
|
||||||
|
- 移除 `VITE_GLOB_PROD_MOCK` 环境变量
|
||||||
|
- 变更 `VITE_GLOB_IMG_URL` 环境变量变更成 `VITE_GLOB_FILE_URL`
|
||||||
|
- 优化 `BasicTable` 组件相关样式
|
||||||
|
- 优化 `TS` 类型定义
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
## 1.9.2
|
## 1.9.2
|
||||||
|
|
||||||
- 升级 `vite` 到 `5.x` 版本
|
- 升级 `vite` 到 `5.x` 版本
|
||||||
|
|||||||
69
README.md
69
README.md
@@ -1,10 +1,7 @@
|
|||||||
## 🚀 简介
|
## 🚀 简介
|
||||||
|
|
||||||
`Naive Ui Admin` 是一款 完全免费 且可商用的中后台解决方案,基于 🌟 `Vue3.0` 🌟、🚀 `Vite` 🚀、✨ [Naive UI](https://www.naiveui.com/) ✨ 和 🎉 `TypeScript` 🎉。
|
`Naive Ui Admin` 是一款 完全免费 且可商用的中后台解决方案,基于 🌟 `Vue3.x` 🌟、🚀 `Vite` 🚀、✨ [Naive UI](https://www.naiveui.com/) ✨ 和 🎉 `TypeScript` 🎉。
|
||||||
它融合了最新的前端技术栈,提炼了典型的业务模型和页面,包括二次封装组件、动态菜单、权限校验等功能,助力快速搭建企业级中后台项目。
|
它融合了最新的前端技术栈,提炼了典型的业务模型和页面,包括二次封装组件、动态菜单、权限校验等功能,助力快速搭建企业级中后台项目
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 🌈 特性
|
## 🌈 特性
|
||||||
📦 二次封装的实用高扩展性组件
|
📦 二次封装的实用高扩展性组件
|
||||||
@@ -14,49 +11,51 @@
|
|||||||
|
|
||||||
|
|
||||||
## 🎥 预览
|
## 🎥 预览
|
||||||
- [naive-ui-admin](https://jekip.github.io)
|
- [naive-ui-admin](https://gratis.naiveadmin.com)
|
||||||
|
|
||||||
账号:admin,密码:123456(随意)
|
账号:admin,密码:123456(随意)
|
||||||
|
|
||||||
## 💡 提示
|
|
||||||
|
|
||||||
如果您需要更多功能和组件,不妨尝试全新的 `NaiveAdmin`,它可能正是您寻找的解决方案
|
## 🚀 Naive Admin - 开箱即用的企业级前后端框架 `商业版本`
|
||||||
|
|
||||||
[NaiveAdmin 官网](https://www.naiveadmin.com)
|
> **✨ 多版本选择 · 四年持续迭代**
|
||||||
|
> 配套前后端支持 Java/Php 语言,支持单体和微服务多租户架构
|
||||||
|
> [详情→官网](https://www.naiveadmin.com) | [更新日志](https://www.yuque.com/u5825/zaqu0e)
|
||||||
|
|
||||||
[NaiveAdmin 变更日志](https://www.yuque.com/u5825/zaqu0e)
|
---
|
||||||
|
|
||||||
[为什么选我们?](https://www.naiveadmin.com/choose/we)
|
## 🔥 为什么选择 NaiveAdmin 商业版?
|
||||||
|
- **省时间**:内置丰富扩展组件与业务模板,不写一行样板代码即可开始业务开发
|
||||||
|
- **经实战**:已落地电网、跨境 ERP、SaaS 等 30+ 场景
|
||||||
|
- **可扩展**:插件式菜单 / 按钮 / 数据权限,新增业务模块「0 侵入」
|
||||||
|
|
||||||
### Plus
|
---
|
||||||
|
|
||||||
基于 `NaiveUi` 全新设计版本,增加了众多特性,值得一试
|
## 🖥️ 纯前端版本
|
||||||
|
|
||||||
[NaiveAdmin Plus 预览](https://plus.naiveadmin.com)
|
| 版本 | 技术栈 | 配套后端 | 预览地址 |
|
||||||
|
|-----|-------|---------|-------------|
|
||||||
|
| **🆕 Naive UI Max** | Vu3 + Ts + NaiveUI | 否 | [https://max.naiveadmin.com](https://max.naiveadmin.com) |
|
||||||
|
| **Naive UI Plus** | Vu3 + Ts + NaiveUI | 支持Java/PHP | [https://plus.naiveadmin.com](https://plus.naiveadmin.com) |
|
||||||
|
|
||||||
### Arco vue
|
## 🔌 前后端版本
|
||||||
|
|
||||||
智能设计体系,提供轻盈体验
|
| 版本 | 技术栈 | 预览地址 |
|
||||||
|
|------|------------------|--------------------------------------------------------------|
|
||||||
|
| **🆕Naive UI Max** | Vu3 + Ts + NaiveUI | [https://max-full.naiveadmin.com](https://max-full.naiveadmin.com) |
|
||||||
|
| **Naive UI Plus** | Vu3 + Ts + NaiveUI | [https://plus-full.naiveadmin.com](https://plus-full.naiveadmin.com) |
|
||||||
|
|
||||||
[NaiveAdmin Arco 预览](https://arco.naiveadmin.com)
|
## 🏢 多租户版本
|
||||||
|
|
||||||
### Element Plus
|
| 版本 | 技术栈 | 适用场景 | 预览地址 |
|
||||||
|
|-----------------------------|-----------------------------|----------------|-------------------------------------------|
|
||||||
|
| **Vue3** | Vu3 + Ts + NaiveUI + Java | 构建企业级 Saas 化系统 | [https://tenant.naiveadmin.com](https://tenant.naiveadmin.com) |
|
||||||
|
| **React** | React + Ts + Ant + Java | 构建企业级 Saas 化系统 | [https://compose.warden.vip](https://compose.warden.vip) |
|
||||||
|
|
||||||
面向设计师和开发者的组件库
|
|
||||||
|
|
||||||
[Element Plus Admin 预览](https://element.naiveadmin.com)
|
|
||||||
|
|
||||||
以上版本同时具备 `NaiveAdmin` 功能/组件/页面,一如既往、开箱即用,欢迎前往查看。
|
|
||||||
|
|
||||||
### Antd vue
|
|
||||||
|
|
||||||
新产品,如果您选的技术栈是 `Antd` 的话,不妨看看
|
|
||||||
|
|
||||||
[NaiveAdmin Antd 预览](https://antd.naiveadmin.com)
|
|
||||||
|
|
||||||
## 📚 文档
|
## 📚 文档
|
||||||
|
|
||||||
[文档地址](https://docs.naiveadmin.com)
|
[开源版本文档](https://docs.naiveadmin.com)
|
||||||
|
|
||||||
## 🛠 准备
|
## 🛠 准备
|
||||||
|
|
||||||
@@ -149,13 +148,13 @@ pnpm build
|
|||||||
|
|
||||||
## 💬 交流
|
## 💬 交流
|
||||||
|
|
||||||
有关 `Naive Ui Admin` 的使用或其他问题,欢迎加入我们的讨论群组或提出问题。
|
有关 `Naive Ui Admin` 的使用或其他问题,可以加入讨论群交流问题
|
||||||
|
|
||||||

|
QQ1群:328347666 (已满)
|
||||||
|
QQ2群:741353560
|
||||||
|
|
||||||
## 💖 赞助
|
## 💖 赞助
|
||||||
#### 如果您觉得这个项目对您有帮助,可以通过下面的链接为作者买一杯果汁,表示感谢!。
|
#### 如果项目有帮到你,不妨请作者喝一杯咖啡吧!
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|

|
||||||
[Paypal Me](https://www.paypal.com/paypalme/majunping)
|
[Paypal Me](https://www.paypal.com/paypalme/majunping)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Plugin,PluginOption } from 'vite';
|
import type { Plugin, PluginOption } from 'vite';
|
||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
|
||||||
@@ -6,11 +6,10 @@ import vue from '@vitejs/plugin-vue';
|
|||||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
|
|
||||||
import { configHtmlPlugin } from './html';
|
import { configHtmlPlugin } from './html';
|
||||||
import { configMockPlugin } from './mock';
|
|
||||||
import { configCompressPlugin } from './compress';
|
import { configCompressPlugin } from './compress';
|
||||||
|
|
||||||
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) {
|
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
||||||
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
|
const { VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
|
||||||
|
|
||||||
const vitePlugins: (Plugin | Plugin[] | PluginOption[])[] = [
|
const vitePlugins: (Plugin | Plugin[] | PluginOption[])[] = [
|
||||||
// have to
|
// have to
|
||||||
@@ -28,9 +27,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock)
|
|||||||
// vite-plugin-html
|
// vite-plugin-html
|
||||||
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
|
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
|
||||||
|
|
||||||
// vite-plugin-mock
|
|
||||||
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock));
|
|
||||||
|
|
||||||
if (isBuild) {
|
if (isBuild) {
|
||||||
// rollup-plugin-gzip
|
// rollup-plugin-gzip
|
||||||
vitePlugins.push(
|
vitePlugins.push(
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Mock plugin for development and production.
|
|
||||||
* https://github.com/anncwb/vite-plugin-mock
|
|
||||||
*/
|
|
||||||
import { viteMockServe } from 'vite-plugin-mock';
|
|
||||||
|
|
||||||
export function configMockPlugin(isBuild: boolean, prodMock: boolean) {
|
|
||||||
return viteMockServe({
|
|
||||||
ignore: /^\_/,
|
|
||||||
mockPath: 'mock',
|
|
||||||
localEnabled: !isBuild,
|
|
||||||
prodEnabled: isBuild && prodMock,
|
|
||||||
injectCode: `
|
|
||||||
import { setupProdMockServer } from '../mock/_createProductionServer';
|
|
||||||
|
|
||||||
setupProdMockServer();
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
|
||||||
|
|
||||||
interface IModuleType {
|
|
||||||
default: any[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const modules = import.meta.glob<IModuleType>('./**/*.ts', { eager: true });
|
|
||||||
|
|
||||||
const mockModules: any[] = [];
|
|
||||||
Object.keys(modules).forEach((key) => {
|
|
||||||
if (key.includes('/_')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mockModules.push(...modules[key].default);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in a production environment. Need to manually import all modules
|
|
||||||
*/
|
|
||||||
export function setupProdMockServer() {
|
|
||||||
createProdMockServer(mockModules);
|
|
||||||
}
|
|
||||||
@@ -1,44 +1,44 @@
|
|||||||
import { Random } from 'mockjs';
|
import { defineMock } from '@alova/mock';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
const consoleInfo = {
|
function getRandom(options) {
|
||||||
|
return Number(faker.commerce.price(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
//访问量
|
//访问量
|
||||||
visits: {
|
visits: {
|
||||||
dayVisits: Random.float(10000, 99999, 2, 2),
|
dayVisits: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
rise: Random.float(10, 99),
|
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
decline: Random.float(10, 99),
|
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
amount: Random.float(99999, 999999, 3, 5),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
},
|
},
|
||||||
//销售额
|
//销售额
|
||||||
saleroom: {
|
saleroom: {
|
||||||
weekSaleroom: Random.float(10000, 99999, 2, 2),
|
weekSaleroom: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
amount: Random.float(99999, 999999, 2, 2),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
degree: Random.float(10, 99),
|
degree: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
},
|
},
|
||||||
//订单量
|
//订单量
|
||||||
orderLarge: {
|
orderLarge: {
|
||||||
weekLarge: Random.float(10000, 99999, 2, 2),
|
weekLarge: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
rise: Random.float(10, 99),
|
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
decline: Random.float(10, 99),
|
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
amount: Random.float(99999, 999999, 2, 2),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
},
|
},
|
||||||
//成交额度
|
//成交额度
|
||||||
volume: {
|
volume: {
|
||||||
weekLarge: Random.float(10000, 99999, 2, 2),
|
weekLarge: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
rise: Random.float(10, 99),
|
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
decline: Random.float(10, 99),
|
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
amount: Random.float(99999, 999999, 2, 2),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
//主控台 卡片数据
|
// 主控台数据
|
||||||
{
|
'/api/dashboard/console': () => {
|
||||||
url: '/api/dashboard/console',
|
return resultSuccess(result);
|
||||||
timeout: 1000,
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return resultSuccess(consoleInfo);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { defineMock } from '@alova/mock';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
import type { ListDate } from '@/api/system/menu';
|
||||||
|
|
||||||
const menuList = () => {
|
const menuList = () => {
|
||||||
const result: any[] = [
|
const result: ListDate[] = [
|
||||||
{
|
{
|
||||||
label: 'Dashboard',
|
label: 'Dashboard',
|
||||||
key: 'dashboard',
|
key: 'dashboard',
|
||||||
@@ -74,16 +76,11 @@ const menuList = () => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'/api/menu/list': () => {
|
||||||
url: '/api/menu/list',
|
const list = menuList();
|
||||||
timeout: 1000,
|
return resultSuccess({
|
||||||
method: 'get',
|
list,
|
||||||
response: () => {
|
});
|
||||||
const list = menuList();
|
|
||||||
return resultSuccess({
|
|
||||||
list,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import { defineMock } from '@alova/mock';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
import { resultSuccess, doCustomTimes } from '../_util';
|
import { resultSuccess, doCustomTimes } from '../_util';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
function getMenuKeys() {
|
function getMenuKeys() {
|
||||||
const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
|
const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
|
||||||
const newKeys = [];
|
const newKeys = [];
|
||||||
doCustomTimes(parseInt(Math.random() * 6), () => {
|
doCustomTimes(parseInt(Math.random() * 6), () => {
|
||||||
const key = keys[Math.floor(Math.random() * keys.length)];
|
const key = keys[Math.floor(Math.random() * keys.length)];
|
||||||
newKeys.push(key);
|
newKeys.push(key as never);
|
||||||
});
|
});
|
||||||
return Array.from(new Set(newKeys));
|
return Array.from(new Set(newKeys));
|
||||||
}
|
}
|
||||||
@@ -14,32 +16,30 @@ const roleList = (pageSize) => {
|
|||||||
const result: any[] = [];
|
const result: any[] = [];
|
||||||
doCustomTimes(pageSize, () => {
|
doCustomTimes(pageSize, () => {
|
||||||
result.push({
|
result.push({
|
||||||
id: '@integer(10,100)',
|
id: faker.string.numeric(4),
|
||||||
name: '@cname()',
|
name: faker.person.firstName(),
|
||||||
explain: '@cname()',
|
explain: faker.lorem.sentence({ min: 2, max: 4 }),
|
||||||
isDefault: '@boolean()',
|
isDefault: faker.helpers.arrayElement([true, false]),
|
||||||
menu_keys: getMenuKeys(),
|
menu_keys: getMenuKeys(),
|
||||||
create_date: `@date('yyyy-MM-dd hh:mm:ss')`,
|
create_date: dayjs(faker.date.anytime()).format('YYYY-MM-DD HH:mm'),
|
||||||
'status|1': ['normal', 'enable', 'disable'],
|
status: faker.helpers.arrayElement(['normal', 'enable', 'disable']),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'/api/role/list': ({ query }) => {
|
||||||
url: '/api/role/list',
|
const { page = 1, pageSize = 10, name } = query;
|
||||||
timeout: 1000,
|
const list = roleList(Number(pageSize));
|
||||||
method: 'get',
|
// 并非真实,只是为了模拟搜索结果
|
||||||
response: ({ query }) => {
|
const count = name ? 30 : 60;
|
||||||
const { page = 1, pageSize = 10 } = query;
|
return resultSuccess({
|
||||||
const list = roleList(Number(pageSize));
|
page: Number(page),
|
||||||
return resultSuccess({
|
pageSize: Number(pageSize),
|
||||||
page: Number(page),
|
pageCount: count,
|
||||||
pageSize: Number(pageSize),
|
itemCount: count * Number(pageSize),
|
||||||
pageCount: 60,
|
list,
|
||||||
list,
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,43 +1,39 @@
|
|||||||
import { Random } from 'mockjs';
|
import { defineMock } from '@alova/mock';
|
||||||
import { resultSuccess, doCustomTimes } from '../_util';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { doCustomTimes, resultSuccess } from '../_util';
|
||||||
const tableList = (pageSize) => {
|
import dayjs from 'dayjs';
|
||||||
|
function tableList(pageSize: number) {
|
||||||
const result: any[] = [];
|
const result: any[] = [];
|
||||||
doCustomTimes(pageSize, () => {
|
doCustomTimes(pageSize, () => {
|
||||||
result.push({
|
result.push({
|
||||||
id: '@integer(10,999999)',
|
id: faker.string.numeric(4),
|
||||||
beginTime: '@datetime',
|
name: faker.person.firstName(),
|
||||||
endTime: '@datetime',
|
sex: faker.person.sexType(),
|
||||||
address: '@city()',
|
avatar: `https://picsum.photos/200/200?v=${faker.string.numeric(4)}`,
|
||||||
name: '@cname()',
|
email: faker.internet.email({ firstName: 'admin' }),
|
||||||
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
|
city: faker.location.city(),
|
||||||
date: `@date('yyyy-MM-dd')`,
|
status: faker.helpers.arrayElement(['close', 'refuse', 'pass']),
|
||||||
time: `@time('HH:mm')`,
|
type: faker.helpers.arrayElement(['person', 'company']),
|
||||||
'no|100000-10000000': 100000,
|
// createDate: faker.helpers.arrayElement(dateStrs),
|
||||||
'status|1': [true, false],
|
createDate: dayjs(faker.date.anytime()).format('YYYY-MM-DD HH:mm'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
//表格数据列表
|
// 表格数据列表
|
||||||
{
|
'/api/table/list': ({ query }) => {
|
||||||
url: '/api/table/list',
|
const { page = 1, pageSize = 10, name } = query;
|
||||||
timeout: 1000,
|
const list = tableList(Number(pageSize));
|
||||||
method: 'get',
|
// 并非真实,只是为了模拟搜索结果
|
||||||
response: ({ query }) => {
|
const count = name ? 30 : 60;
|
||||||
const { page = 1, pageSize = 10, name } = query;
|
return resultSuccess({
|
||||||
const list = tableList(Number(pageSize));
|
page: Number(page),
|
||||||
//并非真实,只是为了模拟搜索结果
|
pageSize: Number(pageSize),
|
||||||
const count = name ? 30 : 60;
|
pageCount: count,
|
||||||
return resultSuccess({
|
itemCount: count * Number(pageSize),
|
||||||
page: Number(page),
|
list,
|
||||||
pageSize: Number(pageSize),
|
});
|
||||||
pageCount: count,
|
|
||||||
itemCount: count * Number(pageSize),
|
|
||||||
list,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Mock from 'mockjs';
|
import Mock from 'mockjs';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
import { defineMock } from '@alova/mock';
|
||||||
|
|
||||||
const Random = Mock.Random;
|
const Random = Mock.Random;
|
||||||
|
|
||||||
@@ -37,23 +38,7 @@ const adminInfo = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'[POST]/api/login': () => resultSuccess({ token }),
|
||||||
url: '/api/login',
|
'/api/admin_info': () => resultSuccess(adminInfo),
|
||||||
timeout: 1000,
|
});
|
||||||
method: 'post',
|
|
||||||
response: () => {
|
|
||||||
return resultSuccess({ token });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/api/admin_info',
|
|
||||||
timeout: 1000,
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
// const token = getRequestToken(request);
|
|
||||||
// if (!token) return resultError('Invalid token');
|
|
||||||
return resultSuccess(adminInfo);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { defineMock } from '@alova/mock';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
const menusList = [
|
const menusList = [
|
||||||
@@ -40,13 +41,6 @@ const menusList = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'/api/menus': () => resultSuccess(menusList),
|
||||||
url: '/api/menus',
|
});
|
||||||
timeout: 1000,
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return resultSuccess(menusList);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
59
package.json
59
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "naive-ui-admin",
|
"name": "naive-ui-admin",
|
||||||
"version": "1.9.3",
|
"version": "2.1.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Ahjung",
|
"name": "Ahjung",
|
||||||
"email": "735878602@qq.com",
|
"email": "735878602@qq.com",
|
||||||
@@ -22,60 +22,60 @@
|
|||||||
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
|
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
|
||||||
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
||||||
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
|
|
||||||
"lint:pretty": "pretty-quick --staged"
|
"lint:pretty": "pretty-quick --staged"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@alova/mock": "^2.0.17",
|
||||||
"@vicons/antd": "^0.12.0",
|
"@vicons/antd": "^0.12.0",
|
||||||
"@vicons/ionicons5": "^0.12.0",
|
"@vicons/ionicons5": "^0.12.0",
|
||||||
"@vueup/vue-quill": "^1.2.0",
|
"@vueup/vue-quill": "^1.2.0",
|
||||||
"@vueuse/core": "^9.13.0",
|
"@vueuse/core": "^9.13.0",
|
||||||
"axios": "^1.7.2",
|
"alova": "^3.3.4",
|
||||||
"blueimp-md5": "^2.19.0",
|
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"echarts": "^5.5.1",
|
"dayjs": "^1.11.18",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
"element-resize-detector": "^1.2.4",
|
"element-resize-detector": "^1.2.4",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mitt": "^3.0.1",
|
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"naive-ui": "^2.38.2",
|
"naive-ui": "^2.43.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.3.1",
|
||||||
"qs": "^6.12.1",
|
"qs": "^6.14.0",
|
||||||
"vfonts": "^0.0.3",
|
"vue": "^3.5.21",
|
||||||
"vue": "^3.4.31",
|
"vue-router": "^4.5.1",
|
||||||
"vue-router": "^4.4.0",
|
|
||||||
"vue-types": "^4.2.1"
|
"vue-types": "^4.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.8.1",
|
"@commitlint/cli": "^17.8.1",
|
||||||
"@commitlint/config-conventional": "^17.8.1",
|
"@commitlint/config-conventional": "^17.8.1",
|
||||||
"@types/lodash": "^4.17.6",
|
"@faker-js/faker": "^9.9.0",
|
||||||
"@types/node": "^18.19.39",
|
"@types/lodash": "^4.17.20",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/node": "^18.19.126",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"@vitejs/plugin-vue": "^3.2.0",
|
"@vitejs/plugin-vue": "^3.2.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
||||||
"@vue/compiler-sfc": "^3.4.31",
|
"@vue/compiler-sfc": "^3.5.21",
|
||||||
"@vue/eslint-config-typescript": "^11.0.3",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.21",
|
||||||
"commitizen": "^4.3.0",
|
"commitizen": "^4.3.1",
|
||||||
"core-js": "^3.37.1",
|
"core-js": "^3.45.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.6.1",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-prettier": "^8.10.0",
|
"eslint-config-prettier": "^8.10.2",
|
||||||
"eslint-define-config": "1.12.0",
|
"eslint-define-config": "1.12.0",
|
||||||
"eslint-plugin-jest": "^27.9.0",
|
"eslint-plugin-jest": "^27.9.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.5",
|
||||||
"eslint-plugin-vue": "^9.26.0",
|
"eslint-plugin-vue": "^9.33.0",
|
||||||
"esno": "^0.16.3",
|
"esno": "^4.8.0",
|
||||||
"gh-pages": "^4.0.0",
|
"gh-pages": "^4.0.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"less": "^4.2.0",
|
"less": "^4.4.1",
|
||||||
"less-loader": "^11.1.4",
|
"less-loader": "^11.1.4",
|
||||||
"lint-staged": "^13.3.0",
|
"lint-staged": "^13.3.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"pretty-quick": "^3.3.1",
|
"pretty-quick": "^3.3.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
@@ -84,16 +84,15 @@
|
|||||||
"stylelint-config-standard": "^29.0.0",
|
"stylelint-config-standard": "^29.0.0",
|
||||||
"stylelint-order": "^5.0.0",
|
"stylelint-order": "^5.0.0",
|
||||||
"stylelint-scss": "^4.7.0",
|
"stylelint-scss": "^4.7.0",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"unplugin-vue-components": "^0.22.12",
|
"unplugin-vue-components": "^0.22.12",
|
||||||
"vite": "^5.3.2",
|
"vite": "^5.4.20",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-html": "^3.2.2",
|
"vite-plugin-html": "^3.2.2",
|
||||||
"vite-plugin-mock": "^2.9.8",
|
|
||||||
"vite-plugin-style-import": "^2.0.0",
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
"vue-demi": "^0.13.11",
|
"vue-demi": "^0.13.11",
|
||||||
"vue-draggable-next": "^2.2.1",
|
"vue-draggable-next": "^2.3.0",
|
||||||
"vue-eslint-parser": "^9.4.3",
|
"vue-eslint-parser": "^9.4.3",
|
||||||
"vuedraggable": "^4.1.0"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
4009
pnpm-lock.yaml
generated
4009
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,35 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
|
export interface TypeVisits {
|
||||||
|
dayVisits: number;
|
||||||
|
rise: number;
|
||||||
|
decline: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
export interface TypeSaleroom {
|
||||||
|
weekSaleroom: number;
|
||||||
|
amount: number;
|
||||||
|
degree: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypeOrderLarge {
|
||||||
|
weekLarge: number;
|
||||||
|
rise: number;
|
||||||
|
decline: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypeConsole {
|
||||||
|
visits: TypeVisits;
|
||||||
|
//销售额
|
||||||
|
saleroom: TypeSaleroom;
|
||||||
|
//订单量
|
||||||
|
orderLarge: TypeOrderLarge;
|
||||||
|
//成交额度
|
||||||
|
volume: TypeOrderLarge;
|
||||||
|
}
|
||||||
|
|
||||||
//获取主控台信息
|
//获取主控台信息
|
||||||
export function getConsoleInfo() {
|
export function getConsoleInfo() {
|
||||||
return http.request({
|
return Alova.Get<TypeConsole>('/dashboard/console');
|
||||||
url: '/dashboard/console',
|
|
||||||
method: 'get',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
export interface ListDate {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
type: number;
|
||||||
|
subtitle: string;
|
||||||
|
openType: number;
|
||||||
|
auth: string;
|
||||||
|
path: string;
|
||||||
|
children?: ListDate[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 根据用户id获取用户菜单
|
* @description: 根据用户id获取用户菜单
|
||||||
*/
|
*/
|
||||||
export function adminMenus() {
|
export function adminMenus() {
|
||||||
return http.request({
|
return Alova.Get('/menus');
|
||||||
url: '/menus',
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,9 +22,7 @@ export function adminMenus() {
|
|||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export function getMenuList(params?) {
|
export function getMenuList(params?) {
|
||||||
return http.request({
|
return Alova.Get<{ list: ListDate[] }>('/menu/list', {
|
||||||
url: '/menu/list',
|
|
||||||
method: 'GET',
|
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 角色列表
|
* @description: 角色列表
|
||||||
*/
|
*/
|
||||||
export function getRoleList() {
|
export function getRoleList(params) {
|
||||||
return http.request({
|
return Alova.Get('/role/list', { params });
|
||||||
url: '/role/list',
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
export interface BasicResponseModel<T = any> {
|
|
||||||
code: number;
|
|
||||||
message: string;
|
|
||||||
result: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BasicPageParams {
|
|
||||||
pageNumber: number;
|
|
||||||
pageSize: number;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 获取用户信息
|
* @description: 获取用户信息
|
||||||
*/
|
*/
|
||||||
export function getUserInfo() {
|
export function getUserInfo() {
|
||||||
return http.request({
|
return Alova.Get<InResult>('/admin_info', {
|
||||||
url: '/admin_info',
|
meta: {
|
||||||
method: 'get',
|
isReturnNativeResponse: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,14 +15,15 @@ export function getUserInfo() {
|
|||||||
* @description: 用户登录
|
* @description: 用户登录
|
||||||
*/
|
*/
|
||||||
export function login(params) {
|
export function login(params) {
|
||||||
return http.request<BasicResponseModel>(
|
return Alova.Post<InResult>(
|
||||||
|
'/login',
|
||||||
{
|
{
|
||||||
url: '/login',
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isTransformResponse: false,
|
meta: {
|
||||||
|
isReturnNativeResponse: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -42,25 +32,14 @@ export function login(params) {
|
|||||||
* @description: 用户修改密码
|
* @description: 用户修改密码
|
||||||
*/
|
*/
|
||||||
export function changePassword(params, uid) {
|
export function changePassword(params, uid) {
|
||||||
return http.request(
|
return Alova.Post(`/user/u${uid}/changepw`, { params });
|
||||||
{
|
|
||||||
url: `/user/u${uid}/changepw`,
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isTransformResponse: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 用户登出
|
* @description: 用户登出
|
||||||
*/
|
*/
|
||||||
export function logout(params) {
|
export function logout(params) {
|
||||||
return http.request({
|
return Alova.Post('/login/logout', {
|
||||||
url: '/login/logout',
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
//获取table
|
//获取table
|
||||||
export function getTableList(params) {
|
export function getTableList(params) {
|
||||||
return http.request({
|
return Alova.Get('/table/list', { params });
|
||||||
url: '/table/list',
|
|
||||||
method: 'get',
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
{{ value }}
|
{{ value }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
|
import { ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
|
||||||
import { useTransition, TransitionPresets } from '@vueuse/core';
|
import { useTransition, TransitionPresets } from '@vueuse/core';
|
||||||
import { isNumber } from '@/utils/is';
|
import { isNumber } from '@/utils/is';
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CountTo' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
startVal: { type: Number, default: 0 },
|
startVal: { type: Number, default: 0 },
|
||||||
endVal: { type: Number, default: 2021 },
|
endVal: { type: Number, default: 2021 },
|
||||||
duration: { type: Number, default: 1500 },
|
duration: { type: Number, default: 1500 },
|
||||||
@@ -36,75 +38,72 @@
|
|||||||
* Digital animation
|
* Digital animation
|
||||||
*/
|
*/
|
||||||
transition: { type: String, default: 'linear' },
|
transition: { type: String, default: 'linear' },
|
||||||
};
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits(['onStarted', 'onFinished']);
|
||||||
name: 'CountTo',
|
|
||||||
props,
|
|
||||||
emits: ['onStarted', 'onFinished'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const source = ref(props.startVal);
|
|
||||||
const disabled = ref(false);
|
|
||||||
let outputValue = useTransition(source);
|
|
||||||
|
|
||||||
const value = computed(() => formatNumber(unref(outputValue)));
|
const source = ref(props.startVal);
|
||||||
|
const disabled = ref(false);
|
||||||
|
let outputValue = useTransition(source);
|
||||||
|
|
||||||
watchEffect(() => {
|
const value = computed(() => formatNumber(unref(outputValue)));
|
||||||
source.value = props.startVal;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch([() => props.startVal, () => props.endVal], () => {
|
watchEffect(() => {
|
||||||
if (props.autoplay) {
|
source.value = props.startVal;
|
||||||
start();
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
watch([() => props.startVal, () => props.endVal], () => {
|
||||||
props.autoplay && start();
|
if (props.autoplay) {
|
||||||
});
|
start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function start() {
|
onMounted(() => {
|
||||||
run();
|
props.autoplay && start();
|
||||||
source.value = props.endVal;
|
});
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
run();
|
||||||
|
source.value = props.endVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
source.value = props.startVal;
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
outputValue = useTransition(source, {
|
||||||
|
disabled,
|
||||||
|
duration: props.duration,
|
||||||
|
onFinished: () => emit('onFinished'),
|
||||||
|
onStarted: () => emit('onStarted'),
|
||||||
|
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(num: number | string) {
|
||||||
|
if (!num && num !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const { decimals, decimal, separator, suffix, prefix } = props;
|
||||||
|
num = Number(num).toFixed(decimals);
|
||||||
|
num += '';
|
||||||
|
|
||||||
|
const x = num.split('.');
|
||||||
|
let x1 = x[0];
|
||||||
|
const x2 = x.length > 1 ? decimal + x[1] : '';
|
||||||
|
|
||||||
|
const rgx = /(\d+)(\d{3})/;
|
||||||
|
if (separator && !isNumber(separator)) {
|
||||||
|
while (rgx.test(x1)) {
|
||||||
|
x1 = x1.replace(rgx, '$1' + separator + '$2');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return prefix + x1 + x2 + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
function reset() {
|
defineExpose({
|
||||||
source.value = props.startVal;
|
reset,
|
||||||
run();
|
|
||||||
}
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
outputValue = useTransition(source, {
|
|
||||||
disabled,
|
|
||||||
duration: props.duration,
|
|
||||||
onFinished: () => emit('onFinished'),
|
|
||||||
onStarted: () => emit('onStarted'),
|
|
||||||
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatNumber(num: number | string) {
|
|
||||||
if (!num) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const { decimals, decimal, separator, suffix, prefix } = props;
|
|
||||||
num = Number(num).toFixed(decimals);
|
|
||||||
num += '';
|
|
||||||
|
|
||||||
const x = num.split('.');
|
|
||||||
let x1 = x[0];
|
|
||||||
const x2 = x.length > 1 ? decimal + x[1] : '';
|
|
||||||
|
|
||||||
const rgx = /(\d+)(\d{3})/;
|
|
||||||
if (separator && !isNumber(separator)) {
|
|
||||||
while (rgx.test(x1)) {
|
|
||||||
x1 = x1.replace(rgx, '$1' + separator + '$2');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prefix + x1 + x2 + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { value, start, reset };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,23 +2,23 @@
|
|||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<!--顶部左侧区域-->
|
<!--顶部左侧区域-->
|
||||||
<div class="flex items-center table-toolbar-left">
|
<div class="flex items-center table-toolbar-left">
|
||||||
<template v-if="title">
|
<template v-if="props.title">
|
||||||
<div class="table-toolbar-left-title">
|
<div class="table-toolbar-left-title">
|
||||||
{{ title }}
|
{{ props.title }}
|
||||||
<n-tooltip trigger="hover" v-if="titleTooltip">
|
<n-tooltip trigger="hover" v-if="props.titleTooltip">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-icon size="18" class="ml-1 text-gray-400 cursor-pointer">
|
<n-icon size="18" class="ml-1 text-gray-400 cursor-pointer">
|
||||||
<QuestionCircleOutlined />
|
<QuestionCircleOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
{{ titleTooltip }}
|
{{ props.titleTooltip }}
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<slot name="tableTitle"></slot>
|
<slot name="tableTitle"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center table-toolbar-right">
|
<div class="flex items-center leading-none table-toolbar-right">
|
||||||
<!--顶部右侧区域-->
|
<!--顶部右侧区域-->
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"></slot>
|
||||||
|
|
||||||
@@ -84,18 +84,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import {
|
import { ref, unref, toRaw, computed, onMounted, nextTick } from 'vue';
|
||||||
ref,
|
|
||||||
defineComponent,
|
|
||||||
reactive,
|
|
||||||
unref,
|
|
||||||
toRaw,
|
|
||||||
computed,
|
|
||||||
toRefs,
|
|
||||||
onMounted,
|
|
||||||
nextTick,
|
|
||||||
} from 'vue';
|
|
||||||
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||||
import { createTableContext } from './hooks/useTableContext';
|
import { createTableContext } from './hooks/useTableContext';
|
||||||
|
|
||||||
@@ -132,181 +122,150 @@
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits([
|
||||||
components: {
|
'fetch-success',
|
||||||
ReloadOutlined,
|
'fetch-error',
|
||||||
ColumnHeightOutlined,
|
'update:checked-row-keys',
|
||||||
ColumnSetting,
|
'edit-end',
|
||||||
QuestionCircleOutlined,
|
'edit-cancel',
|
||||||
},
|
'edit-row-end',
|
||||||
props: {
|
'edit-change',
|
||||||
...basicProps,
|
]);
|
||||||
},
|
|
||||||
emits: [
|
|
||||||
'fetch-success',
|
|
||||||
'fetch-error',
|
|
||||||
'update:checked-row-keys',
|
|
||||||
'edit-end',
|
|
||||||
'edit-cancel',
|
|
||||||
'edit-row-end',
|
|
||||||
'edit-change',
|
|
||||||
],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const deviceHeight = ref(150);
|
|
||||||
const tableElRef = ref<ComponentRef>(null);
|
|
||||||
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
|
||||||
let paginationEl: HTMLElement | null;
|
|
||||||
const isStriped = ref(props.striped || false);
|
|
||||||
const tableData = ref<Recordable[]>([]);
|
|
||||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
|
||||||
|
|
||||||
const getProps = computed(() => {
|
const props = defineProps({ ...basicProps });
|
||||||
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
|
const deviceHeight = ref(150);
|
||||||
});
|
const tableElRef = ref<ComponentRef>(null);
|
||||||
|
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
||||||
|
let paginationEl: HTMLElement | null;
|
||||||
|
const isStriped = ref(props.striped || false);
|
||||||
|
const tableData = ref<Recordable[]>([]);
|
||||||
|
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||||
|
|
||||||
const { getLoading, setLoading } = useLoading(getProps);
|
const getProps = computed(() => {
|
||||||
|
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
|
||||||
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
|
||||||
|
|
||||||
const { getDataSourceRef, getDataSource, getRowKey, reload } = useDataSource(
|
|
||||||
getProps,
|
|
||||||
{
|
|
||||||
getPaginationInfo,
|
|
||||||
setPagination,
|
|
||||||
tableData,
|
|
||||||
setLoading,
|
|
||||||
},
|
|
||||||
emit
|
|
||||||
);
|
|
||||||
|
|
||||||
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
|
|
||||||
useColumns(getProps);
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
tableSize: unref(getProps as any).size || 'medium',
|
|
||||||
isColumnSetting: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
//页码切换
|
|
||||||
function updatePage(page) {
|
|
||||||
setPagination({ page: page });
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
//分页数量切换
|
|
||||||
function updatePageSize(size) {
|
|
||||||
setPagination({ page: 1, pageSize: size });
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
//密度切换
|
|
||||||
function densitySelect(e) {
|
|
||||||
state.tableSize = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
//选中行
|
|
||||||
function updateCheckedRowKeys(rowKeys) {
|
|
||||||
emit('update:checked-row-keys', rowKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
//获取表格大小
|
|
||||||
const getTableSize = computed(() => state.tableSize);
|
|
||||||
|
|
||||||
//组装表格信息
|
|
||||||
const getBindValues = computed(() => {
|
|
||||||
const tableData = unref(getDataSourceRef);
|
|
||||||
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
|
||||||
return {
|
|
||||||
...unref(getProps),
|
|
||||||
loading: unref(getLoading),
|
|
||||||
columns: toRaw(unref(getPageColumns)),
|
|
||||||
rowKey: unref(getRowKey),
|
|
||||||
data: tableData,
|
|
||||||
size: unref(getTableSize),
|
|
||||||
remote: true,
|
|
||||||
'max-height': maxHeight,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
//获取分页信息
|
|
||||||
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
|
|
||||||
|
|
||||||
function setProps(props: Partial<BasicTableProps>) {
|
|
||||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
|
||||||
}
|
|
||||||
|
|
||||||
const setStriped = (value: boolean) => (isStriped.value = value);
|
|
||||||
|
|
||||||
const tableAction = {
|
|
||||||
reload,
|
|
||||||
setColumns,
|
|
||||||
setLoading,
|
|
||||||
setProps,
|
|
||||||
getColumns,
|
|
||||||
getPageColumns,
|
|
||||||
getCacheColumns,
|
|
||||||
setCacheColumnsField,
|
|
||||||
emit,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCanResize = computed(() => {
|
|
||||||
const { canResize } = unref(getProps);
|
|
||||||
return canResize;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function computeTableHeight() {
|
|
||||||
const table = unref(tableElRef);
|
|
||||||
if (!table) return;
|
|
||||||
if (!unref(getCanResize)) return;
|
|
||||||
const tableEl: any = table?.$el;
|
|
||||||
const headEl = tableEl.querySelector('.n-data-table-thead ');
|
|
||||||
const { bottomIncludeBody } = getViewportOffset(headEl);
|
|
||||||
const headerH = 64;
|
|
||||||
let paginationH = 2;
|
|
||||||
let marginH = 24;
|
|
||||||
if (!isBoolean(unref(pagination))) {
|
|
||||||
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
|
||||||
if (paginationEl) {
|
|
||||||
const offsetHeight = paginationEl.offsetHeight;
|
|
||||||
paginationH += offsetHeight || 0;
|
|
||||||
} else {
|
|
||||||
paginationH += 28;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let height =
|
|
||||||
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
|
||||||
const maxHeight = props.maxHeight;
|
|
||||||
height = maxHeight && maxHeight < height ? maxHeight : height;
|
|
||||||
deviceHeight.value = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
useWindowSizeFn(computeTableHeight, 280);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
computeTableHeight();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
tableElRef,
|
|
||||||
getBindValues,
|
|
||||||
getDataSource,
|
|
||||||
densityOptions,
|
|
||||||
reload,
|
|
||||||
densitySelect,
|
|
||||||
updatePage,
|
|
||||||
updatePageSize,
|
|
||||||
pagination,
|
|
||||||
tableAction,
|
|
||||||
setStriped,
|
|
||||||
isStriped,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tableSize = ref(unref(getProps as any).size || 'medium');
|
||||||
|
|
||||||
|
const { getLoading, setLoading } = useLoading(getProps);
|
||||||
|
|
||||||
|
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
||||||
|
|
||||||
|
const { getDataSourceRef, getDataSource, getRowKey, reload } = useDataSource(
|
||||||
|
getProps,
|
||||||
|
{
|
||||||
|
getPaginationInfo,
|
||||||
|
setPagination,
|
||||||
|
tableData,
|
||||||
|
setLoading,
|
||||||
|
},
|
||||||
|
emit
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
|
||||||
|
useColumns(getProps);
|
||||||
|
|
||||||
|
//页码切换
|
||||||
|
function updatePage(page) {
|
||||||
|
setPagination({ page: page });
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
//分页数量切换
|
||||||
|
function updatePageSize(size) {
|
||||||
|
setPagination({ page: 1, pageSize: size });
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
//密度切换
|
||||||
|
function densitySelect(e) {
|
||||||
|
tableSize.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取表格大小
|
||||||
|
const getTableSize = computed(() => tableSize.value);
|
||||||
|
|
||||||
|
//组装表格信息
|
||||||
|
const getBindValues = computed(() => {
|
||||||
|
const tableData = unref(getDataSourceRef);
|
||||||
|
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
||||||
|
return {
|
||||||
|
...unref(getProps),
|
||||||
|
loading: unref(getLoading),
|
||||||
|
columns: toRaw(unref(getPageColumns)),
|
||||||
|
rowKey: unref(getRowKey),
|
||||||
|
data: tableData,
|
||||||
|
size: unref(getTableSize),
|
||||||
|
remote: true,
|
||||||
|
'max-height': maxHeight,
|
||||||
|
title: '', // 重置为空 避免绑定到 table 上面
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//获取分页信息
|
||||||
|
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
|
||||||
|
|
||||||
|
function setProps(props: Partial<BasicTableProps>) {
|
||||||
|
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||||
|
}
|
||||||
|
|
||||||
|
const setStriped = (value: boolean) => (isStriped.value = value);
|
||||||
|
|
||||||
|
const tableAction = {
|
||||||
|
reload,
|
||||||
|
setColumns,
|
||||||
|
setLoading,
|
||||||
|
setProps,
|
||||||
|
getColumns,
|
||||||
|
getDataSource,
|
||||||
|
getPageColumns,
|
||||||
|
getCacheColumns,
|
||||||
|
setCacheColumnsField,
|
||||||
|
emit,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCanResize = computed(() => {
|
||||||
|
const { canResize } = unref(getProps);
|
||||||
|
return canResize;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function computeTableHeight() {
|
||||||
|
const table = unref(tableElRef);
|
||||||
|
if (!table) return;
|
||||||
|
if (!unref(getCanResize)) return;
|
||||||
|
const tableEl: any = table?.$el;
|
||||||
|
const headEl = tableEl.querySelector('.n-data-table-thead ');
|
||||||
|
const { bottomIncludeBody } = getViewportOffset(headEl);
|
||||||
|
const headerH = 64;
|
||||||
|
let paginationH = 2;
|
||||||
|
let marginH = 24;
|
||||||
|
if (!isBoolean(unref(pagination))) {
|
||||||
|
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
||||||
|
if (paginationEl) {
|
||||||
|
const offsetHeight = paginationEl.offsetHeight;
|
||||||
|
paginationH += offsetHeight || 0;
|
||||||
|
} else {
|
||||||
|
paginationH += 28;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let height =
|
||||||
|
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
||||||
|
const maxHeight = props.maxHeight;
|
||||||
|
height = maxHeight && maxHeight < height ? maxHeight : height;
|
||||||
|
deviceHeight.value = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
useWindowSizeFn(computeTableHeight, 280);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
computeTableHeight();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||||
|
|
||||||
|
defineExpose(tableAction);
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.table-toolbar {
|
.table-toolbar {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="editable-cell-action" v-if="!getRowEditable">
|
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||||
<n-icon class="mx-2 cursor-pointer">
|
<n-icon class="mx-2 cursor-pointer" title="保存">
|
||||||
<CheckOutlined @click="handleSubmit" />
|
<CheckOutlined @click="handleSubmit" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-icon class="mx-2 cursor-pointer">
|
<n-icon class="mx-2 cursor-pointer" title="取消">
|
||||||
<CloseOutlined @click="handleCancel" />
|
<CloseOutlined @click="handleCancel" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
const title: any = column.title;
|
const title: any = column.title;
|
||||||
column.title = () => {
|
column.title = () => {
|
||||||
return renderTooltip(
|
return renderTooltip(
|
||||||
h('span', {}, [
|
h('div', { class: 'flex items-center' }, [
|
||||||
h('span', { style: { 'margin-right': '5px' } }, title),
|
h('span', { style: { 'margin-right': '5px' } }, title),
|
||||||
h(
|
h(
|
||||||
NIcon,
|
NIcon,
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ export const websiteConfig = Object.freeze({
|
|||||||
title: 'NaiveUiAdmin',
|
title: 'NaiveUiAdmin',
|
||||||
logo: logoImage,
|
logo: logoImage,
|
||||||
loginImage: loginImage,
|
loginImage: loginImage,
|
||||||
loginDesc: 'Naive Ui Admin中后台前端/设计解决方案',
|
loginDesc: 'Naive Ui Admin 中后台前端/设计解决方案',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { GlobConfig } from '/#/config';
|
import type { GlobConfig, LocalConfig } from '/#/config';
|
||||||
|
|
||||||
import { warn } from '@/utils/log';
|
|
||||||
import { getAppEnvConfig } from '@/utils/env';
|
import { getAppEnvConfig } from '@/utils/env';
|
||||||
|
import { warn } from '@/utils/log';
|
||||||
|
|
||||||
|
// 这里的 useGlobSetting 用于获取全局配置,以下环境变量 带 VITE_GLOB_开头 会打包到 app.config 中去
|
||||||
export const useGlobSetting = (): Readonly<GlobConfig> => {
|
export const useGlobSetting = (): Readonly<GlobConfig> => {
|
||||||
const {
|
const {
|
||||||
VITE_GLOB_APP_TITLE,
|
VITE_GLOB_APP_TITLE,
|
||||||
@@ -10,8 +11,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||||||
VITE_GLOB_APP_SHORT_NAME,
|
VITE_GLOB_APP_SHORT_NAME,
|
||||||
VITE_GLOB_API_URL_PREFIX,
|
VITE_GLOB_API_URL_PREFIX,
|
||||||
VITE_GLOB_UPLOAD_URL,
|
VITE_GLOB_UPLOAD_URL,
|
||||||
VITE_GLOB_PROD_MOCK,
|
VITE_GLOB_FILE_URL,
|
||||||
VITE_GLOB_IMG_URL,
|
|
||||||
} = getAppEnvConfig();
|
} = getAppEnvConfig();
|
||||||
|
|
||||||
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
||||||
@@ -21,14 +21,25 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Take global configuration
|
// Take global configuration
|
||||||
const glob: Readonly<GlobConfig> = {
|
return {
|
||||||
title: VITE_GLOB_APP_TITLE,
|
title: VITE_GLOB_APP_TITLE,
|
||||||
apiUrl: VITE_GLOB_API_URL,
|
apiUrl: VITE_GLOB_API_URL,
|
||||||
shortName: VITE_GLOB_APP_SHORT_NAME,
|
shortName: VITE_GLOB_APP_SHORT_NAME,
|
||||||
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
||||||
uploadUrl: VITE_GLOB_UPLOAD_URL,
|
uploadUrl: VITE_GLOB_UPLOAD_URL,
|
||||||
prodMock: VITE_GLOB_PROD_MOCK,
|
fileUrl: VITE_GLOB_FILE_URL,
|
||||||
imgUrl: VITE_GLOB_IMG_URL,
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 这里的 useLocalSetting 用于获取本地配置,以下环境变量不会打包到 app.config 中去
|
||||||
|
export const useLocalSetting = (): Readonly<LocalConfig> => {
|
||||||
|
const { VITE_USE_MOCK, VITE_LOGGER_MOCK } = import.meta.env;
|
||||||
|
|
||||||
|
function strToBoolean(val): boolean {
|
||||||
|
return val === 'true';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
useMock: strToBoolean(VITE_USE_MOCK),
|
||||||
|
loggerMock: strToBoolean(VITE_LOGGER_MOCK),
|
||||||
};
|
};
|
||||||
return glob as Readonly<GlobConfig>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<n-breadcrumb v-if="crumbsSetting.show">
|
<n-breadcrumb v-if="crumbsSetting.show">
|
||||||
<template
|
<template
|
||||||
v-for="routeItem in breadcrumbList"
|
v-for="routeItem in breadcrumbList"
|
||||||
:key="routeItem.name === 'Redirect' ? void 0 : routeItem.name"
|
:key="routeItem.name === RedirectName ? void 0 : routeItem.name"
|
||||||
>
|
>
|
||||||
<n-breadcrumb-item v-if="routeItem.meta.title">
|
<n-breadcrumb-item v-if="routeItem.meta.title">
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
@@ -101,8 +101,7 @@
|
|||||||
<div class="layout-header-trigger layout-header-trigger-min">
|
<div class="layout-header-trigger layout-header-trigger-min">
|
||||||
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<n-avatar round :src="websiteConfig.logo">
|
<n-avatar :src="websiteConfig.logo">
|
||||||
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<UserOutlined />
|
<UserOutlined />
|
||||||
</template>
|
</template>
|
||||||
@@ -130,17 +129,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue';
|
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import components from './components';
|
|
||||||
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
|
|
||||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
|
||||||
import { useUserStore } from '@/store/modules/user';
|
|
||||||
import { useScreenLockStore } from '@/store/modules/screenLock';
|
|
||||||
import ProjectSetting from './ProjectSetting.vue';
|
|
||||||
import { AsideMenu } from '@/layout/components/Menu';
|
|
||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
|
||||||
import { websiteConfig } from '@/config/website.config';
|
import { websiteConfig } from '@/config/website.config';
|
||||||
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
|
import { AsideMenu } from '@/layout/components/Menu';
|
||||||
|
import { RedirectName } from '@/router/constant';
|
||||||
|
import { useScreenLockStore } from '@/store/modules/screenLock';
|
||||||
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||||
|
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
|
||||||
|
import { computed, defineComponent, reactive, ref, toRefs, unref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import components from './components';
|
||||||
|
import ProjectSetting from './ProjectSetting.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PageHeader',
|
name: 'PageHeader',
|
||||||
@@ -347,6 +347,7 @@
|
|||||||
mixMenu,
|
mixMenu,
|
||||||
websiteConfig,
|
websiteConfig,
|
||||||
handleMenuCollapsed,
|
handleMenuCollapsed,
|
||||||
|
RedirectName,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const RedirectRoute: RouteRecordRaw = {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/redirect/:path(.*)',
|
path: '/redirect/:path(.*)',
|
||||||
name: RedirectName,
|
name: `${RedirectName}Son`,
|
||||||
component: () => import('@/views/redirect/index.vue'),
|
component: () => import('@/views/redirect/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: RedirectName,
|
title: RedirectName,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
|
||||||
import { isNavigationFailure, Router } from 'vue-router';
|
|
||||||
import { useUser } from '@/store/modules/user';
|
|
||||||
import { useAsyncRoute } from '@/store/modules/asyncRoute';
|
|
||||||
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
|
||||||
import { storage } from '@/utils/Storage';
|
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
import { ErrorPageRoute } from '@/router/base';
|
import { ErrorPageRoute } from '@/router/base';
|
||||||
|
import { useAsyncRoute } from '@/store/modules/asyncRoute';
|
||||||
|
import { useUser } from '@/store/modules/user';
|
||||||
|
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
||||||
|
import { storage } from '@/utils/Storage';
|
||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { isNavigationFailure, Router } from 'vue-router';
|
||||||
|
import { RedirectName } from './constant';
|
||||||
|
|
||||||
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
||||||
|
|
||||||
@@ -91,7 +92,7 @@ export function createRouterGuards(router: Router) {
|
|||||||
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
|
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
|
||||||
// 需要缓存的组件
|
// 需要缓存的组件
|
||||||
keepAliveComponents.push(currentComName);
|
keepAliveComponents.push(currentComName);
|
||||||
} else if (!to.meta?.keepAlive || to.name == 'Redirect') {
|
} else if (!to.meta?.keepAlive || to.name == RedirectName) {
|
||||||
// 不需要缓存的组件
|
// 不需要缓存的组件
|
||||||
const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
|
const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
|
|||||||
@@ -5,17 +5,6 @@ import { renderIcon, renderNew } from '@/utils';
|
|||||||
|
|
||||||
const routeName = 'comp';
|
const routeName = 'comp';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/comp',
|
path: '/comp',
|
||||||
|
|||||||
@@ -5,16 +5,6 @@ import { renderIcon } from '@/utils/index';
|
|||||||
|
|
||||||
const routeName = 'dashboard';
|
const routeName = 'dashboard';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { ExclamationCircleOutlined } from '@vicons/antd';
|
import { ExclamationCircleOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/exception',
|
path: '/exception',
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { ProfileOutlined } from '@vicons/antd';
|
import { ProfileOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/form',
|
path: '/form',
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { TableOutlined } from '@vicons/antd';
|
import { TableOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/list',
|
path: '/list',
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: 'https://www.naiveadmin.com',
|
name: 'https://www.naiveadmin.com',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Pro 版本',
|
title: 'Plus 版本',
|
||||||
extra: renderNew(),
|
extra: renderNew(),
|
||||||
icon: renderIcon(SketchOutlined),
|
icon: renderIcon(SketchOutlined),
|
||||||
sort: 12,
|
sort: 12,
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { CheckCircleOutlined } from '@vicons/antd';
|
import { CheckCircleOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/result',
|
path: '/result',
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { SettingOutlined } from '@vicons/antd';
|
import { SettingOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/setting',
|
path: '/setting',
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { OptionsSharp } from '@vicons/ionicons5';
|
import { OptionsSharp } from '@vicons/ionicons5';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/system',
|
path: '/system',
|
||||||
@@ -30,7 +19,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: 'menu',
|
path: 'menu',
|
||||||
name: 'system_menu',
|
name: 'system_menu',
|
||||||
meta: {
|
meta: {
|
||||||
title: '菜单权限管理',
|
title: '菜单权限',
|
||||||
},
|
},
|
||||||
component: () => import('@/views/system/menu/menu.vue'),
|
component: () => import('@/views/system/menu/menu.vue'),
|
||||||
},
|
},
|
||||||
@@ -38,7 +27,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: 'role',
|
path: 'role',
|
||||||
name: 'system_role',
|
name: 'system_role',
|
||||||
meta: {
|
meta: {
|
||||||
title: '角色权限管理',
|
title: '角色权限',
|
||||||
},
|
},
|
||||||
component: () => import('@/views/system/role/role.vue'),
|
component: () => import('@/views/system/role/role.vue'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
import { RedirectName } from '@/router/constant';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { RouteLocationNormalized } from 'vue-router';
|
import { RouteLocationNormalized } from 'vue-router';
|
||||||
|
|
||||||
// 不需要出现在标签页中的路由
|
// 不需要出现在标签页中的路由
|
||||||
const whiteList = ['Redirect', 'login'];
|
const whiteList = [RedirectName, `${RedirectName}Son`, 'login'];
|
||||||
|
|
||||||
export type RouteItem = Partial<RouteLocationNormalized> & {
|
export type RouteItem = Partial<RouteLocationNormalized> & {
|
||||||
fullPath: string;
|
fullPath: string;
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ export const useUserStore = defineStore({
|
|||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
async getInfo() {
|
async getInfo() {
|
||||||
const result = await getUserInfoApi();
|
const data = await getUserInfoApi();
|
||||||
|
const { result } = data;
|
||||||
if (result.permissions && result.permissions.length) {
|
if (result.permissions && result.permissions.length) {
|
||||||
const permissionsList = result.permissions;
|
const permissionsList = result.permissions;
|
||||||
this.setPermissions(permissionsList);
|
this.setPermissions(permissionsList);
|
||||||
@@ -93,7 +94,7 @@ export const useUserStore = defineStore({
|
|||||||
// 登出
|
// 登出
|
||||||
async logout() {
|
async logout() {
|
||||||
this.setPermissions([]);
|
this.setPermissions([]);
|
||||||
this.setUserInfo({ name: '', email: '' });
|
this.setUserInfo({ username: '', email: '' });
|
||||||
storage.remove(ACCESS_TOKEN);
|
storage.remove(ACCESS_TOKEN);
|
||||||
storage.remove(CURRENT_USER);
|
storage.remove(CURRENT_USER);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,8 +28,9 @@ export function getAppEnvConfig() {
|
|||||||
VITE_GLOB_APP_SHORT_NAME,
|
VITE_GLOB_APP_SHORT_NAME,
|
||||||
VITE_GLOB_API_URL_PREFIX,
|
VITE_GLOB_API_URL_PREFIX,
|
||||||
VITE_GLOB_UPLOAD_URL,
|
VITE_GLOB_UPLOAD_URL,
|
||||||
VITE_GLOB_PROD_MOCK,
|
VITE_GLOB_FILE_URL,
|
||||||
VITE_GLOB_IMG_URL,
|
VITE_USE_MOCK,
|
||||||
|
VITE_LOGGER_MOCK,
|
||||||
} = ENV;
|
} = ENV;
|
||||||
|
|
||||||
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
||||||
@@ -44,8 +45,9 @@ export function getAppEnvConfig() {
|
|||||||
VITE_GLOB_APP_SHORT_NAME,
|
VITE_GLOB_APP_SHORT_NAME,
|
||||||
VITE_GLOB_API_URL_PREFIX,
|
VITE_GLOB_API_URL_PREFIX,
|
||||||
VITE_GLOB_UPLOAD_URL,
|
VITE_GLOB_UPLOAD_URL,
|
||||||
VITE_GLOB_PROD_MOCK,
|
VITE_GLOB_FILE_URL,
|
||||||
VITE_GLOB_IMG_URL,
|
VITE_USE_MOCK,
|
||||||
|
VITE_LOGGER_MOCK,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
126
src/utils/http/alova/index.ts
Normal file
126
src/utils/http/alova/index.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { createAlova } from 'alova';
|
||||||
|
import VueHook from 'alova/vue';
|
||||||
|
import adapterFetch from 'alova/fetch';
|
||||||
|
import { createAlovaMockAdapter } from '@alova/mock';
|
||||||
|
import { isString } from 'lodash-es';
|
||||||
|
import mocks from './mocks';
|
||||||
|
import { useUser } from '@/store/modules/user';
|
||||||
|
import { storage } from '@/utils/Storage';
|
||||||
|
import { useGlobSetting, useLocalSetting } from '@/hooks/setting';
|
||||||
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
|
import { isUrl } from '@/utils';
|
||||||
|
|
||||||
|
const { apiUrl, urlPrefix } = useGlobSetting();
|
||||||
|
|
||||||
|
const { useMock, loggerMock } = useLocalSetting();
|
||||||
|
|
||||||
|
const mockAdapter = createAlovaMockAdapter([...mocks], {
|
||||||
|
// 全局控制是否启用mock接口,默认为true
|
||||||
|
enable: useMock,
|
||||||
|
|
||||||
|
// 非模拟请求适配器,用于未匹配mock接口时发送请求
|
||||||
|
httpAdapter: adapterFetch(),
|
||||||
|
|
||||||
|
// mock接口响应延迟,单位毫秒
|
||||||
|
delay: 1000,
|
||||||
|
|
||||||
|
// 自定义打印mock接口请求信息
|
||||||
|
// mockRequestLogger: (res) => {
|
||||||
|
// loggerMock && console.log(`Mock Request ${res.url}`, res);
|
||||||
|
// },
|
||||||
|
mockRequestLogger: loggerMock,
|
||||||
|
onMockError(error, currentMethod) {
|
||||||
|
console.error('🚀 ~ onMockError ~ currentMethod:', currentMethod);
|
||||||
|
console.error('🚀 ~ onMockError ~ error:', error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Alova = createAlova({
|
||||||
|
baseURL: apiUrl,
|
||||||
|
statesHook: VueHook,
|
||||||
|
// 关闭全局请求缓存
|
||||||
|
// cacheFor: null,
|
||||||
|
// 全局缓存配置
|
||||||
|
// cacheFor: {
|
||||||
|
// POST: {
|
||||||
|
// mode: 'memory',
|
||||||
|
// expire: 60 * 10 * 1000
|
||||||
|
// },
|
||||||
|
// GET: {
|
||||||
|
// mode: 'memory',
|
||||||
|
// expire: 60 * 10 * 1000
|
||||||
|
// },
|
||||||
|
// HEAD: 60 * 10 * 1000 // 统一设置HEAD请求的缓存模式
|
||||||
|
// },
|
||||||
|
// 在开发环境开启缓存命中日志
|
||||||
|
cacheLogger: process.env.NODE_ENV === 'development',
|
||||||
|
requestAdapter: mockAdapter,
|
||||||
|
beforeRequest(method) {
|
||||||
|
const userStore = useUser();
|
||||||
|
const token = userStore.getToken;
|
||||||
|
// 添加 token 到请求头
|
||||||
|
if (!method.meta?.ignoreToken && token) {
|
||||||
|
method.config.headers['token'] = token;
|
||||||
|
}
|
||||||
|
// 处理 api 请求前缀
|
||||||
|
const isUrlStr = isUrl(method.url as string);
|
||||||
|
if (!isUrlStr && urlPrefix) {
|
||||||
|
method.url = `${urlPrefix}${method.url}`;
|
||||||
|
}
|
||||||
|
if (!isUrlStr && apiUrl && isString(apiUrl)) {
|
||||||
|
method.url = `${apiUrl}${method.url}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responded: {
|
||||||
|
onSuccess: async (response, method) => {
|
||||||
|
const res = (response.json && (await response.json())) || response.body;
|
||||||
|
|
||||||
|
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||||
|
if (method.meta?.isReturnNativeResponse) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// 请根据自身情况修改数据结构
|
||||||
|
const { message, code, result } = res;
|
||||||
|
|
||||||
|
// 不进行任何处理,直接返回
|
||||||
|
// 用于需要直接获取 code、result、 message 这些信息时开启
|
||||||
|
if (method.meta?.isTransformResponse === false) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const Message = window.$message;
|
||||||
|
// @ts-ignore
|
||||||
|
const Modal = window.$dialog;
|
||||||
|
|
||||||
|
const LoginPath = PageEnum.BASE_LOGIN;
|
||||||
|
if (ResultEnum.SUCCESS === code) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// 需要登录
|
||||||
|
if (code === 912) {
|
||||||
|
Modal?.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: '登录身份已失效,请重新登录!',
|
||||||
|
okText: '确定',
|
||||||
|
closable: false,
|
||||||
|
maskClosable: false,
|
||||||
|
onOk: async () => {
|
||||||
|
storage.clear();
|
||||||
|
window.location.href = LoginPath;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// 可按需处理错误 一般情况下不是 912 错误,不一定需要弹出 message
|
||||||
|
Message?.error(message);
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 项目,多个不同 api 地址,可导出多个实例
|
||||||
|
// export const AlovaTwo = createAlova({
|
||||||
|
// baseURL: 'http://localhost:9001',
|
||||||
|
// });
|
||||||
10
src/utils/http/alova/mocks.ts
Normal file
10
src/utils/http/alova/mocks.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// 这里按需导入 mock 文件,只有在这里导入并导出,才会执行 mock 拦截
|
||||||
|
// 跟根据实际开发情况配置
|
||||||
|
import UserMock from '../../../../mock/user';
|
||||||
|
import MenusMock from '../../../../mock/user/menus';
|
||||||
|
import ConsoleMock from '../../../../mock/dashboard/console';
|
||||||
|
import TableMock from '../../../../mock/table/list';
|
||||||
|
import SystemMenuMock from '../../../../mock/system/menu';
|
||||||
|
import SystemRoleMock from '../../../../mock/system/role';
|
||||||
|
|
||||||
|
export default [UserMock, MenusMock, TableMock, ConsoleMock, SystemMenuMock, SystemRoleMock];
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
import { AxiosCanceler } from './axiosCancel';
|
|
||||||
import { isFunction } from '@/utils/is';
|
|
||||||
import { cloneDeep } from 'lodash-es';
|
|
||||||
|
|
||||||
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
|
|
||||||
import { ContentTypeEnum } from '@/enums/httpEnum';
|
|
||||||
|
|
||||||
export * from './axiosTransform';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: axios模块
|
|
||||||
*/
|
|
||||||
export class VAxios {
|
|
||||||
private axiosInstance: AxiosInstance;
|
|
||||||
private options: CreateAxiosOptions;
|
|
||||||
|
|
||||||
constructor(options: CreateAxiosOptions) {
|
|
||||||
this.options = options;
|
|
||||||
this.axiosInstance = axios.create(options);
|
|
||||||
this.setupInterceptors();
|
|
||||||
}
|
|
||||||
|
|
||||||
getAxios(): AxiosInstance {
|
|
||||||
return this.axiosInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 重新配置axios
|
|
||||||
*/
|
|
||||||
configAxios(config: CreateAxiosOptions) {
|
|
||||||
if (!this.axiosInstance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.createAxios(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 设置通用header
|
|
||||||
*/
|
|
||||||
setHeader(headers: any): void {
|
|
||||||
if (!this.axiosInstance) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Object.assign(this.axiosInstance.defaults.headers, headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求方法
|
|
||||||
*/
|
|
||||||
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
|
||||||
let conf: AxiosRequestConfig = cloneDeep(config);
|
|
||||||
const transform = this.getTransform();
|
|
||||||
|
|
||||||
const { requestOptions } = this.options;
|
|
||||||
|
|
||||||
const opt: RequestOptions = Object.assign({}, requestOptions, options);
|
|
||||||
|
|
||||||
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
|
|
||||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
|
||||||
conf = beforeRequestHook(conf, opt);
|
|
||||||
}
|
|
||||||
|
|
||||||
//这里重新 赋值成最新的配置
|
|
||||||
// @ts-ignore
|
|
||||||
conf.requestOptions = opt;
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.axiosInstance
|
|
||||||
.request<any, AxiosResponse<Result>>(conf)
|
|
||||||
.then((res: AxiosResponse<Result>) => {
|
|
||||||
// 请求是否被取消
|
|
||||||
const isCancel = axios.isCancel(res);
|
|
||||||
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
|
|
||||||
try {
|
|
||||||
const ret = transformRequestData(res, opt);
|
|
||||||
resolve(ret);
|
|
||||||
} catch (err) {
|
|
||||||
reject(err || new Error('request error!'));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve(res as unknown as Promise<T>);
|
|
||||||
})
|
|
||||||
.catch((e: Error) => {
|
|
||||||
if (requestCatch && isFunction(requestCatch)) {
|
|
||||||
reject(requestCatch(e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reject(e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 创建axios实例
|
|
||||||
*/
|
|
||||||
private createAxios(config: CreateAxiosOptions): void {
|
|
||||||
this.axiosInstance = axios.create(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTransform() {
|
|
||||||
const { transform } = this.options;
|
|
||||||
return transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 文件上传
|
|
||||||
*/
|
|
||||||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
|
|
||||||
const formData = new window.FormData();
|
|
||||||
const customFilename = params.name || 'file';
|
|
||||||
|
|
||||||
if (params.filename) {
|
|
||||||
formData.append(customFilename, params.file, params.filename);
|
|
||||||
} else {
|
|
||||||
formData.append(customFilename, params.file);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.data) {
|
|
||||||
Object.keys(params.data).forEach((key) => {
|
|
||||||
const value = params.data![key];
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
value.forEach((item) => {
|
|
||||||
formData.append(`${key}[]`, item);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
formData.append(key, params.data![key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.axiosInstance.request<T>({
|
|
||||||
method: 'POST',
|
|
||||||
data: formData,
|
|
||||||
headers: {
|
|
||||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
|
||||||
ignoreCancelToken: true,
|
|
||||||
},
|
|
||||||
...config,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 拦截器配置
|
|
||||||
*/
|
|
||||||
private setupInterceptors() {
|
|
||||||
const transform = this.getTransform();
|
|
||||||
if (!transform) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const {
|
|
||||||
requestInterceptors,
|
|
||||||
requestInterceptorsCatch,
|
|
||||||
responseInterceptors,
|
|
||||||
responseInterceptorsCatch,
|
|
||||||
} = transform;
|
|
||||||
|
|
||||||
const axiosCanceler = new AxiosCanceler();
|
|
||||||
|
|
||||||
// 请求拦截器配置处理
|
|
||||||
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
|
|
||||||
const {
|
|
||||||
headers: { ignoreCancelToken },
|
|
||||||
} = config;
|
|
||||||
const ignoreCancel =
|
|
||||||
ignoreCancelToken !== undefined
|
|
||||||
? ignoreCancelToken
|
|
||||||
: this.options.requestOptions?.ignoreCancelToken;
|
|
||||||
|
|
||||||
!ignoreCancel && axiosCanceler.addPending(config);
|
|
||||||
if (requestInterceptors && isFunction(requestInterceptors)) {
|
|
||||||
config = requestInterceptors(config, this.options);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}, undefined);
|
|
||||||
|
|
||||||
// 请求拦截器错误捕获
|
|
||||||
requestInterceptorsCatch &&
|
|
||||||
isFunction(requestInterceptorsCatch) &&
|
|
||||||
this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch);
|
|
||||||
|
|
||||||
// 响应结果拦截器处理
|
|
||||||
this.axiosInstance.interceptors.response.use((res: AxiosResponse<any>) => {
|
|
||||||
res && axiosCanceler.removePending(res.config);
|
|
||||||
if (responseInterceptors && isFunction(responseInterceptors)) {
|
|
||||||
res = responseInterceptors(res);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}, undefined);
|
|
||||||
|
|
||||||
// 响应结果拦截器错误捕获
|
|
||||||
responseInterceptorsCatch &&
|
|
||||||
isFunction(responseInterceptorsCatch) &&
|
|
||||||
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import axios, { AxiosRequestConfig, Canceler } from 'axios';
|
|
||||||
import qs from 'qs';
|
|
||||||
|
|
||||||
import { isFunction } from '@/utils/is/index';
|
|
||||||
|
|
||||||
// 声明一个 Map 用于存储每个请求的标识 和 取消函数
|
|
||||||
let pendingMap = new Map<string, Canceler>();
|
|
||||||
|
|
||||||
export const getPendingUrl = (config: AxiosRequestConfig) =>
|
|
||||||
[config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&');
|
|
||||||
|
|
||||||
export class AxiosCanceler {
|
|
||||||
/**
|
|
||||||
* 添加请求
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
addPending(config: AxiosRequestConfig) {
|
|
||||||
this.removePending(config);
|
|
||||||
const url = getPendingUrl(config);
|
|
||||||
config.cancelToken =
|
|
||||||
config.cancelToken ||
|
|
||||||
new axios.CancelToken((cancel) => {
|
|
||||||
if (!pendingMap.has(url)) {
|
|
||||||
// 如果 pending 中不存在当前请求,则添加进去
|
|
||||||
pendingMap.set(url, cancel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 清空所有pending
|
|
||||||
*/
|
|
||||||
removeAllPending() {
|
|
||||||
pendingMap.forEach((cancel) => {
|
|
||||||
cancel && isFunction(cancel) && cancel();
|
|
||||||
});
|
|
||||||
pendingMap.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除请求
|
|
||||||
* @param {Object} config
|
|
||||||
*/
|
|
||||||
removePending(config: AxiosRequestConfig) {
|
|
||||||
const url = getPendingUrl(config);
|
|
||||||
|
|
||||||
if (pendingMap.has(url)) {
|
|
||||||
// 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除
|
|
||||||
const cancel = pendingMap.get(url);
|
|
||||||
cancel && cancel(url);
|
|
||||||
pendingMap.delete(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 重置
|
|
||||||
*/
|
|
||||||
reset(): void {
|
|
||||||
pendingMap = new Map<string, Canceler>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/**
|
|
||||||
* 数据处理类,可以根据项目自行配置
|
|
||||||
*/
|
|
||||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
||||||
import type { RequestOptions, Result } from './types';
|
|
||||||
|
|
||||||
export interface CreateAxiosOptions extends AxiosRequestConfig {
|
|
||||||
authenticationScheme?: string;
|
|
||||||
transform?: AxiosTransform;
|
|
||||||
requestOptions?: RequestOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class AxiosTransform {
|
|
||||||
/**
|
|
||||||
* @description: 请求之前处理配置
|
|
||||||
* @description: Process configuration before request
|
|
||||||
*/
|
|
||||||
beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求成功处理
|
|
||||||
*/
|
|
||||||
transformRequestData?: (res: AxiosResponse<Result>, options: RequestOptions) => any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求失败处理
|
|
||||||
*/
|
|
||||||
requestCatch?: (e: Error) => Promise<any>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求之前的拦截器
|
|
||||||
*/
|
|
||||||
requestInterceptors?: (
|
|
||||||
config: AxiosRequestConfig,
|
|
||||||
options: CreateAxiosOptions
|
|
||||||
) => AxiosRequestConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求之后的拦截器
|
|
||||||
*/
|
|
||||||
responseInterceptors?: (res: AxiosResponse<any>) => AxiosResponse<any>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求之前的拦截器错误处理
|
|
||||||
*/
|
|
||||||
requestInterceptorsCatch?: (error: Error) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求之后的拦截器错误处理
|
|
||||||
*/
|
|
||||||
responseInterceptorsCatch?: (error: Error) => void;
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
export function checkStatus(status: number, msg: string): void {
|
|
||||||
const $message = window['$message'];
|
|
||||||
switch (status) {
|
|
||||||
case 400:
|
|
||||||
$message.error(msg);
|
|
||||||
break;
|
|
||||||
// 401: 未登录
|
|
||||||
// 未登录则跳转登录页面,并携带当前页面的路径
|
|
||||||
// 在登录成功后返回当前页面,这一步需要在登录页操作。
|
|
||||||
case 401:
|
|
||||||
$message.error('用户没有权限(令牌、用户名、密码错误)!');
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
$message.error('用户得到授权,但是访问是被禁止的。!');
|
|
||||||
break;
|
|
||||||
// 404请求不存在
|
|
||||||
case 404:
|
|
||||||
$message.error('网络请求错误,未找到该资源!');
|
|
||||||
break;
|
|
||||||
case 405:
|
|
||||||
$message.error('网络请求错误,请求方法未允许!');
|
|
||||||
break;
|
|
||||||
case 408:
|
|
||||||
$message.error('网络请求超时');
|
|
||||||
break;
|
|
||||||
case 500:
|
|
||||||
$message.error('服务器错误,请联系管理员!');
|
|
||||||
break;
|
|
||||||
case 501:
|
|
||||||
$message.error('网络未实现');
|
|
||||||
break;
|
|
||||||
case 502:
|
|
||||||
$message.error('网络错误');
|
|
||||||
break;
|
|
||||||
case 503:
|
|
||||||
$message.error('服务不可用,服务器暂时过载或维护!');
|
|
||||||
break;
|
|
||||||
case 504:
|
|
||||||
$message.error('网络超时');
|
|
||||||
break;
|
|
||||||
case 505:
|
|
||||||
$message.error('http版本不支持该请求!');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$message.error(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { isObject, isString } from '@/utils/is';
|
|
||||||
|
|
||||||
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
|
|
||||||
|
|
||||||
export function joinTimestamp<T extends boolean>(
|
|
||||||
join: boolean,
|
|
||||||
restful: T
|
|
||||||
): T extends true ? string : object;
|
|
||||||
|
|
||||||
export function joinTimestamp(join: boolean, restful = false): string | object {
|
|
||||||
if (!join) {
|
|
||||||
return restful ? '' : {};
|
|
||||||
}
|
|
||||||
const now = new Date().getTime();
|
|
||||||
if (restful) {
|
|
||||||
return `?_t=${now}`;
|
|
||||||
}
|
|
||||||
return { _t: now };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: Format request parameter time
|
|
||||||
*/
|
|
||||||
export function formatRequestDate(params: Recordable) {
|
|
||||||
if (Object.prototype.toString.call(params) !== '[object Object]') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in params) {
|
|
||||||
if (params[key] && params[key]._isAMomentObject) {
|
|
||||||
params[key] = params[key].format(DATE_TIME_FORMAT);
|
|
||||||
}
|
|
||||||
if (isString(key)) {
|
|
||||||
const value = params[key];
|
|
||||||
if (value) {
|
|
||||||
try {
|
|
||||||
params[key] = isString(value) ? value.trim() : value;
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error as any);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isObject(params[key])) {
|
|
||||||
formatRequestDate(params[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
|
|
||||||
import { VAxios } from './Axios';
|
|
||||||
import { AxiosTransform } from './axiosTransform';
|
|
||||||
import axios, { AxiosResponse } from 'axios';
|
|
||||||
import { checkStatus } from './checkStatus';
|
|
||||||
import { joinTimestamp, formatRequestDate } from './helper';
|
|
||||||
import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum';
|
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
|
||||||
|
|
||||||
import { useGlobSetting } from '@/hooks/setting';
|
|
||||||
|
|
||||||
import { isString } from '@/utils/is/';
|
|
||||||
import { deepMerge, isUrl } from '@/utils';
|
|
||||||
import { setObjToUrlParams } from '@/utils/urlUtils';
|
|
||||||
|
|
||||||
import { RequestOptions, Result, CreateAxiosOptions } from './types';
|
|
||||||
|
|
||||||
import { useUser } from '@/store/modules/user';
|
|
||||||
|
|
||||||
const globSetting = useGlobSetting();
|
|
||||||
const urlPrefix = globSetting.urlPrefix || '';
|
|
||||||
|
|
||||||
import router from '@/router';
|
|
||||||
import { storage } from '@/utils/Storage';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 数据处理,方便区分多种处理方式
|
|
||||||
*/
|
|
||||||
const transform: AxiosTransform = {
|
|
||||||
/**
|
|
||||||
* @description: 处理请求数据
|
|
||||||
*/
|
|
||||||
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
|
||||||
const {
|
|
||||||
isShowMessage = true,
|
|
||||||
isShowErrorMessage,
|
|
||||||
isShowSuccessMessage,
|
|
||||||
successMessageText,
|
|
||||||
errorMessageText,
|
|
||||||
isTransformResponse,
|
|
||||||
isReturnNativeResponse,
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
|
||||||
if (isReturnNativeResponse) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
// 不进行任何处理,直接返回
|
|
||||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
|
||||||
if (!isTransformResponse) {
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = res;
|
|
||||||
|
|
||||||
const $dialog = window['$dialog'];
|
|
||||||
const $message = window['$message'];
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
// return '[HTTP] Request has no return value';
|
|
||||||
throw new Error('请求出错,请稍候重试');
|
|
||||||
}
|
|
||||||
// 这里 code,result,message为 后台统一的字段,需要修改为项目自己的接口返回格式
|
|
||||||
const { code, result, message } = data;
|
|
||||||
// 请求成功
|
|
||||||
const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
|
|
||||||
// 是否显示提示信息
|
|
||||||
if (isShowMessage) {
|
|
||||||
if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
|
|
||||||
// 是否显示自定义信息提示
|
|
||||||
$dialog.success({
|
|
||||||
type: 'success',
|
|
||||||
content: successMessageText || message || '操作成功!',
|
|
||||||
});
|
|
||||||
} else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
|
|
||||||
// 是否显示自定义信息提示
|
|
||||||
$message.error(message || errorMessageText || '操作失败!');
|
|
||||||
} else if (!hasSuccess && options.errorMessageMode === 'modal') {
|
|
||||||
// errorMessageMode=‘custom-modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
|
||||||
$dialog.info({
|
|
||||||
title: '提示',
|
|
||||||
content: message,
|
|
||||||
positiveText: '确定',
|
|
||||||
onPositiveClick: () => {},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接口请求成功,直接返回结果
|
|
||||||
if (code === ResultEnum.SUCCESS) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
|
|
||||||
let errorMsg = message;
|
|
||||||
switch (code) {
|
|
||||||
// 请求失败
|
|
||||||
case ResultEnum.ERROR:
|
|
||||||
$message.error(errorMsg);
|
|
||||||
break;
|
|
||||||
// 登录超时
|
|
||||||
case ResultEnum.TIMEOUT:
|
|
||||||
const LoginName = PageEnum.BASE_LOGIN_NAME;
|
|
||||||
const LoginPath = PageEnum.BASE_LOGIN;
|
|
||||||
if (router.currentRoute.value?.name === LoginName) return;
|
|
||||||
// 到登录页
|
|
||||||
errorMsg = '登录超时,请重新登录!';
|
|
||||||
$dialog.warning({
|
|
||||||
title: '提示',
|
|
||||||
content: '登录身份已失效,请重新登录!',
|
|
||||||
positiveText: '确定',
|
|
||||||
//negativeText: '取消',
|
|
||||||
closable: false,
|
|
||||||
maskClosable: false,
|
|
||||||
onPositiveClick: () => {
|
|
||||||
storage.clear();
|
|
||||||
window.location.href = LoginPath;
|
|
||||||
},
|
|
||||||
onNegativeClick: () => {},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw new Error(errorMsg);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 请求之前处理config
|
|
||||||
beforeRequestHook: (config, options) => {
|
|
||||||
const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
|
|
||||||
|
|
||||||
const isUrlStr = isUrl(config.url as string);
|
|
||||||
|
|
||||||
if (!isUrlStr && joinPrefix) {
|
|
||||||
config.url = `${urlPrefix}${config.url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isUrlStr && apiUrl && isString(apiUrl)) {
|
|
||||||
config.url = `${apiUrl}${config.url}`;
|
|
||||||
}
|
|
||||||
const params = config.params || {};
|
|
||||||
const data = config.data || false;
|
|
||||||
if (config.method?.toUpperCase() === RequestEnum.GET) {
|
|
||||||
if (!isString(params)) {
|
|
||||||
// 给 get 请求加上时间戳参数,避免从缓存中拿数据。
|
|
||||||
config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
|
|
||||||
} else {
|
|
||||||
// 兼容restful风格
|
|
||||||
config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
|
|
||||||
config.params = undefined;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!isString(params)) {
|
|
||||||
formatDate && formatRequestDate(params);
|
|
||||||
if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) {
|
|
||||||
config.data = data;
|
|
||||||
config.params = params;
|
|
||||||
} else {
|
|
||||||
config.data = params;
|
|
||||||
config.params = undefined;
|
|
||||||
}
|
|
||||||
if (joinParamsToUrl) {
|
|
||||||
config.url = setObjToUrlParams(
|
|
||||||
config.url as string,
|
|
||||||
Object.assign({}, config.params, config.data)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 兼容restful风格
|
|
||||||
config.url = config.url + params;
|
|
||||||
config.params = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求拦截器处理
|
|
||||||
*/
|
|
||||||
requestInterceptors: (config, options) => {
|
|
||||||
// 请求之前处理config
|
|
||||||
const userStore = useUser();
|
|
||||||
const token = userStore.getToken;
|
|
||||||
if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
|
|
||||||
// jwt token
|
|
||||||
(config as Recordable).headers.Authorization = options.authenticationScheme
|
|
||||||
? `${options.authenticationScheme} ${token}`
|
|
||||||
: token;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 响应错误处理
|
|
||||||
*/
|
|
||||||
responseInterceptorsCatch: (error: any) => {
|
|
||||||
const $dialog = window['$dialog'];
|
|
||||||
const $message = window['$message'];
|
|
||||||
const { response, code, message } = error || {};
|
|
||||||
// TODO 此处要根据后端接口返回格式修改
|
|
||||||
const msg: string =
|
|
||||||
response && response.data && response.data.message ? response.data.message : '';
|
|
||||||
const err: string = error.toString();
|
|
||||||
try {
|
|
||||||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
|
||||||
$message.error('接口请求超时,请刷新页面重试!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (err && err.includes('Network Error')) {
|
|
||||||
$dialog.info({
|
|
||||||
title: '网络异常',
|
|
||||||
content: '请检查您的网络连接是否正常',
|
|
||||||
positiveText: '确定',
|
|
||||||
//negativeText: '取消',
|
|
||||||
closable: false,
|
|
||||||
maskClosable: false,
|
|
||||||
onPositiveClick: () => {},
|
|
||||||
onNegativeClick: () => {},
|
|
||||||
});
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error as any);
|
|
||||||
}
|
|
||||||
// 请求是否被取消
|
|
||||||
const isCancel = axios.isCancel(error);
|
|
||||||
if (!isCancel) {
|
|
||||||
checkStatus(error.response && error.response.status, msg);
|
|
||||||
} else {
|
|
||||||
console.warn(error, '请求被取消!');
|
|
||||||
}
|
|
||||||
//return Promise.reject(error);
|
|
||||||
return Promise.reject(response?.data);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function createAxios(opt?: Partial<CreateAxiosOptions>) {
|
|
||||||
return new VAxios(
|
|
||||||
deepMerge(
|
|
||||||
{
|
|
||||||
timeout: 10 * 1000,
|
|
||||||
authenticationScheme: '',
|
|
||||||
// 接口前缀
|
|
||||||
prefixUrl: urlPrefix,
|
|
||||||
headers: { 'Content-Type': ContentTypeEnum.JSON },
|
|
||||||
// 数据处理方式
|
|
||||||
transform,
|
|
||||||
// 配置项,下面的选项都可以在独立的接口请求中覆盖
|
|
||||||
requestOptions: {
|
|
||||||
// 默认将prefix 添加到url
|
|
||||||
joinPrefix: true,
|
|
||||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
|
||||||
isReturnNativeResponse: false,
|
|
||||||
// 需要对返回数据进行处理
|
|
||||||
isTransformResponse: true,
|
|
||||||
// post请求的时候添加参数到url
|
|
||||||
joinParamsToUrl: false,
|
|
||||||
// 格式化提交参数时间
|
|
||||||
formatDate: true,
|
|
||||||
// 消息提示类型
|
|
||||||
errorMessageMode: 'none',
|
|
||||||
// 接口地址
|
|
||||||
apiUrl: globSetting.apiUrl,
|
|
||||||
// 接口拼接地址
|
|
||||||
urlPrefix: urlPrefix,
|
|
||||||
// 是否加入时间戳
|
|
||||||
joinTime: true,
|
|
||||||
// 忽略重复请求
|
|
||||||
ignoreCancelToken: true,
|
|
||||||
// 是否携带token
|
|
||||||
withToken: true,
|
|
||||||
},
|
|
||||||
withCredentials: false,
|
|
||||||
},
|
|
||||||
opt || {}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const http = createAxios();
|
|
||||||
|
|
||||||
// 项目,多个不同 api 地址,直接在这里导出多个
|
|
||||||
// src/api ts 里面接口,就可以单独使用这个请求,
|
|
||||||
// import { httpTwo } from '@/utils/http/axios'
|
|
||||||
// export const httpTwo = createAxios({
|
|
||||||
// requestOptions: {
|
|
||||||
// apiUrl: 'http://localhost:9001',
|
|
||||||
// urlPrefix: 'api',
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { AxiosRequestConfig } from 'axios';
|
|
||||||
import { AxiosTransform } from './axiosTransform';
|
|
||||||
|
|
||||||
export interface CreateAxiosOptions extends AxiosRequestConfig {
|
|
||||||
transform?: AxiosTransform;
|
|
||||||
requestOptions?: RequestOptions;
|
|
||||||
authenticationScheme?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传文件
|
|
||||||
export interface UploadFileParams {
|
|
||||||
// 其他参数
|
|
||||||
data?: Recordable;
|
|
||||||
// 文件参数接口字段名
|
|
||||||
name?: string;
|
|
||||||
// 文件
|
|
||||||
file: File | Blob;
|
|
||||||
// 文件名称
|
|
||||||
filename?: string;
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RequestOptions {
|
|
||||||
// 请求参数拼接到url
|
|
||||||
joinParamsToUrl?: boolean;
|
|
||||||
// 格式化请求参数时间
|
|
||||||
formatDate?: boolean;
|
|
||||||
// 是否显示提示信息
|
|
||||||
isShowMessage?: boolean;
|
|
||||||
// 是否解析成JSON
|
|
||||||
isParseToJson?: boolean;
|
|
||||||
// 成功的文本信息
|
|
||||||
successMessageText?: string;
|
|
||||||
// 是否显示成功信息
|
|
||||||
isShowSuccessMessage?: boolean;
|
|
||||||
// 是否显示失败信息
|
|
||||||
isShowErrorMessage?: boolean;
|
|
||||||
// 错误的文本信息
|
|
||||||
errorMessageText?: string;
|
|
||||||
// 是否加入url
|
|
||||||
joinPrefix?: boolean;
|
|
||||||
// 接口地址, 不填则使用默认apiUrl
|
|
||||||
apiUrl?: string;
|
|
||||||
// 请求拼接路径
|
|
||||||
urlPrefix?: string;
|
|
||||||
// 错误消息提示类型
|
|
||||||
errorMessageMode?: 'none' | 'modal';
|
|
||||||
// 是否添加时间戳
|
|
||||||
joinTime?: boolean;
|
|
||||||
// 不进行任何处理,直接返回
|
|
||||||
isTransformResponse?: boolean;
|
|
||||||
// 是否返回原生响应头
|
|
||||||
isReturnNativeResponse?: boolean;
|
|
||||||
//忽略重复请求
|
|
||||||
ignoreCancelToken?: boolean;
|
|
||||||
// 是否携带token
|
|
||||||
withToken?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Result<T = any> {
|
|
||||||
code: number;
|
|
||||||
type?: 'success' | 'error' | 'warning';
|
|
||||||
message: string;
|
|
||||||
result?: T;
|
|
||||||
}
|
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import { basicModal, useModal } from '@/components/Modal';
|
import { basicModal, useModal } from '@/components/Modal';
|
||||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||||
|
|||||||
@@ -1,16 +1,34 @@
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { NAvatar } from 'naive-ui';
|
import { NAvatar, NTag } from 'naive-ui';
|
||||||
|
import { BasicColumn } from '@/components/Table';
|
||||||
|
export interface ListData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
sex: string;
|
||||||
|
avatar: string;
|
||||||
|
email: string;
|
||||||
|
city: string;
|
||||||
|
status: string;
|
||||||
|
type: string;
|
||||||
|
createDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const columns = [
|
const sexMap = {
|
||||||
|
male: '男',
|
||||||
|
female: '女',
|
||||||
|
unknown: '未知',
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
close: '已取消',
|
||||||
|
refuse: '已拒绝',
|
||||||
|
pass: '已通过',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const columns: BasicColumn<ListData>[] = [
|
||||||
{
|
{
|
||||||
title: 'id',
|
title: 'id',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '编码',
|
|
||||||
key: 'no',
|
|
||||||
width: 100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
@@ -19,65 +37,78 @@ export const columns = [
|
|||||||
// 默认必填校验
|
// 默认必填校验
|
||||||
editRule: true,
|
editRule: true,
|
||||||
edit: true,
|
edit: true,
|
||||||
width: 200,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '头像',
|
title: '头像',
|
||||||
key: 'avatar',
|
key: 'avatar',
|
||||||
width: 100,
|
render(record) {
|
||||||
render(row) {
|
|
||||||
return h(NAvatar, {
|
return h(NAvatar, {
|
||||||
size: 48,
|
size: 50,
|
||||||
src: row.avatar,
|
src: record.avatar,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '地址',
|
title: '性别',
|
||||||
key: 'address',
|
key: 'sex',
|
||||||
|
render(record) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type: record.sex === 'male' ? 'info' : 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => sexMap[record.sex],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
key: 'email',
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '城市',
|
||||||
|
key: 'city',
|
||||||
editComponent: 'NSelect',
|
editComponent: 'NSelect',
|
||||||
editComponentProps: {
|
editComponentProps: {
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: '广东省',
|
label: '深圳市',
|
||||||
value: 1,
|
value: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '浙江省',
|
label: '广州市',
|
||||||
value: 2,
|
value: 2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
edit: true,
|
edit: true,
|
||||||
width: 200,
|
width: 220,
|
||||||
ellipsis: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '开始日期',
|
title: '状态',
|
||||||
key: 'beginTime',
|
key: 'status',
|
||||||
edit: true,
|
render(record) {
|
||||||
width: 160,
|
return h(
|
||||||
editComponent: 'NDatePicker',
|
NTag,
|
||||||
editComponentProps: {
|
{
|
||||||
type: 'datetime',
|
type:
|
||||||
format: 'yyyy-MM-dd HH:mm:ss',
|
record.status === 'close'
|
||||||
valueFormat: 'yyyy-MM-dd HH:mm:ss',
|
? 'default'
|
||||||
|
: record.status === 'refuse'
|
||||||
|
? 'error'
|
||||||
|
: 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => statusMap[record.status],
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
ellipsis: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '结束日期',
|
|
||||||
key: 'endTime',
|
|
||||||
width: 160,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
key: 'date',
|
key: 'createDate',
|
||||||
width: 160,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '停留时间',
|
|
||||||
key: 'time',
|
|
||||||
width: 80,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,11 +10,7 @@
|
|||||||
:actionColumn="actionColumn"
|
:actionColumn="actionColumn"
|
||||||
:scroll-x="1360"
|
:scroll-x="1360"
|
||||||
@update:checked-row-keys="onCheckedRow"
|
@update:checked-row-keys="onCheckedRow"
|
||||||
>
|
/>
|
||||||
<template #toolbar>
|
|
||||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -32,18 +28,18 @@
|
|||||||
|
|
||||||
const params = reactive({
|
const params = reactive({
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
name: 'xiaoMa',
|
name: 'NaiveAdmin',
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionColumn = reactive({
|
const actionColumn = reactive({
|
||||||
width: 150,
|
width: 180,
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render(record) {
|
render(record) {
|
||||||
return h(TableAction as any, {
|
return h(TableAction as any, {
|
||||||
style: 'text',
|
style: 'button',
|
||||||
actions: createActions(record),
|
actions: createActions(record),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -53,26 +49,16 @@
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: '删除',
|
label: '删除',
|
||||||
type: 'error',
|
|
||||||
// 配置 color 会覆盖 type
|
// 配置 color 会覆盖 type
|
||||||
color: 'red',
|
|
||||||
icon: DeleteOutlined,
|
icon: DeleteOutlined,
|
||||||
onClick: handleDelete.bind(null, record),
|
onClick: handleDelete.bind(null, record),
|
||||||
// 根据业务控制是否显示 isShow 和 auth 是并且关系
|
|
||||||
ifShow: () => {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
// 根据权限控制是否显示: 有权限,会显示,支持多个
|
// 根据权限控制是否显示: 有权限,会显示,支持多个
|
||||||
auth: ['basic_list'],
|
auth: ['basic_list'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '编辑',
|
label: '编辑',
|
||||||
type: 'primary',
|
|
||||||
icon: EditOutlined,
|
icon: EditOutlined,
|
||||||
onClick: handleEdit.bind(null, record),
|
onClick: handleEdit.bind(null, record),
|
||||||
ifShow: () => {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
auth: ['basic_list'],
|
auth: ['basic_list'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -86,10 +72,6 @@
|
|||||||
console.log(rowKeys);
|
console.log(rowKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadTable() {
|
|
||||||
actionRef.value.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDelete(record) {
|
function handleDelete(record) {
|
||||||
console.log(record);
|
console.log(record);
|
||||||
dialog.info({
|
dialog.info({
|
||||||
|
|||||||
@@ -1,72 +1,95 @@
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { NAvatar, NTag } from 'naive-ui';
|
import { NAvatar, NTag } from 'naive-ui';
|
||||||
|
import { BasicColumn } from '@/components/Table';
|
||||||
|
export interface ListData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
sex: string;
|
||||||
|
avatar: string;
|
||||||
|
email: string;
|
||||||
|
city: string;
|
||||||
|
status: string;
|
||||||
|
type: string;
|
||||||
|
createDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const columns = [
|
const sexMap = {
|
||||||
|
male: '男',
|
||||||
|
female: '女',
|
||||||
|
unknown: '未知',
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
close: '已取消',
|
||||||
|
refuse: '已拒绝',
|
||||||
|
pass: '已通过',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const columns: BasicColumn<ListData>[] = [
|
||||||
{
|
{
|
||||||
title: 'id',
|
title: 'id',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '编码',
|
|
||||||
key: 'no',
|
|
||||||
width: 100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: 100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '头像',
|
title: '头像',
|
||||||
key: 'avatar',
|
key: 'avatar',
|
||||||
width: 100,
|
render(record) {
|
||||||
render(row) {
|
|
||||||
return h(NAvatar, {
|
return h(NAvatar, {
|
||||||
size: 48,
|
size: 50,
|
||||||
src: row.avatar,
|
src: record.avatar,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '地址',
|
title: '性别',
|
||||||
key: 'address',
|
key: 'sex',
|
||||||
width: 150,
|
render(record) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type: record.sex === 'male' ? 'info' : 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => sexMap[record.sex],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '开始日期',
|
title: '邮箱',
|
||||||
key: 'beginTime',
|
key: 'email',
|
||||||
width: 160,
|
width: 220,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '结束日期',
|
title: '城市',
|
||||||
key: 'endTime',
|
key: 'city',
|
||||||
width: 160,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
width: 100,
|
render(record) {
|
||||||
render(row) {
|
|
||||||
return h(
|
return h(
|
||||||
NTag,
|
NTag,
|
||||||
{
|
{
|
||||||
type: row.status ? 'success' : 'error',
|
type:
|
||||||
|
record.status === 'close'
|
||||||
|
? 'default'
|
||||||
|
: record.status === 'refuse'
|
||||||
|
? 'error'
|
||||||
|
: 'success',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: () => (row.status ? '启用' : '禁用'),
|
default: () => statusMap[record.status],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
key: 'date',
|
key: 'createDate',
|
||||||
width: 160,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '停留时间',
|
|
||||||
key: 'time',
|
|
||||||
width: 80,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -11,11 +11,7 @@
|
|||||||
@edit-change="onEditChange"
|
@edit-change="onEditChange"
|
||||||
@update:checked-row-keys="onCheckedRow"
|
@update:checked-row-keys="onCheckedRow"
|
||||||
:scroll-x="1360"
|
:scroll-x="1360"
|
||||||
>
|
/>
|
||||||
<template #toolbar>
|
|
||||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -28,7 +24,7 @@
|
|||||||
const actionRef = ref();
|
const actionRef = ref();
|
||||||
const params = reactive({
|
const params = reactive({
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
name: 'xiaoMa',
|
name: 'NaiveAdmin',
|
||||||
});
|
});
|
||||||
|
|
||||||
function onEditChange({ column, value, record }) {
|
function onEditChange({ column, value, record }) {
|
||||||
@@ -46,11 +42,6 @@
|
|||||||
console.log(rowKeys);
|
console.log(rowKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadTable() {
|
|
||||||
console.log(actionRef.value);
|
|
||||||
actionRef.value.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function editEnd({ value }) {
|
function editEnd({ value }) {
|
||||||
console.log(value);
|
console.log(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,7 @@
|
|||||||
@edit-change="onEditChange"
|
@edit-change="onEditChange"
|
||||||
@update:checked-row-keys="onCheckedRow"
|
@update:checked-row-keys="onCheckedRow"
|
||||||
:scroll-x="1590"
|
:scroll-x="1590"
|
||||||
>
|
/>
|
||||||
<template #toolbar>
|
|
||||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
</n-card>
|
</n-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -30,7 +26,7 @@
|
|||||||
const currentEditKeyRef = ref('');
|
const currentEditKeyRef = ref('');
|
||||||
const params = reactive({
|
const params = reactive({
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
name: 'xiaoMa',
|
name: 'NaiveAdmin',
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionColumn = reactive({
|
const actionColumn = reactive({
|
||||||
@@ -101,11 +97,6 @@
|
|||||||
console.log(rowKeys);
|
console.log(rowKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadTable() {
|
|
||||||
console.log(actionRef.value);
|
|
||||||
actionRef.value.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
function editEnd({ value }) {
|
function editEnd({ value }) {
|
||||||
console.log(value);
|
console.log(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,97 +1,115 @@
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { NAvatar } from 'naive-ui';
|
import { NAvatar, NTag } from 'naive-ui';
|
||||||
|
import { BasicColumn } from '@/components/Table';
|
||||||
|
export interface ListData {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
sex: string;
|
||||||
|
avatar: string;
|
||||||
|
email: string;
|
||||||
|
city: string;
|
||||||
|
status: string;
|
||||||
|
type: string;
|
||||||
|
createDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const columns = [
|
const sexMap = {
|
||||||
|
male: '男',
|
||||||
|
female: '女',
|
||||||
|
unknown: '未知',
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
close: '已取消',
|
||||||
|
refuse: '已拒绝',
|
||||||
|
pass: '已通过',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const columns: BasicColumn<ListData>[] = [
|
||||||
{
|
{
|
||||||
title: 'id',
|
title: 'id',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '编码',
|
|
||||||
key: 'no',
|
|
||||||
width: 100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
editComponent: 'NInput',
|
editComponent: 'NInput',
|
||||||
editRow: true,
|
editRow: true,
|
||||||
// 默认必填校验
|
|
||||||
editRule: true,
|
editRule: true,
|
||||||
edit: true,
|
edit: true,
|
||||||
width: 200,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '头像',
|
title: '头像',
|
||||||
key: 'avatar',
|
key: 'avatar',
|
||||||
width: 100,
|
render(record) {
|
||||||
render(row) {
|
|
||||||
return h(NAvatar, {
|
return h(NAvatar, {
|
||||||
size: 48,
|
size: 50,
|
||||||
src: row.avatar,
|
src: record.avatar,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '地址',
|
title: '性别',
|
||||||
key: 'address',
|
key: 'sex',
|
||||||
editRow: true,
|
render(record) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type: record.sex === 'male' ? 'info' : 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => sexMap[record.sex],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '邮箱',
|
||||||
|
key: 'email',
|
||||||
|
width: 220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '城市',
|
||||||
|
key: 'city',
|
||||||
editComponent: 'NSelect',
|
editComponent: 'NSelect',
|
||||||
editComponentProps: {
|
editComponentProps: {
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: '广东省',
|
label: '深圳市',
|
||||||
value: 1,
|
value: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '浙江省',
|
label: '广州市',
|
||||||
value: 2,
|
value: 2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
edit: true,
|
|
||||||
width: 200,
|
|
||||||
ellipsis: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '开始日期',
|
|
||||||
key: 'beginTime',
|
|
||||||
editRow: true,
|
editRow: true,
|
||||||
edit: true,
|
edit: true,
|
||||||
width: 240,
|
width: 220,
|
||||||
editComponent: 'NDatePicker',
|
|
||||||
editComponentProps: {
|
|
||||||
type: 'datetime',
|
|
||||||
format: 'yyyy-MM-dd HH:mm:ss',
|
|
||||||
valueFormat: 'yyyy-MM-dd HH:mm:ss',
|
|
||||||
},
|
|
||||||
ellipsis: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '结束日期',
|
|
||||||
key: 'endTime',
|
|
||||||
width: 160,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
editRow: true,
|
render(record) {
|
||||||
edit: true,
|
return h(
|
||||||
width: 100,
|
NTag,
|
||||||
editComponent: 'NSwitch',
|
{
|
||||||
editValueMap: (value) => {
|
type:
|
||||||
return value ? '启用' : '禁用';
|
record.status === 'close'
|
||||||
|
? 'default'
|
||||||
|
: record.status === 'refuse'
|
||||||
|
? 'error'
|
||||||
|
: 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => statusMap[record.status],
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
key: 'date',
|
key: 'createDate',
|
||||||
width: 160,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '停留时间',
|
|
||||||
key: 'time',
|
|
||||||
width: 80,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,59 +1,95 @@
|
|||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
import { NAvatar } from 'naive-ui';
|
import { NAvatar, NTag } from 'naive-ui';
|
||||||
import { BasicColumn } from '@/components/Table';
|
import { BasicColumn } from '@/components/Table';
|
||||||
export interface ListData {
|
export interface ListData {
|
||||||
id: string;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
sex: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
address: string;
|
email: string;
|
||||||
beginTime: string;
|
city: string;
|
||||||
endTime: string;
|
status: string;
|
||||||
date: string;
|
type: string;
|
||||||
|
createDate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sexMap = {
|
||||||
|
male: '男',
|
||||||
|
female: '女',
|
||||||
|
unknown: '未知',
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusMap = {
|
||||||
|
close: '已取消',
|
||||||
|
refuse: '已拒绝',
|
||||||
|
pass: '已通过',
|
||||||
|
};
|
||||||
|
|
||||||
export const columns: BasicColumn<ListData>[] = [
|
export const columns: BasicColumn<ListData>[] = [
|
||||||
{
|
{
|
||||||
title: 'id',
|
title: 'id',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
width: 100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: '名称',
|
||||||
key: 'name',
|
key: 'name',
|
||||||
width: 100,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '头像',
|
title: '头像',
|
||||||
key: 'avatar',
|
key: 'avatar',
|
||||||
width: 100,
|
render(record) {
|
||||||
render(row) {
|
|
||||||
return h(NAvatar, {
|
return h(NAvatar, {
|
||||||
size: 48,
|
size: 50,
|
||||||
src: row.avatar,
|
src: record.avatar,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '地址',
|
title: '性别',
|
||||||
key: 'address',
|
key: 'sex',
|
||||||
auth: ['basic_list'], // 同时根据权限控制是否显示
|
render(record) {
|
||||||
ifShow: (_column) => {
|
return h(
|
||||||
return true; // 根据业务控制是否显示
|
NTag,
|
||||||
|
{
|
||||||
|
type: record.sex === 'male' ? 'info' : 'error',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => sexMap[record.sex],
|
||||||
|
}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
width: 150,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '开始日期',
|
title: '邮箱',
|
||||||
key: 'beginTime',
|
key: 'email',
|
||||||
width: 160,
|
width: 220,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '结束日期',
|
title: '城市',
|
||||||
key: 'endTime',
|
key: 'city',
|
||||||
width: 160,
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
render(record) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{
|
||||||
|
type:
|
||||||
|
record.status === 'close'
|
||||||
|
? 'default'
|
||||||
|
: record.status === 'refuse'
|
||||||
|
? 'error'
|
||||||
|
: 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
default: () => statusMap[record.status],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '创建时间',
|
||||||
key: 'date',
|
key: 'createDate',
|
||||||
width: 100,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,65 +1,70 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-card :bordered="false" class="proCard">
|
<n-flex vertical>
|
||||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
<n-card :bordered="false">
|
||||||
<template #statusSlot="{ model, field }">
|
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||||
<n-input v-model:value="model[field]" />
|
<template #statusSlot="{ model, field }">
|
||||||
</template>
|
<n-input v-model:value="model[field]" />
|
||||||
</BasicForm>
|
</template>
|
||||||
|
</BasicForm>
|
||||||
<BasicTable
|
</n-card>
|
||||||
:columns="columns"
|
<n-card :bordered="false">
|
||||||
:request="loadDataTable"
|
<BasicTable
|
||||||
:row-key="(row:ListData) => row.id"
|
:columns="columns"
|
||||||
ref="actionRef"
|
:request="loadDataTable"
|
||||||
:actionColumn="actionColumn"
|
:row-key="(row:ListData) => row.id"
|
||||||
@update:checked-row-keys="onCheckedRow"
|
ref="actionRef"
|
||||||
:scroll-x="1090"
|
:actionColumn="actionColumn"
|
||||||
:striped="true"
|
@update:checked-row-keys="onCheckedRow"
|
||||||
>
|
:scroll-x="1090"
|
||||||
<template #tableTitle>
|
:striped="true"
|
||||||
<n-button type="primary" @click="addTable">
|
|
||||||
<template #icon>
|
|
||||||
<n-icon>
|
|
||||||
<PlusOutlined />
|
|
||||||
</n-icon>
|
|
||||||
</template>
|
|
||||||
新建
|
|
||||||
</n-button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #toolbar>
|
|
||||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
|
|
||||||
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
|
|
||||||
<n-form
|
|
||||||
:model="formParams"
|
|
||||||
:rules="rules"
|
|
||||||
ref="formRef"
|
|
||||||
label-placement="left"
|
|
||||||
:label-width="80"
|
|
||||||
class="py-4"
|
|
||||||
>
|
>
|
||||||
<n-form-item label="名称" path="name">
|
<template #tableTitle>
|
||||||
<n-input placeholder="请输入名称" v-model:value="formParams.name" />
|
<n-button type="primary" @click="addTable">
|
||||||
</n-form-item>
|
<template #icon>
|
||||||
<n-form-item label="地址" path="address">
|
<n-icon>
|
||||||
<n-input type="textarea" placeholder="请输入地址" v-model:value="formParams.address" />
|
<PlusOutlined />
|
||||||
</n-form-item>
|
</n-icon>
|
||||||
<n-form-item label="日期" path="date">
|
</template>
|
||||||
<n-date-picker type="datetime" placeholder="请选择日期" v-model:value="formParams.date" />
|
新建
|
||||||
</n-form-item>
|
</n-button>
|
||||||
</n-form>
|
</template>
|
||||||
|
|
||||||
<template #action>
|
<template #toolbar> </template>
|
||||||
<n-space>
|
</BasicTable>
|
||||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
|
||||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
<n-modal v-model:show="showModal" :show-icon="false" preset="dialog" title="新建">
|
||||||
</n-space>
|
<n-form
|
||||||
</template>
|
:model="formParams"
|
||||||
</n-modal>
|
:rules="rules"
|
||||||
</n-card>
|
ref="formRef"
|
||||||
|
label-placement="left"
|
||||||
|
:label-width="80"
|
||||||
|
class="py-4"
|
||||||
|
>
|
||||||
|
<n-form-item label="名称" path="name">
|
||||||
|
<n-input placeholder="请输入名称" v-model:value="formParams.name" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="地址" path="address">
|
||||||
|
<n-input type="textarea" placeholder="请输入地址" v-model:value="formParams.address" />
|
||||||
|
</n-form-item>
|
||||||
|
<n-form-item label="日期" path="date">
|
||||||
|
<n-date-picker
|
||||||
|
type="datetime"
|
||||||
|
placeholder="请选择日期"
|
||||||
|
v-model:value="formParams.date"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-form>
|
||||||
|
|
||||||
|
<template #action>
|
||||||
|
<n-space>
|
||||||
|
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||||
|
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</n-card>
|
||||||
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="view-account">
|
<div class="view-account">
|
||||||
<div class="view-account-header"></div>
|
<div class="view-account-header"></div>
|
||||||
<div class="view-account-container">
|
<div class="view-account-background">
|
||||||
|
<div class="line line-1"></div>
|
||||||
|
<div class="line line-2"></div>
|
||||||
|
<div class="line line-3"></div>
|
||||||
|
<div class="square square-1"></div>
|
||||||
|
<div class="square square-2"></div>
|
||||||
|
<div class="triangle"></div>
|
||||||
|
<div class="wave wave-1"></div>
|
||||||
|
<div class="wave wave-2"></div>
|
||||||
|
<div class="wave wave-3"></div>
|
||||||
|
</div>
|
||||||
|
<div class="view-account-container animate__animated animate__fadeInDown">
|
||||||
<div class="view-account-top">
|
<div class="view-account-top">
|
||||||
<div class="view-account-top-logo">
|
<div class="view-account-top-logo">
|
||||||
<img :src="websiteConfig.loginImage" alt="" />
|
<img :src="websiteConfig.loginImage" alt="" />
|
||||||
@@ -9,15 +20,22 @@
|
|||||||
<div class="view-account-top-desc">{{ websiteConfig.loginDesc }}</div>
|
<div class="view-account-top-desc">{{ websiteConfig.loginDesc }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view-account-form">
|
<div class="view-account-form">
|
||||||
|
<h2 class="view-account-title">账号登录</h2>
|
||||||
|
<div class="login-welcome">欢迎回来,请登录您的账号</div>
|
||||||
<n-form
|
<n-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
label-placement="left"
|
label-placement="left"
|
||||||
size="large"
|
size="large"
|
||||||
:model="formInline"
|
:model="formInline"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
|
class="login-form"
|
||||||
>
|
>
|
||||||
<n-form-item path="username">
|
<n-form-item path="username" class="username-item">
|
||||||
<n-input v-model:value="formInline.username" placeholder="请输入用户名">
|
<n-input
|
||||||
|
v-model:value="formInline.username"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
class="login-input"
|
||||||
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
<PersonOutline />
|
<PersonOutline />
|
||||||
@@ -25,12 +43,13 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item path="password">
|
<n-form-item path="password" class="password-item">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="formInline.password"
|
v-model:value="formInline.password"
|
||||||
type="password"
|
type="password"
|
||||||
showPasswordOn="click"
|
showPasswordOn="click"
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
|
class="login-input"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<n-icon size="18" color="#808695">
|
<n-icon size="18" color="#808695">
|
||||||
@@ -39,42 +58,52 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item class="default-color">
|
<n-form-item class="default-color remember-forgot">
|
||||||
<div class="flex justify-between">
|
<div class="flex-between-wrapper">
|
||||||
<div class="flex-initial">
|
<div class="left">
|
||||||
<n-checkbox v-model:checked="autoLogin">自动登录</n-checkbox>
|
<n-checkbox v-model:checked="autoLogin">自动登录</n-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-initial order-last">
|
<div class="right">
|
||||||
<a href="javascript:">忘记密码</a>
|
<a href="javascript:" class="forgot-link">忘记密码</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item>
|
<n-form-item>
|
||||||
<n-button type="primary" @click="handleSubmit" size="large" :loading="loading" block>
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
@click="handleSubmit"
|
||||||
|
size="large"
|
||||||
|
:loading="loading"
|
||||||
|
block
|
||||||
|
class="login-button"
|
||||||
|
>
|
||||||
登录
|
登录
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item class="default-color">
|
<n-form-item class="default-color other-item">
|
||||||
<div class="flex view-account-other">
|
<div class="flex view-account-other">
|
||||||
<div class="flex-initial">
|
<div class="flex-initial other-text">
|
||||||
<span>其它登录方式</span>
|
<span>其它登录方式</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-initial mx-2">
|
<div class="social-login">
|
||||||
<a href="javascript:">
|
<a href="javascript:" class="social-icon">
|
||||||
<n-icon size="24" color="#2d8cf0">
|
<n-icon size="24" color="#909399">
|
||||||
<LogoGithub />
|
<LogoGithub />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<a href="javascript:" class="social-icon">
|
||||||
<div class="flex-initial mx-2">
|
<n-icon size="24" color="#909399">
|
||||||
<a href="javascript:">
|
|
||||||
<n-icon size="24" color="#2d8cf0">
|
|
||||||
<LogoFacebook />
|
<LogoFacebook />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="javascript:" class="social-icon">
|
||||||
|
<n-icon size="24" color="#909399">
|
||||||
|
<LogoWechat />
|
||||||
|
</n-icon>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-initial" style="margin-left: auto">
|
<div class="flex-initial" style="margin-left: auto">
|
||||||
<a href="javascript:">注册账号</a>
|
<a href="javascript:" class="register-link">注册账号</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
@@ -85,14 +114,25 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref, onMounted } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { useUserStore } from '@/store/modules/user';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import { ResultEnum } from '@/enums/httpEnum';
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook } from '@vicons/ionicons5';
|
import { PersonOutline, LockClosedOutline, LogoGithub, LogoFacebook, LogoWechat } from '@vicons/ionicons5';
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
import { websiteConfig } from '@/config/website.config';
|
import { websiteConfig } from '@/config/website.config';
|
||||||
|
|
||||||
|
// 添加页面加载动画效果
|
||||||
|
onMounted(() => {
|
||||||
|
// 聚焦用户名输入框
|
||||||
|
setTimeout(() => {
|
||||||
|
const usernameInput = document.querySelector('input[placeholder="请输入用户名"]');
|
||||||
|
if (usernameInput) {
|
||||||
|
(usernameInput as HTMLElement).focus();
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
interface FormState {
|
interface FormState {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
@@ -161,27 +201,122 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
background-color: #f0f2f5;
|
||||||
|
background: linear-gradient(140deg, #e8f1fa, #c2d9ec, #a1c3e0, #80aed3);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCI+CiAgPGNpcmNsZSBjeD0iMTAiIGN5PSIxMCIgcj0iMiIgZmlsbD0icmdiYSg0NSwgMTQwLCAyNDAsIDAuMSkiIC8+Cjwvc3ZnPg==');
|
||||||
|
opacity: 0.6;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj4KICA8cmVjdCB4PSI1MCIgeT0iNTAiIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgdHJhbnNmb3JtPSJyb3RhdGUoNDUgNTUgNTUpIiBmaWxsPSJyZ2JhKDQ1LCAxNDAsIDI0MCwgMC4wNSkiIC8+Cjwvc3ZnPg==');
|
||||||
|
opacity: 0.8;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&-container {
|
&-container {
|
||||||
flex: 1;
|
padding: 32px 40px 20px;
|
||||||
padding: 32px 12px;
|
max-width: 580px;
|
||||||
max-width: 384px;
|
min-width: 460px;
|
||||||
min-width: 320px;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-top: 10vh;
|
||||||
|
position: relative;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除圆形装饰元素
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 40px;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(to right, #2d8cf0, #0081ff);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-welcome {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-top {
|
&-top {
|
||||||
padding: 32px 0;
|
padding: 10px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
&-logo {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-desc {
|
&-desc {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #808695;
|
color: #606266;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-other {
|
&-other {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-color {
|
.default-color {
|
||||||
@@ -191,18 +326,368 @@
|
|||||||
color: #515a6e;
|
color: #515a6e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 42px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
opacity: 0;
|
||||||
|
border-radius: 100%;
|
||||||
|
transform: scale(1, 1) translate(-50%);
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus:not(:active)::after {
|
||||||
|
animation: ripple 1s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ripple {
|
||||||
|
0% {
|
||||||
|
transform: scale(0, 0);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: scale(25, 25);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(40, 40);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember-forgot {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
.flex-between-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-link {
|
||||||
|
color: #606266;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #2d8cf0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-login {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-icon {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 12px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
background-color: rgba(144, 147, 153, 0.1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(45, 140, 240, 0.2);
|
||||||
|
transform: scale(1.1);
|
||||||
|
|
||||||
|
:deep(svg) {
|
||||||
|
color: #2d8cf0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.register-link {
|
||||||
|
color: #2d8cf0;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #57a3f3;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
:deep(.n-form-item-feedback-wrapper) {
|
||||||
|
min-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-input) {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-input {
|
||||||
|
:deep(.n-input__input-el) {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.n-input-wrapper) {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
:deep(.n-input-wrapper) {
|
||||||
|
box-shadow: 0 0 0 1px rgba(45, 140, 240, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.username-item, .password-item {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-text {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.view-account {
|
.view-account {
|
||||||
background-image: url('../../assets/images/login.svg');
|
background-image: url('../../assets/images/login.svg'),
|
||||||
|
radial-gradient(circle at 10% 20%, rgba(100, 149, 237, 0.25) 0%, rgba(65, 105, 225, 0.2) 40%, rgba(30, 144, 255, 0.1) 90%);
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: 50%;
|
background-position: 50%;
|
||||||
background-size: 100%;
|
background-size: cover;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(135deg, rgba(45, 140, 240, 0.1), rgba(45, 140, 240, 0.05));
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8ZGVmcz4KICA8cGF0dGVybiBpZD0icGF0dGVybiIgeD0iMCIgeT0iMCIgd2lkdGg9IjYwIiBoZWlnaHQ9IjYwIiBwYXR0ZXJuVW5pdHM9InVzZXJTcGFjZU9uVXNlIiBwYXR0ZXJuVHJhbnNmb3JtPSJyb3RhdGUoNDUpIj4KICAgIDxjaXJjbGUgY3g9IjMwIiBjeT0iMzAiIHI9IjEuNSIgZmlsbD0icmdiYSg0NSwgMTQwLCAyNDAsIDAuMikiIC8+CiAgPC9wYXR0ZXJuPgo8L2RlZnM+CjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjcGF0dGVybikiIC8+Cjwvc3ZnPg==');
|
||||||
|
opacity: 0.3;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-container {
|
||||||
|
margin-top: 15vh;
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-height: 650px) {
|
||||||
|
.view-account-container {
|
||||||
|
margin-top: 5vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-account-background {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
.line {
|
||||||
|
position: absolute;
|
||||||
|
background: linear-gradient(90deg, rgba(45, 140, 240, 0.2), rgba(0, 129, 255, 0.1));
|
||||||
|
|
||||||
|
&-1 {
|
||||||
|
width: 300px;
|
||||||
|
height: 2px;
|
||||||
|
top: 15%;
|
||||||
|
right: 5%;
|
||||||
|
transform: rotate(-30deg);
|
||||||
|
animation: pulse 8s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-2 {
|
||||||
|
width: 200px;
|
||||||
|
height: 2px;
|
||||||
|
bottom: 20%;
|
||||||
|
left: 10%;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
animation: pulse 6s ease-in-out infinite 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-3 {
|
||||||
|
width: 150px;
|
||||||
|
height: 2px;
|
||||||
|
top: 40%;
|
||||||
|
left: 5%;
|
||||||
|
transform: rotate(-15deg);
|
||||||
|
animation: pulse 7s ease-in-out infinite 2s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-account-container {
|
.square {
|
||||||
padding: 32px 0 24px 0;
|
position: absolute;
|
||||||
|
|
||||||
|
&-1 {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
top: 10%;
|
||||||
|
left: 15%;
|
||||||
|
background: linear-gradient(45deg, rgba(45, 140, 240, 0.15), rgba(0, 129, 255, 0.05));
|
||||||
|
transform: rotate(30deg);
|
||||||
|
animation: rotate 15s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-2 {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
bottom: 15%;
|
||||||
|
right: 10%;
|
||||||
|
border: 2px solid rgba(45, 140, 240, 0.1);
|
||||||
|
background: transparent;
|
||||||
|
animation: rotate 12s linear infinite reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.triangle {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30%;
|
||||||
|
right: 20%;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-left: 50px solid transparent;
|
||||||
|
border-right: 50px solid transparent;
|
||||||
|
border-bottom: 80px solid rgba(45, 140, 240, 0.08);
|
||||||
|
animation: float 10s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave {
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.3;
|
||||||
|
transform-origin: bottom left;
|
||||||
|
|
||||||
|
&-1 {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNDQwIDMyMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+PHBhdGggZmlsbD0icmdiYSg0NSwgMTQwLCAyNDAsIDAuMikiIGQ9Ik0wLDMyMEMwLDI0MCA0MCwxNjAgODAsMTYwQzEyMCwxNjAgMTYwLDI0MCAyMDAsMjQwQzI0MCwyNDAgMjgwLDE2MCAzMjAsMTYwQzM2MCwxNjAgNDAwLDI0MCA0NDAsMjQwQzQ4MCwyNDAgNTIwLDE2MCA1NjAsMTYwQzYwMCwxNjAgNjQwLDI0MCA2ODAsMjQwQzcyMCwyNDAgNzYwLDE2MCA4MDAsMTYwQzg0MCwxNjAgODgwLDI0MCA5MjAsMjQwQzk2MCwyNDAgMTAwMCwxNjAgMTA0MCwxNjBDMTA4MCwxNjAgMTEyMCwyNDAgMTE2MCwyNDBDMTIwMCwyNDAgMTI0MCwxNjAgMTI4MCwxNjBDMTMyMCwxNjAgMTM2MCwyNDAgMTQwMCwyNDBDMTQ0MCwyNDAgMTQ0MCwxNjAgMTQ0MCwxNjBMMTQ0MCwzMjBMMCwzMjBaIj48L3BhdGg+PC9zdmc+');
|
||||||
|
background-size: 100% 120px;
|
||||||
|
animation: wave-left-to-right 15s ease-in-out infinite;
|
||||||
|
transform: rotate(-2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-2 {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNDQwIDMyMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+PHBhdGggZmlsbD0icmdiYSg0NSwgMTQwLCAyNDAsIDAuMTUpIiBkPSJNMCwzMjBDMCwyNDAgNjAsMTgwIDEyMCwxODBDMTgwLDE4MCAyNDAsMjQwIDMwMCwyNDBDMzYwLDI0MCA0MjAsMTgwIDQ4MCwxODBDNTQwLDE4MCA2MDAsMjQwIDY2MCwyNDBDNzIwLDI0MCA3ODAsMTgwIDg0MCwxODBDOTAwLDE4MCA5NjAsMjQwIDEwMjAsMjQwQzEwODAsMjQwIDExNDAsMTgwIDEyMDAsMTgwQzEyNjAsMTgwIDEzMjAsMjQwIDEzODAsMjQwQzE0NDAsMjQwIDE0NDAsMTgwIDE0NDAsMTgwTDE0NDAsMzIwTDAsMzIwWiI+PC9wYXRoPjwvc3ZnPg==');
|
||||||
|
background-size: 100% 100px;
|
||||||
|
animation: wave-left-to-right 18s ease-in-out infinite;
|
||||||
|
animation-delay: -5s;
|
||||||
|
transform: rotate(-1deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-3 {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 80px;
|
||||||
|
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNDQwIDMyMCIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+PHBhdGggZmlsbD0icmdiYSg0NSwgMTQwLCAyNDAsIDAuMSkiIGQ9Ik0wLDMyMEMwLDI2MCAzMCwyMDAgNjAsMjAwQzkwLDIwMCAxMjAsMjYwIDE1MCwyNjBDMTgwLDI2MCAyMTAsMjAwIDI0MCwyMDBDMjcwLDIwMCAzMDAsMjYwIDMzMCwyNjBDMzYwLDI2MCAzOTAsMjAwIDQyMCwyMDBDNDUwLDIwMCA0ODAsMjYwIDUxMCwyNjBDNTQwLDI2MCA1NzAsMjAwIDYwMCwyMDBDNjMwLDIwMCA2NjAsMjYwIDY5MCwyNjBDNzIwLDI2MCA3NTAsMjAwIDc4MCwyMDBDODEwLDIwMCA4NDAsMjYwIDg3MCwyNjBDOTAwLDI2MCA5MzAsMjAwIDk2MCwyMDBDOTkwLDIwMCAxMDIwLDI2MCAxMDUwLDI2MEMxMDgwLDI2MCAxMTEwLDIwMCAxMTQwLDIwMEMxMTcwLDIwMCAxMjAwLDI2MCAxMjMwLDI2MEMxMjYwLDI2MCAxMjkwLDIwMCAxMzIwLDIwMEMxMzUwLDIwMCAxMzgwLDI2MCAxNDEwLDI2MEMxNDQwLDI2MCAxNDQwLDIwMCAxNDQwLDIwMEwxNDQwLDMyMEwwLDMyMFoiPjwvcGF0aD48L3N2Zz4=');
|
||||||
|
background-size: 100% 80px;
|
||||||
|
animation: wave-left-to-right 20s ease-in-out infinite;
|
||||||
|
animation-delay: -2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wave-left-to-right {
|
||||||
|
0% {
|
||||||
|
background-position-x: 0;
|
||||||
|
background-position-y: 100%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position-x: 720px;
|
||||||
|
background-position-y: 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position-x: 1440px;
|
||||||
|
background-position-y: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="thing-cell"
|
class="thing-cell"
|
||||||
v-for="item in typeTabList"
|
v-for="item in typeTabList"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:class="{ 'thing-cell-on': type === item.key }"
|
:class="{ 'thing-cell-on': state.type === item.key }"
|
||||||
@click="switchType(item)"
|
@click="switchType(item)"
|
||||||
>
|
>
|
||||||
<template #header>{{ item.name }}</template>
|
<template #header>{{ item.name }}</template>
|
||||||
@@ -16,61 +16,63 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
<n-grid-item span="18">
|
<n-grid-item span="18">
|
||||||
<n-card :bordered="false" size="small" :title="typeTitle" class="proCard">
|
<n-card :bordered="false" size="small" :title="state.typeTitle" class="proCard">
|
||||||
<BasicSetting v-if="type === 1" />
|
<BasicSetting v-if="state.type === 1" />
|
||||||
<SafetySetting v-if="type === 2" />
|
<SafetySetting v-if="state.type === 2" />
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import BasicSetting from './BasicSetting.vue';
|
import BasicSetting from './BasicSetting.vue';
|
||||||
import SafetySetting from './SafetySetting.vue';
|
import SafetySetting from './SafetySetting.vue';
|
||||||
|
|
||||||
const typeTabList = [
|
const typeTabList = [
|
||||||
{
|
{
|
||||||
name: '基本设置',
|
name: '基本设置',
|
||||||
desc: '个人账户信息设置',
|
desc: '个人账户信息设置',
|
||||||
key: 1,
|
key: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '安全设置',
|
name: '安全设置',
|
||||||
desc: '密码,邮箱等设置',
|
desc: '密码,邮箱等设置',
|
||||||
key: 2,
|
key: 2,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const type = ref(1);
|
const state = reactive({
|
||||||
const typeTitle = ref('基本设置');
|
type: 1,
|
||||||
|
typeTitle: '基本设置',
|
||||||
|
});
|
||||||
|
|
||||||
function switchType(e) {
|
function switchType(e) {
|
||||||
type.value = e.key;
|
state.type = e.key;
|
||||||
typeTitle.value = e.name;
|
state.typeTitle = e.name;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.thing-cell {
|
.thing-cell {
|
||||||
margin: 0 -16px 10px;
|
margin: 0 -16px 10px;
|
||||||
padding: 5px 16px;
|
padding: 5px 16px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #f3f3f3;
|
background: #f3f3f3;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.thing-cell-on {
|
.thing-cell-on {
|
||||||
background: #f0faff;
|
background: #f0faff;
|
||||||
|
color: #2d8cf0;
|
||||||
|
|
||||||
|
::v-deep(.n-thing-main .n-thing-header .n-thing-header__title) {
|
||||||
color: #2d8cf0;
|
color: #2d8cf0;
|
||||||
|
|
||||||
::v-deep(.n-thing-main .n-thing-header .n-thing-header__title) {
|
|
||||||
color: #2d8cf0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #f0faff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f0faff;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
class="thing-cell"
|
class="thing-cell"
|
||||||
v-for="item in typeTabList"
|
v-for="item in typeTabList"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:class="{ 'thing-cell-on': type === item.key }"
|
:class="{ 'thing-cell-on': state.type === item.key }"
|
||||||
@click="switchType(item)"
|
@click="switchType(item)"
|
||||||
>
|
>
|
||||||
<template #header>{{ item.name }}</template>
|
<template #header>{{ item.name }}</template>
|
||||||
@@ -16,10 +16,10 @@
|
|||||||
</n-card>
|
</n-card>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
<n-grid-item span="18">
|
<n-grid-item span="18">
|
||||||
<n-card :bordered="false" size="small" :title="typeTitle" class="proCard">
|
<n-card :bordered="false" size="small" :title="state.typeTitle" class="proCard">
|
||||||
<BasicSetting v-if="type === 1" />
|
<BasicSetting v-if="state.type === 1" />
|
||||||
<RevealSetting v-if="type === 2" />
|
<RevealSetting v-if="state.type === 2" />
|
||||||
<EmailSetting v-if="type === 3" />
|
<EmailSetting v-if="state.type === 3" />
|
||||||
</n-card>
|
</n-card>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
|
|||||||
@@ -132,6 +132,7 @@
|
|||||||
import { getMenuList } from '@/api/system/menu';
|
import { getMenuList } from '@/api/system/menu';
|
||||||
import { getTreeItem } from '@/utils';
|
import { getTreeItem } from '@/utils';
|
||||||
import CreateDrawer from './CreateDrawer.vue';
|
import CreateDrawer from './CreateDrawer.vue';
|
||||||
|
import type { ListDate } from '@/api/system/menu';
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
label: {
|
label: {
|
||||||
@@ -155,7 +156,7 @@
|
|||||||
|
|
||||||
let expandedKeys = ref([]);
|
let expandedKeys = ref([]);
|
||||||
|
|
||||||
const treeData = ref([]);
|
const treeData = ref<ListDate[]>([]);
|
||||||
|
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
const subLoading = ref(false);
|
const subLoading = ref(false);
|
||||||
|
|||||||
68
src/views/system/role/CreateModal.vue
Normal file
68
src/views/system/role/CreateModal.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<basicModal @register="modalRegister" ref="modalRef" @on-ok="okModal">
|
||||||
|
<div class="pt-8">
|
||||||
|
<BasicForm @register="registerForm" />
|
||||||
|
</div>
|
||||||
|
</basicModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { FormSchema, useForm } from '@/components/Form';
|
||||||
|
import { basicModal, useModal } from '@/components/Modal';
|
||||||
|
|
||||||
|
const schemas: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
component: 'NInput',
|
||||||
|
label: '角色名称',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入角色名称',
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: '请输入角色名称', trigger: ['blur'] }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'explain',
|
||||||
|
component: 'NInput',
|
||||||
|
label: '角色说明',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
placeholder: '请输入角色角色说明',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'isDefault',
|
||||||
|
component: 'NSwitch',
|
||||||
|
label: '默认角色',
|
||||||
|
componentProps: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [registerForm, { submit }] = useForm({
|
||||||
|
gridProps: { cols: 1 },
|
||||||
|
collapsedRows: 3,
|
||||||
|
labelWidth: 80,
|
||||||
|
layout: 'horizontal',
|
||||||
|
submitButtonText: '保存',
|
||||||
|
showActionButtonGroup: false,
|
||||||
|
schemas,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [modalRegister, { openModal, closeModal, setSubLoading }] = useModal({
|
||||||
|
title: '新增角色',
|
||||||
|
subBtuText: '保存',
|
||||||
|
});
|
||||||
|
|
||||||
|
async function okModal() {
|
||||||
|
const formRes = await submit();
|
||||||
|
if (formRes) {
|
||||||
|
closeModal();
|
||||||
|
console.log('formRes', formRes);
|
||||||
|
} else {
|
||||||
|
setSubLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
openModal,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
76
src/views/system/role/EditModal.vue
Normal file
76
src/views/system/role/EditModal.vue
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
<basicModal @register="modalRegister" ref="modalRef" @on-ok="okModal">
|
||||||
|
<div class="pt-8">
|
||||||
|
<BasicForm @register="registerForm" />
|
||||||
|
</div>
|
||||||
|
</basicModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { nextTick } from 'vue';
|
||||||
|
import { FormSchema, useForm } from '@/components/Form';
|
||||||
|
import { basicModal, useModal } from '@/components/Modal';
|
||||||
|
|
||||||
|
const schemas: FormSchema[] = [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
component: 'NInput',
|
||||||
|
label: '角色名称',
|
||||||
|
componentProps: {
|
||||||
|
placeholder: '请输入角色名称',
|
||||||
|
},
|
||||||
|
rules: [{ required: true, message: '请输入角色名称', trigger: ['blur'] }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'explain',
|
||||||
|
component: 'NInput',
|
||||||
|
label: '角色说明',
|
||||||
|
componentProps: {
|
||||||
|
type: 'textarea',
|
||||||
|
placeholder: '请输入角色角色说明',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'isDefault',
|
||||||
|
component: 'NSwitch',
|
||||||
|
label: '默认角色',
|
||||||
|
componentProps: {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [registerForm, { submit, setFieldsValue }] = useForm({
|
||||||
|
gridProps: { cols: 1 },
|
||||||
|
collapsedRows: 3,
|
||||||
|
labelWidth: 80,
|
||||||
|
layout: 'horizontal',
|
||||||
|
submitButtonText: '保存',
|
||||||
|
showActionButtonGroup: false,
|
||||||
|
schemas,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [modalRegister, { openModal, closeModal, setSubLoading }] = useModal({
|
||||||
|
title: '编辑角色',
|
||||||
|
subBtuText: '保存',
|
||||||
|
});
|
||||||
|
|
||||||
|
function showModal(record: any) {
|
||||||
|
openModal();
|
||||||
|
nextTick(() => {
|
||||||
|
record && setFieldsValue({ ...record });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function okModal() {
|
||||||
|
const formRes = await submit();
|
||||||
|
if (formRes) {
|
||||||
|
closeModal();
|
||||||
|
console.log('formRes', formRes);
|
||||||
|
} else {
|
||||||
|
setSubLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
showModal,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -15,13 +15,13 @@
|
|||||||
@update:checked-row-keys="onCheckedRow"
|
@update:checked-row-keys="onCheckedRow"
|
||||||
>
|
>
|
||||||
<template #tableTitle>
|
<template #tableTitle>
|
||||||
<n-button type="primary">
|
<n-button type="primary" @click="addRole">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon>
|
<n-icon>
|
||||||
<PlusOutlined />
|
<PlusOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
添加角色
|
新增角色
|
||||||
</n-button>
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -59,6 +59,8 @@
|
|||||||
</n-space>
|
</n-space>
|
||||||
</template>
|
</template>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
|
<CreateModal ref="createModalRef" />
|
||||||
|
<EditModal ref="editModalRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -71,23 +73,24 @@
|
|||||||
import { columns } from './columns';
|
import { columns } from './columns';
|
||||||
import { PlusOutlined } from '@vicons/antd';
|
import { PlusOutlined } from '@vicons/antd';
|
||||||
import { getTreeAll } from '@/utils';
|
import { getTreeAll } from '@/utils';
|
||||||
import { useRouter } from 'vue-router';
|
import CreateModal from './CreateModal.vue';
|
||||||
|
import EditModal from './EditModal.vue';
|
||||||
|
import type { ListDate } from '@/api/system/menu';
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const actionRef = ref();
|
const actionRef = ref();
|
||||||
|
const createModalRef = ref();
|
||||||
|
const editModalRef = ref();
|
||||||
const showModal = ref(false);
|
const showModal = ref(false);
|
||||||
const formBtnLoading = ref(false);
|
const formBtnLoading = ref(false);
|
||||||
const checkedAll = ref(false);
|
const checkedAll = ref(false);
|
||||||
const editRoleTitle = ref('');
|
const editRoleTitle = ref('');
|
||||||
const treeData = ref([]);
|
const treeData = ref<ListDate[]>([]);
|
||||||
const expandedKeys = ref([]);
|
const expandedKeys = ref<string[]>([]);
|
||||||
const checkedKeys = ref(['console', 'step-form']);
|
const checkedKeys = ref<string[]>(['console', 'step-form']);
|
||||||
|
|
||||||
const params = reactive({
|
const params = reactive({
|
||||||
pageSize: 5,
|
name: 'NaiveAdmin',
|
||||||
name: 'xiaoMa',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionColumn = reactive({
|
const actionColumn = reactive({
|
||||||
@@ -140,6 +143,10 @@
|
|||||||
return await getRoleList(_params);
|
return await getRoleList(_params);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function addRole() {
|
||||||
|
createModalRef.value.openModal();
|
||||||
|
}
|
||||||
|
|
||||||
function onCheckedRow(rowKeys: any[]) {
|
function onCheckedRow(rowKeys: any[]) {
|
||||||
console.log(rowKeys);
|
console.log(rowKeys);
|
||||||
}
|
}
|
||||||
@@ -161,7 +168,8 @@
|
|||||||
|
|
||||||
function handleEdit(record: Recordable) {
|
function handleEdit(record: Recordable) {
|
||||||
console.log('点击了编辑', record);
|
console.log('点击了编辑', record);
|
||||||
router.push({ name: 'basic-info', params: { id: record.id } });
|
// router.push({ name: 'basic-info', params: { id: record.id } });
|
||||||
|
editModalRef.value.showModal(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDelete(record: Recordable) {
|
function handleDelete(record: Recordable) {
|
||||||
@@ -203,8 +211,8 @@
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const treeMenuList = await getMenuList();
|
const treeMenuList = await getMenuList();
|
||||||
expandedKeys.value = treeMenuList.list.map((item) => item.key);
|
expandedKeys.value = treeMenuList?.list.map((item) => item.key);
|
||||||
treeData.value = treeMenuList.list;
|
treeData.value = treeMenuList?.list;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
24
types/config.d.ts
vendored
24
types/config.d.ts
vendored
@@ -52,8 +52,14 @@ export interface GlobConfig {
|
|||||||
shortName: string;
|
shortName: string;
|
||||||
urlPrefix?: string;
|
urlPrefix?: string;
|
||||||
uploadUrl?: string;
|
uploadUrl?: string;
|
||||||
prodMock: boolean;
|
fileUrl?: string;
|
||||||
imgUrl?: string;
|
}
|
||||||
|
|
||||||
|
export interface LocalConfig {
|
||||||
|
// 生产环境开启 mock
|
||||||
|
useMock: boolean;
|
||||||
|
// 打印 mock 请求信息
|
||||||
|
loggerMock: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobEnvConfig {
|
export interface GlobEnvConfig {
|
||||||
@@ -63,12 +69,14 @@ export interface GlobEnvConfig {
|
|||||||
VITE_GLOB_API_URL: string;
|
VITE_GLOB_API_URL: string;
|
||||||
// 接口前缀
|
// 接口前缀
|
||||||
VITE_GLOB_API_URL_PREFIX?: string;
|
VITE_GLOB_API_URL_PREFIX?: string;
|
||||||
// Project abbreviation
|
// 网站别名
|
||||||
VITE_GLOB_APP_SHORT_NAME: string;
|
VITE_GLOB_APP_SHORT_NAME: string;
|
||||||
// 图片上传地址
|
// 文件上传地址
|
||||||
VITE_GLOB_UPLOAD_URL?: string;
|
VITE_GLOB_UPLOAD_URL?: string;
|
||||||
//图片前缀地址
|
// 文件前缀地址
|
||||||
VITE_GLOB_IMG_URL?: string;
|
VITE_GLOB_FILE_URL?: string;
|
||||||
//生产环境开启mock
|
// 开启 mock
|
||||||
VITE_GLOB_PROD_MOCK: boolean;
|
VITE_USE_MOCK: string;
|
||||||
|
// 是否开启控制台打印 mock 请求信息
|
||||||
|
VITE_LOGGER_MOCK: string;
|
||||||
}
|
}
|
||||||
|
|||||||
7
types/global.d.ts
vendored
7
types/global.d.ts
vendored
@@ -30,6 +30,12 @@ declare global {
|
|||||||
-readonly [P in keyof T]: T[P];
|
-readonly [P in keyof T]: T[P];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare interface InResult<T = any> {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
result: T;
|
||||||
|
}
|
||||||
|
|
||||||
declare type Nullable<T> = T | null;
|
declare type Nullable<T> = T | null;
|
||||||
declare type NonNullable<T> = T extends null | undefined ? never : T;
|
declare type NonNullable<T> = T extends null | undefined ? never : T;
|
||||||
declare type Recordable<T = any> = Record<string, T>;
|
declare type Recordable<T = any> = Record<string, T>;
|
||||||
@@ -60,6 +66,7 @@ declare global {
|
|||||||
declare interface ViteEnv {
|
declare interface ViteEnv {
|
||||||
VITE_PORT: number;
|
VITE_PORT: number;
|
||||||
VITE_USE_MOCK: boolean;
|
VITE_USE_MOCK: boolean;
|
||||||
|
VITE_LOGGER_MOCK: boolean;
|
||||||
VITE_PUBLIC_PATH: string;
|
VITE_PUBLIC_PATH: string;
|
||||||
VITE_GLOB_APP_TITLE: string;
|
VITE_GLOB_APP_TITLE: string;
|
||||||
VITE_GLOB_APP_SHORT_NAME: string;
|
VITE_GLOB_APP_SHORT_NAME: string;
|
||||||
|
|||||||
7
types/images.d.ts
vendored
Normal file
7
types/images.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
declare module '*.svg';
|
||||||
|
declare module '*.png';
|
||||||
|
declare module '*.jpg';
|
||||||
|
declare module '*.jpeg';
|
||||||
|
declare module '*.gif';
|
||||||
|
declare module '*.bmp';
|
||||||
|
declare module '*.tiff';
|
||||||
@@ -22,9 +22,7 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
const root = process.cwd();
|
const root = process.cwd();
|
||||||
const env = loadEnv(mode, root);
|
const env = loadEnv(mode, root);
|
||||||
const viteEnv = wrapperEnv(env);
|
const viteEnv = wrapperEnv(env);
|
||||||
const { VITE_PUBLIC_PATH, VITE_PORT, VITE_GLOB_PROD_MOCK, VITE_PROXY } =
|
const { VITE_PUBLIC_PATH, VITE_PORT, VITE_PROXY } = viteEnv;
|
||||||
viteEnv;
|
|
||||||
const prodMock = VITE_GLOB_PROD_MOCK;
|
|
||||||
const isBuild = command === 'build';
|
const isBuild = command === 'build';
|
||||||
return {
|
return {
|
||||||
base: VITE_PUBLIC_PATH,
|
base: VITE_PUBLIC_PATH,
|
||||||
@@ -42,9 +40,11 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
],
|
],
|
||||||
dedupe: ['vue'],
|
dedupe: ['vue'],
|
||||||
},
|
},
|
||||||
plugins: createVitePlugins(viteEnv, isBuild, prodMock),
|
plugins: createVitePlugins(viteEnv, isBuild),
|
||||||
define: {
|
define: {
|
||||||
|
__APP_ENV__: JSON.stringify(env.APP_ENV),
|
||||||
__APP_INFO__: JSON.stringify(__APP_INFO__),
|
__APP_INFO__: JSON.stringify(__APP_INFO__),
|
||||||
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: false,
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
@@ -56,11 +56,29 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||||||
exclude: ['vue-demi'],
|
exclude: ['vue-demi'],
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
target: 'es2015',
|
target: 'es2020',
|
||||||
cssTarget: 'chrome80',
|
cssTarget: 'chrome80',
|
||||||
outDir: OUTPUT_DIR,
|
outDir: OUTPUT_DIR,
|
||||||
reportCompressedSize: false,
|
reportCompressedSize: false,
|
||||||
chunkSizeWarningLimit: 2000,
|
chunkSizeWarningLimit: 2000,
|
||||||
|
// 构建分包策略
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'naive-ui': ['naive-ui'],
|
||||||
|
'lodash-es': ['lodash-es'],
|
||||||
|
'vue-router': ['vue-router'],
|
||||||
|
'vue-quill': ['@vueup/vue-quill'],
|
||||||
|
'vicons-antd': ['@vicons/antd'],
|
||||||
|
'vicons-ionicons5': ['@vicons/ionicons5'],
|
||||||
|
vuedraggable: ['vuedraggable'],
|
||||||
|
echarts: ['echarts'],
|
||||||
|
vueuse: ['@vueuse/core'],
|
||||||
|
vue: ['vue'],
|
||||||
|
pinia: ['pinia'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user