Compare commits
35 Commits
v5.0.0-bet
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
4c2e01e320 | ||
|
ce9918b21a | ||
|
f314c04a59 | ||
|
8d3b688a5f | ||
|
138def830f | ||
|
546d29c39b | ||
|
cf24e82e53 | ||
|
5dcd7105c0 | ||
|
7cca118cfd | ||
|
97d1e288a9 | ||
|
8d0c30b001 | ||
|
64c1dbb5c4 | ||
|
93e5537332 | ||
|
c2f5c8ee91 | ||
|
73fa762052 | ||
|
8d4588b029 | ||
|
a935696af0 | ||
|
2b081e4eb4 | ||
|
9acc5f156e | ||
|
a4d38a4307 | ||
|
2dd0aa8575 | ||
|
67abb3b2d9 | ||
|
70b0889be9 | ||
|
050fc557a6 | ||
|
879dd6b318 | ||
|
d8d1ad2ab7 | ||
|
95a8604a2f | ||
|
d82f3ec874 | ||
|
c84b7bcca1 | ||
|
010dbcbfe0 | ||
|
935493cce8 | ||
|
95167dcfc0 | ||
|
fc345dc77b | ||
|
0a438082a6 | ||
|
6ad8d08ff4 |
@ -1,7 +1,7 @@
|
|||||||
# 生产环境的环境变量(命名必须以 VITE_ 开头)
|
# 生产环境的环境变量(命名必须以 VITE_ 开头)
|
||||||
|
|
||||||
## 后端接口地址(如果解决跨域问题采用 CORS 就需要写绝对路径)
|
## 后端接口地址(如果解决跨域问题采用 CORS 就需要写绝对路径)
|
||||||
VITE_BASE_URL = https://mock.mengxuegu.com/mock/63218b5fb4c53348ed2bc212/api/v1
|
VITE_BASE_URL = https://apifoxmock.com/m1/2930465-2145633-default/api/v1
|
||||||
|
|
||||||
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/v3-admin-vite/ 域名下就需要填写 /v3-admin-vite/)
|
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/v3-admin-vite/ 域名下就需要填写 /v3-admin-vite/)
|
||||||
VITE_PUBLIC_PATH = /v3-admin-vite/
|
VITE_PUBLIC_PATH = /v3-admin-vite/
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# 预发布环境的环境变量(命名必须以 VITE_ 开头)
|
# 预发布环境的环境变量(命名必须以 VITE_ 开头)
|
||||||
|
|
||||||
## 后端接口地址(如果解决跨域问题采用 CORS 就需要写绝对路径)
|
## 后端接口地址(如果解决跨域问题采用 CORS 就需要写绝对路径)
|
||||||
VITE_BASE_URL = https://mock.mengxuegu.com/mock/63218b5fb4c53348ed2bc212/api/v1
|
VITE_BASE_URL = https://apifoxmock.com/m1/2930465-2145633-default/api/v1
|
||||||
|
|
||||||
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/ 域名下就需要填写 /)
|
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/ 域名下就需要填写 /)
|
||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
2
.github/workflows/deploy.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
version: 9.15.0
|
version: 10.2.0
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pnpm install && pnpm build
|
run: pnpm install && pnpm build
|
||||||
|
1
.vscode/hook.code-snippets
vendored
@ -2,7 +2,6 @@
|
|||||||
"Vue3 Composable 代码结构一键生成": {
|
"Vue3 Composable 代码结构一键生成": {
|
||||||
"prefix": "Vue3 Composable",
|
"prefix": "Vue3 Composable",
|
||||||
"body": [
|
"body": [
|
||||||
"import { ref } from \"vue\"\n",
|
|
||||||
"const refName1 = ref<string>(\"这是一个响应式变量\")\n",
|
"const refName1 = ref<string>(\"这是一个响应式变量\")\n",
|
||||||
"export function useName() {",
|
"export function useName() {",
|
||||||
"\tconst refName2 = ref<string>(\"这是一个响应式变量\")\n",
|
"\tconst refName2 = ref<string>(\"这是一个响应式变量\")\n",
|
||||||
|
27
README.md
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
V3 Admin Vite is a free and open-source foundational solution for backend management systems, based on popular technologies such as Vue3, Vite, TypeScript, Element Plus, and others
|
V3 Admin Vite is a well-crafted backend management system template, built with popular technologies such as Vue3, Vite, TypeScript, and Element Plus
|
||||||
|
|
||||||
## Notifications
|
## Notifications
|
||||||
|
|
||||||
@ -27,6 +27,9 @@ V3 Admin Vite is a free and open-source foundational solution for backend manage
|
|||||||
> [!TIP]
|
> [!TIP]
|
||||||
> Paid services are officially launched! If you don’t want to do it yourself but want to remove TS or other modules, try the lazy package! [Click to check it out](https://github.com/un-pany/v3-admin-vite/issues/225)
|
> Paid services are officially launched! If you don’t want to do it yourself but want to remove TS or other modules, try the lazy package! [Click to check it out](https://github.com/un-pany/v3-admin-vite/issues/225)
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If you have mobile web app needs, try the new open-source template. [MobVue](https://github.com/un-pany/mobvue)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@ -37,7 +40,7 @@ V3 Admin Vite is a free and open-source foundational solution for backend manage
|
|||||||
- Latest version of `Visual Studio Code`
|
- Latest version of `Visual Studio Code`
|
||||||
- Install the recommended plugins in the `.vscode/extensions.json` file
|
- Install the recommended plugins in the `.vscode/extensions.json` file
|
||||||
- `node` 20.x or 22+
|
- `node` 20.x or 22+
|
||||||
- `pnpm` 9+
|
- `pnpm` 9.x or 10+
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@ -133,21 +136,23 @@ pnpm test
|
|||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
**Online Preview**:[github-pages](https://un-pany.github.io/v3-admin-vite)
|
**Online Preview**: [github-pages](https://un-pany.github.io/v3-admin-vite)
|
||||||
|
|
||||||
**Chinese Documentation**:[link](https://juejin.cn/post/7089377403717287972)
|
**Chinese Documentation**: [link](https://juejin.cn/post/7089377403717287972)
|
||||||
|
|
||||||
**Zero to Hero Tutorial**:[link](https://juejin.cn/column/7207659644487139387)
|
**Zero to Hero Tutorial**: [link](https://juejin.cn/column/7207659644487139387)
|
||||||
|
|
||||||
|
**Mobile Web App**: [mobvue](https://github.com/un-pany/mobvue)
|
||||||
|
|
||||||
**Electron Desktop Version**: [v3-electron-vite](https://github.com/un-pany/v3-electron-vite)
|
**Electron Desktop Version**: [v3-electron-vite](https://github.com/un-pany/v3-electron-vite)
|
||||||
|
|
||||||
**Chinese Repository**:[gitee](https://gitee.com/un-pany/v3-admin-vite)
|
**Chinese Repository**: [gitee](https://gitee.com/un-pany/v3-admin-vite)
|
||||||
|
|
||||||
**Optional Group**:[check how to join](https://github.com/un-pany/v3-admin-vite/issues/191)
|
**Optional Group**: [check how to join](https://github.com/un-pany/v3-admin-vite/issues/191)
|
||||||
|
|
||||||
**Donations**:[buy a coffee for the author](https://github.com/un-pany/v3-admin-vite/issues/69)
|
**Donations**: [buy a coffee for the author](https://github.com/un-pany/v3-admin-vite/issues/69)
|
||||||
|
|
||||||
**Releases & Changelog**:[releases](https://github.com/un-pany/v3-admin-vite/releases)
|
**Releases & Changelog**: [releases](https://github.com/un-pany/v3-admin-vite/releases)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -163,7 +168,7 @@ pnpm test
|
|||||||
|
|
||||||
**User Management**: Login, logout demonstration
|
**User Management**: Login, logout demonstration
|
||||||
|
|
||||||
**Permission Management**: Page-level permissions (dynamic routing), button-level permissions (directive permissions, permission functions), route guards
|
**Permission Management**: Page-level permissions (dynamic routing), button-level permissions (permission directives, permission functions), route guards
|
||||||
|
|
||||||
**Multiple Environments**: Development, staging, and production environments
|
**Multiple Environments**: Development, staging, and production environments
|
||||||
|
|
||||||
@ -199,7 +204,7 @@ pnpm test
|
|||||||
|
|
||||||
**CSS Variables**: Primarily controls layout and color in the project
|
**CSS Variables**: Primarily controls layout and color in the project
|
||||||
|
|
||||||
**ESlint**: Code linting and formatting
|
**ESLint**: Code linting and formatting
|
||||||
|
|
||||||
**Axios**: Sends network requests
|
**Axios**: Sends network requests
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
V3 Admin Vite 是一个免费开源的中后台管理系统基础解决方案,基于 Vue3、Vite、TypeScript、Element Plus 等主流技术
|
V3 Admin Vite 是一个精心制作的后台管理系统模板,基于 Vue3、Vite、TypeScript、Element Plus 等主流技术
|
||||||
|
|
||||||
## 通知
|
## 通知
|
||||||
|
|
||||||
@ -27,6 +27,9 @@ V3 Admin Vite 是一个免费开源的中后台管理系统基础解决方案,
|
|||||||
> [!TIP]
|
> [!TIP]
|
||||||
> 正式推出付费服务,如果不想自己动手,但想移除 TS 或其他模块?试试懒人套餐
|
> 正式推出付费服务,如果不想自己动手,但想移除 TS 或其他模块?试试懒人套餐
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> 如果你有移动端 H5 需求,试试新的开源模板。[MobVue](https://github.com/un-pany/mobvue)
|
||||||
|
|
||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@ -37,7 +40,7 @@ V3 Admin Vite 是一个免费开源的中后台管理系统基础解决方案,
|
|||||||
- 新版 `Visual Studio Code`
|
- 新版 `Visual Studio Code`
|
||||||
- 安装 `.vscode/extensions.json` 文件中推荐的插件
|
- 安装 `.vscode/extensions.json` 文件中推荐的插件
|
||||||
- `node` 20.x 或 22+
|
- `node` 20.x 或 22+
|
||||||
- `pnpm` 9+
|
- `pnpm` 9.x 或 10+
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@ -113,7 +116,7 @@ pnpm test
|
|||||||
|
|
||||||
`fix` 修复错误
|
`fix` 修复错误
|
||||||
|
|
||||||
`perf` 优化
|
`perf` 性能优化
|
||||||
|
|
||||||
`refactor` 重构代码
|
`refactor` 重构代码
|
||||||
|
|
||||||
@ -139,7 +142,9 @@ pnpm test
|
|||||||
|
|
||||||
**零基础教程**:[链接](https://juejin.cn/column/7207659644487139387)
|
**零基础教程**:[链接](https://juejin.cn/column/7207659644487139387)
|
||||||
|
|
||||||
**Electron 桌面版**: [v3-electron-vite](https://github.com/un-pany/v3-electron-vite)
|
**移动端 H5**:[mobvue](https://github.com/un-pany/mobvue)
|
||||||
|
|
||||||
|
**Electron 桌面版**:[v3-electron-vite](https://github.com/un-pany/v3-electron-vite)
|
||||||
|
|
||||||
**国内仓库**:[gitee](https://gitee.com/un-pany/v3-admin-vite)
|
**国内仓库**:[gitee](https://gitee.com/un-pany/v3-admin-vite)
|
||||||
|
|
||||||
@ -155,15 +160,15 @@ pnpm test
|
|||||||
|
|
||||||
**详细的注释**:各个配置项都写有尽可能详细的注释
|
**详细的注释**:各个配置项都写有尽可能详细的注释
|
||||||
|
|
||||||
**最新的依赖**: 及时更新所有三方依赖至最新版
|
**最新的依赖**:及时更新所有三方依赖至最新版
|
||||||
|
|
||||||
**有一点规范**: 代码风格统一、命名风格统一、注释风格统一
|
**有一点规范**:代码风格统一、命名风格统一、注释风格统一
|
||||||
|
|
||||||
## 内置功能
|
## 内置功能
|
||||||
|
|
||||||
**用户管理**:登录、登出演示
|
**用户管理**:登录、登出演示
|
||||||
|
|
||||||
**权限管理**:页面级权限(动态路由)、按钮级权限(指令权限、权限函数)、路由守卫
|
**权限管理**:页面级权限(动态路由)、按钮级权限(权限指令、权限函数)、路由守卫
|
||||||
|
|
||||||
**多环境**:开发环境(development)、预发布环境(staging)、生产环境(production)
|
**多环境**:开发环境(development)、预发布环境(staging)、生产环境(production)
|
||||||
|
|
||||||
@ -173,9 +178,9 @@ pnpm test
|
|||||||
|
|
||||||
**首页**:根据不同用户显示不同的 Dashboard 页面
|
**首页**:根据不同用户显示不同的 Dashboard 页面
|
||||||
|
|
||||||
**错误页**: 403、404
|
**错误页**:403、404
|
||||||
|
|
||||||
**兼容移动端**: 布局兼容移动端页面分辨率
|
**兼容移动端**:布局兼容移动端页面分辨率
|
||||||
|
|
||||||
**其他**:SVG 雪碧图、动态侧边栏、动态面包屑、标签页快捷导航、内容区放大与全屏、组合式函数
|
**其他**:SVG 雪碧图、动态侧边栏、动态面包屑、标签页快捷导航、内容区放大与全屏、组合式函数
|
||||||
|
|
||||||
@ -185,7 +190,7 @@ pnpm test
|
|||||||
|
|
||||||
**Element Plus**:Element UI 的 Vue3 版本
|
**Element Plus**:Element UI 的 Vue3 版本
|
||||||
|
|
||||||
**Pinia**: 传说中的 Vuex5
|
**Pinia**:传说中的 Vuex5
|
||||||
|
|
||||||
**Vite**:真的很快
|
**Vite**:真的很快
|
||||||
|
|
||||||
@ -199,7 +204,7 @@ pnpm test
|
|||||||
|
|
||||||
**CSS 变量**:主要控制项目的布局和颜色
|
**CSS 变量**:主要控制项目的布局和颜色
|
||||||
|
|
||||||
**ESlint**:代码校验与格式化
|
**ESLint**:代码校验与格式化
|
||||||
|
|
||||||
**Axios**:发送网络请求(已封装好)
|
**Axios**:发送网络请求(已封装好)
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ export default antfu(
|
|||||||
"no-console": "off",
|
"no-console": "off",
|
||||||
"no-debugger": "off",
|
"no-debugger": "off",
|
||||||
"symbol-description": "off",
|
"symbol-description": "off",
|
||||||
"antfu/if-newline": "off"
|
"antfu/if-newline": "off",
|
||||||
|
"unicorn/no-instanceof-builtins": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
38
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "v3-admin-vite",
|
"name": "v3-admin-vite",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "5.0.0-beta.1",
|
"version": "5.0.0-beta.6",
|
||||||
"description": "A crafted admin template, built with Vue3, Vite, TypeScript, Element Plus, and more",
|
"description": "A crafted admin template, built with Vue3, Vite, TypeScript, Element Plus, and more",
|
||||||
"author": "pany <939630029@qq.com> (https://github.com/pany-ang)",
|
"author": "pany <939630029@qq.com> (https://github.com/pany-ang)",
|
||||||
"repository": "https://github.com/un-pany/v3-admin-vite",
|
"repository": "https://github.com/un-pany/v3-admin-vite",
|
||||||
@ -16,9 +16,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "2.3.1",
|
"@element-plus/icons-vue": "2.3.1",
|
||||||
"axios": "1.7.9",
|
"axios": "1.8.4",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"element-plus": "2.9.0",
|
"element-plus": "2.9.7",
|
||||||
"js-cookie": "3.0.5",
|
"js-cookie": "3.0.5",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"mitt": "3.0.1",
|
"mitt": "3.0.1",
|
||||||
@ -26,37 +26,37 @@
|
|||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"path-browserify": "1.0.1",
|
"path-browserify": "1.0.1",
|
||||||
"path-to-regexp": "8.2.0",
|
"path-to-regexp": "8.2.0",
|
||||||
"pinia": "2.3.0",
|
"pinia": "3.0.2",
|
||||||
"screenfull": "6.0.2",
|
"screenfull": "6.0.2",
|
||||||
"vue": "3.5.13",
|
"vue": "3.5.13",
|
||||||
"vue-router": "4.5.0",
|
"vue-router": "4.5.0",
|
||||||
"vxe-table": "4.6.25"
|
"vxe-table": "4.6.25"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@antfu/eslint-config": "3.11.2",
|
"@antfu/eslint-config": "4.12.0",
|
||||||
"@types/js-cookie": "3.0.6",
|
"@types/js-cookie": "3.0.6",
|
||||||
"@types/lodash-es": "4.17.12",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/node": "22.10.1",
|
"@types/node": "22.14.1",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/path-browserify": "1.0.3",
|
"@types/path-browserify": "1.0.3",
|
||||||
"@vitejs/plugin-vue": "5.2.1",
|
"@vitejs/plugin-vue": "5.2.3",
|
||||||
"@vitejs/plugin-vue-jsx": "4.1.1",
|
"@vitejs/plugin-vue-jsx": "4.1.2",
|
||||||
"@vue/test-utils": "2.4.6",
|
"@vue/test-utils": "2.4.6",
|
||||||
"eslint": "9.16.0",
|
"eslint": "9.24.0",
|
||||||
"eslint-plugin-format": "0.1.3",
|
"eslint-plugin-format": "1.0.1",
|
||||||
"happy-dom": "15.11.7",
|
"happy-dom": "17.4.4",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
"lint-staged": "15.2.10",
|
"lint-staged": "15.5.1",
|
||||||
"sass": "1.78.0",
|
"sass": "1.78.0",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.8.3",
|
||||||
"unocss": "0.65.1",
|
"unocss": "66.1.0-beta.12",
|
||||||
"unplugin-auto-import": "0.18.6",
|
"unplugin-auto-import": "19.1.2",
|
||||||
"unplugin-svg-component": "0.12.1",
|
"unplugin-svg-component": "0.12.1",
|
||||||
"unplugin-vue-components": "0.27.5",
|
"unplugin-vue-components": "28.5.0",
|
||||||
"vite": "6.0.3",
|
"vite": "6.3.2",
|
||||||
"vite-svg-loader": "5.1.0",
|
"vite-svg-loader": "5.1.0",
|
||||||
"vitest": "2.1.8",
|
"vitest": "3.1.1",
|
||||||
"vue-tsc": "2.1.10"
|
"vue-tsc": "2.2.8"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*": "eslint --fix"
|
"*": "eslint --fix"
|
||||||
|
4082
pnpm-lock.yaml
generated
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 8.4 KiB |
@ -1,37 +0,0 @@
|
|||||||
import type * as Table from "./type"
|
|
||||||
import { request } from "@/http/axios"
|
|
||||||
|
|
||||||
/** 增 */
|
|
||||||
export function createTableDataApi(data: Table.CreateOrUpdateTableRequestData) {
|
|
||||||
return request({
|
|
||||||
url: "table",
|
|
||||||
method: "post",
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 删 */
|
|
||||||
export function deleteTableDataApi(id: string) {
|
|
||||||
return request({
|
|
||||||
url: `table/${id}`,
|
|
||||||
method: "delete"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 改 */
|
|
||||||
export function updateTableDataApi(data: Table.CreateOrUpdateTableRequestData) {
|
|
||||||
return request({
|
|
||||||
url: "table",
|
|
||||||
method: "put",
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 查 */
|
|
||||||
export function getTableDataApi(params: Table.TableRequestData) {
|
|
||||||
return request<Table.TableResponseData>({
|
|
||||||
url: "table",
|
|
||||||
method: "get",
|
|
||||||
params
|
|
||||||
})
|
|
||||||
}
|
|
37
src/common/apis/tables/index.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import type * as Tables from "./type"
|
||||||
|
import { request } from "@/http/axios"
|
||||||
|
|
||||||
|
/** 增 */
|
||||||
|
export function createTableDataApi(data: Tables.CreateOrUpdateTableRequestData) {
|
||||||
|
return request({
|
||||||
|
url: "tables",
|
||||||
|
method: "post",
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删 */
|
||||||
|
export function deleteTableDataApi(id: number) {
|
||||||
|
return request({
|
||||||
|
url: `tables/${id}`,
|
||||||
|
method: "delete"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 改 */
|
||||||
|
export function updateTableDataApi(data: Tables.CreateOrUpdateTableRequestData) {
|
||||||
|
return request({
|
||||||
|
url: "tables",
|
||||||
|
method: "put",
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查 */
|
||||||
|
export function getTableDataApi(params: Tables.TableRequestData) {
|
||||||
|
return request<Tables.TableResponseData>({
|
||||||
|
url: "tables",
|
||||||
|
method: "get",
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
export interface CreateOrUpdateTableRequestData {
|
export interface CreateOrUpdateTableRequestData {
|
||||||
id?: string
|
id?: number
|
||||||
username: string
|
username: string
|
||||||
password?: string
|
password?: string
|
||||||
}
|
}
|
||||||
@ -18,7 +18,7 @@ export interface TableRequestData {
|
|||||||
export interface TableData {
|
export interface TableData {
|
||||||
createTime: string
|
createTime: string
|
||||||
email: string
|
email: string
|
||||||
id: string
|
id: number
|
||||||
phone: string
|
phone: string
|
||||||
roles: string
|
roles: string
|
||||||
status: boolean
|
status: boolean
|
@ -1,10 +0,0 @@
|
|||||||
import type * as Login from "./type"
|
|
||||||
import { request } from "@/http/axios"
|
|
||||||
|
|
||||||
/** 获取当前登陆用户详情 */
|
|
||||||
export function getUserInfoApi() {
|
|
||||||
return request<Login.UserInfoResponseData>({
|
|
||||||
url: "users/info",
|
|
||||||
method: "get"
|
|
||||||
})
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export type UserInfoResponseData = ApiResponseData<{ username: string, roles: string[] }>
|
|
10
src/common/apis/users/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type * as Users from "./type"
|
||||||
|
import { request } from "@/http/axios"
|
||||||
|
|
||||||
|
/** 获取当前登录用户详情 */
|
||||||
|
export function getCurrentUserApi() {
|
||||||
|
return request<Users.CurrentUserResponseData>({
|
||||||
|
url: "users/me",
|
||||||
|
method: "get"
|
||||||
|
})
|
||||||
|
}
|
1
src/common/apis/users/type.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type CurrentUserResponseData = ApiResponseData<{ username: string, roles: string[] }>
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 407 KiB After Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 8.4 KiB |
@ -38,8 +38,9 @@ body {
|
|||||||
background-color: var(--v3-body-bg-color);
|
background-color: var(--v3-body-bg-color);
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
font-family: Inter, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑",
|
font-family:
|
||||||
Arial, sans-serif;
|
Inter, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial,
|
||||||
|
sans-serif;
|
||||||
@extend %scrollbar;
|
@extend %scrollbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ElScrollbar } from "element-plus"
|
import type { ElScrollbar } from "element-plus"
|
||||||
import type { RouteRecordName, RouteRecordRaw } from "vue-router"
|
import type { RouteRecordNameGeneric, RouteRecordRaw } from "vue-router"
|
||||||
import { usePermissionStore } from "@/pinia/stores/permission"
|
import { usePermissionStore } from "@/pinia/stores/permission"
|
||||||
import { useDevice } from "@@/composables/useDevice"
|
import { useDevice } from "@@/composables/useDevice"
|
||||||
import { isExternal } from "@@/utils/validate"
|
import { isExternal } from "@@/utils/validate"
|
||||||
@ -20,7 +20,7 @@ const resultRef = ref<InstanceType<typeof Result> | null>(null)
|
|||||||
|
|
||||||
const keyword = ref<string>("")
|
const keyword = ref<string>("")
|
||||||
const result = shallowRef<RouteRecordRaw[]>([])
|
const result = shallowRef<RouteRecordRaw[]>([])
|
||||||
const activeRouteName = ref<RouteRecordName | undefined>(undefined)
|
const activeRouteName = ref<RouteRecordNameGeneric | undefined>(undefined)
|
||||||
/** 是否按下了上键或下键(用于解决和 mouseenter 事件的冲突) */
|
/** 是否按下了上键或下键(用于解决和 mouseenter 事件的冲突) */
|
||||||
const isPressUpOrDown = ref<boolean>(false)
|
const isPressUpOrDown = ref<boolean>(false)
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { RouteRecordName, RouteRecordRaw } from "vue-router"
|
import type { RouteRecordNameGeneric, RouteRecordRaw } from "vue-router"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: RouteRecordRaw[]
|
data: RouteRecordRaw[]
|
||||||
@ -9,7 +9,7 @@ interface Props {
|
|||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
/** 选中的菜单 */
|
/** 选中的菜单 */
|
||||||
const modelValue = defineModel<RouteRecordName | undefined>({ required: true })
|
const modelValue = defineModel<RouteRecordNameGeneric | undefined>({ required: true })
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
|
@ -1,24 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ThemeName } from "@@/composables/useTheme"
|
|
||||||
import { useTheme } from "@@/composables/useTheme"
|
import { useTheme } from "@@/composables/useTheme"
|
||||||
import { MagicStick } from "@element-plus/icons-vue"
|
import { MagicStick } from "@element-plus/icons-vue"
|
||||||
|
|
||||||
const { themeList, activeThemeName, setTheme } = useTheme()
|
const { themeList, activeThemeName, setTheme } = useTheme()
|
||||||
|
|
||||||
function handleChangeTheme({ clientX, clientY }: MouseEvent, themeName: ThemeName) {
|
|
||||||
const maxRadius = Math.hypot(
|
|
||||||
Math.max(clientX, window.innerWidth - clientX),
|
|
||||||
Math.max(clientY, window.innerHeight - clientY)
|
|
||||||
)
|
|
||||||
const style = document.documentElement.style
|
|
||||||
style.setProperty("--v3-theme-x", `${clientX}px`)
|
|
||||||
style.setProperty("--v3-theme-y", `${clientY}px`)
|
|
||||||
style.setProperty("--v3-theme-r", `${maxRadius}px`)
|
|
||||||
const handler = () => {
|
|
||||||
setTheme(themeName)
|
|
||||||
}
|
|
||||||
document.startViewTransition ? document.startViewTransition(handler) : handler()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -36,7 +20,7 @@ function handleChangeTheme({ clientX, clientY }: MouseEvent, themeName: ThemeNam
|
|||||||
v-for="(theme, index) in themeList"
|
v-for="(theme, index) in themeList"
|
||||||
:key="index"
|
:key="index"
|
||||||
:disabled="activeThemeName === theme.name"
|
:disabled="activeThemeName === theme.name"
|
||||||
@click="(e: MouseEvent) => handleChangeTheme(e, theme.name)"
|
@click="(e: MouseEvent) => setTheme(e, theme.name)"
|
||||||
>
|
>
|
||||||
<span>{{ theme.title }}</span>
|
<span>{{ theme.title }}</span>
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
import type { RouteLocationNormalized } from "vue-router"
|
import type { Handler } from "mitt"
|
||||||
import mitt, { type Handler } from "mitt"
|
import type { RouteLocationNormalizedGeneric } from "vue-router"
|
||||||
|
import mitt from "mitt"
|
||||||
|
|
||||||
/** 回调函数的类型 */
|
/** 回调函数的类型 */
|
||||||
type Callback = (route: RouteLocationNormalized) => void
|
type Callback = (route: RouteLocationNormalizedGeneric) => void
|
||||||
|
|
||||||
const emitter = mitt()
|
const emitter = mitt()
|
||||||
|
|
||||||
const key = Symbol("ROUTE_CHANGE")
|
const key = Symbol("ROUTE_CHANGE")
|
||||||
|
|
||||||
let latestRoute: RouteLocationNormalized
|
let latestRoute: RouteLocationNormalizedGeneric
|
||||||
|
|
||||||
/** 设置最新的路由信息,触发路由变化事件 */
|
/** 设置最新的路由信息,触发路由变化事件 */
|
||||||
export function setRouteChange(to: RouteLocationNormalized) {
|
export function setRouteChange(to: RouteLocationNormalizedGeneric) {
|
||||||
// 触发事件
|
// 触发事件
|
||||||
emitter.emit(key, to)
|
emitter.emit(key, to)
|
||||||
// 缓存最新的路由信息
|
// 缓存最新的路由信息
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { getActiveThemeName, setActiveThemeName } from "@@/utils/cache/local-storage"
|
import { getActiveThemeName, setActiveThemeName } from "@@/utils/cache/local-storage"
|
||||||
|
import { setCssVar } from "@@/utils/css"
|
||||||
|
|
||||||
const DEFAULT_THEME_NAME = "normal"
|
const DEFAULT_THEME_NAME = "normal"
|
||||||
|
|
||||||
@ -32,8 +33,18 @@ const themeList: ThemeList[] = [
|
|||||||
const activeThemeName = ref<ThemeName>(getActiveThemeName() || DEFAULT_THEME_NAME)
|
const activeThemeName = ref<ThemeName>(getActiveThemeName() || DEFAULT_THEME_NAME)
|
||||||
|
|
||||||
/** 设置主题 */
|
/** 设置主题 */
|
||||||
function setTheme(value: ThemeName) {
|
function setTheme({ clientX, clientY }: MouseEvent, value: ThemeName) {
|
||||||
activeThemeName.value = value
|
const maxRadius = Math.hypot(
|
||||||
|
Math.max(clientX, window.innerWidth - clientX),
|
||||||
|
Math.max(clientY, window.innerHeight - clientY)
|
||||||
|
)
|
||||||
|
setCssVar("--v3-theme-x", `${clientX}px`)
|
||||||
|
setCssVar("--v3-theme-y", `${clientY}px`)
|
||||||
|
setCssVar("--v3-theme-r", `${maxRadius}px`)
|
||||||
|
const handler = () => {
|
||||||
|
activeThemeName.value = value
|
||||||
|
}
|
||||||
|
document.startViewTransition ? document.startViewTransition(handler) : handler()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在 html 根元素上挂载 class */
|
/** 在 html 根元素上挂载 class */
|
||||||
|
@ -7,7 +7,7 @@ export function checkPermission(permissionRoles: string[]): boolean {
|
|||||||
const { roles } = useUserStore()
|
const { roles } = useUserStore()
|
||||||
return roles.some(role => permissionRoles.includes(role))
|
return roles.some(role => permissionRoles.includes(role))
|
||||||
} else {
|
} else {
|
||||||
console.error("参数必须是一个数组且长度大于 0,参考:checkPermission(['admin','editor'])")
|
console.error("参数必须是一个数组且长度大于 0,参考:checkPermission(['admin', 'editor'])")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ export function isArray<T>(arg: T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 判断是否为字符串 */
|
/** 判断是否为字符串 */
|
||||||
export function isString<T>(str: T) {
|
export function isString(str: unknown) {
|
||||||
return typeof str === "string" || str instanceof String
|
return typeof str === "string" || str instanceof String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,16 +52,18 @@ function createInstance() {
|
|||||||
(error) => {
|
(error) => {
|
||||||
// status 是 HTTP 状态码
|
// status 是 HTTP 状态码
|
||||||
const status = get(error, "response.status")
|
const status = get(error, "response.status")
|
||||||
|
const message = get(error, "response.data.message")
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 400:
|
case 400:
|
||||||
error.message = "请求错误"
|
error.message = "请求错误"
|
||||||
break
|
break
|
||||||
case 401:
|
case 401:
|
||||||
// Token 过期时
|
// Token 过期时
|
||||||
|
error.message = message || "未授权"
|
||||||
logout()
|
logout()
|
||||||
break
|
break
|
||||||
case 403:
|
case 403:
|
||||||
error.message = "拒绝访问"
|
error.message = message || "拒绝访问"
|
||||||
break
|
break
|
||||||
case 404:
|
case 404:
|
||||||
error.message = "请求地址出错"
|
error.message = "请求地址出错"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TagView } from "@/pinia/stores/tags-view"
|
import type { TagView } from "@/pinia/stores/tags-view"
|
||||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw, RouterLink } from "vue-router"
|
import type { RouteLocationNormalizedGeneric, RouteRecordRaw, RouterLink } from "vue-router"
|
||||||
import { usePermissionStore } from "@/pinia/stores/permission"
|
import { usePermissionStore } from "@/pinia/stores/permission"
|
||||||
import { useTagsViewStore } from "@/pinia/stores/tags-view"
|
import { useTagsViewStore } from "@/pinia/stores/tags-view"
|
||||||
import { useRouteListener } from "@@/composables/useRouteListener"
|
import { useRouteListener } from "@@/composables/useRouteListener"
|
||||||
@ -77,7 +77,7 @@ function initTags() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 添加标签页 */
|
/** 添加标签页 */
|
||||||
function addTags(route: RouteLocationNormalizedLoaded) {
|
function addTags(route: RouteLocationNormalizedGeneric) {
|
||||||
if (route.name) {
|
if (route.name) {
|
||||||
tagsViewStore.addVisitedView(route)
|
tagsViewStore.addVisitedView(route)
|
||||||
tagsViewStore.addCachedView(route)
|
tagsViewStore.addCachedView(route)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { CreateOrUpdateTableRequestData, TableData } from "@@/apis/table/type"
|
import type { CreateOrUpdateTableRequestData, TableData } from "@@/apis/tables/type"
|
||||||
import type { FormInstance, FormRules } from "element-plus"
|
import type { FormInstance, FormRules } from "element-plus"
|
||||||
import { createTableDataApi, deleteTableDataApi, getTableDataApi, updateTableDataApi } from "@@/apis/table"
|
import { createTableDataApi, deleteTableDataApi, getTableDataApi, updateTableDataApi } from "@@/apis/tables"
|
||||||
import { usePagination } from "@@/composables/usePagination"
|
import { usePagination } from "@@/composables/usePagination"
|
||||||
import { CirclePlus, Delete, Download, Refresh, RefreshRight, Search } from "@element-plus/icons-vue"
|
import { CirclePlus, Delete, Download, Refresh, RefreshRight, Search } from "@element-plus/icons-vue"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
@ -84,8 +84,8 @@ function getTableData() {
|
|||||||
getTableDataApi({
|
getTableDataApi({
|
||||||
currentPage: paginationData.currentPage,
|
currentPage: paginationData.currentPage,
|
||||||
size: paginationData.pageSize,
|
size: paginationData.pageSize,
|
||||||
username: searchData.username || undefined,
|
username: searchData.username,
|
||||||
phone: searchData.phone || undefined
|
phone: searchData.phone
|
||||||
}).then(({ data }) => {
|
}).then(({ data }) => {
|
||||||
paginationData.total = data.total
|
paginationData.total = data.total
|
||||||
tableData.value = data.list
|
tableData.value = data.list
|
||||||
@ -110,6 +110,12 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
|
<el-alert
|
||||||
|
title="数据来源"
|
||||||
|
type="success"
|
||||||
|
description="由 Apifox 提供在线 Mock,数据不具备真实性,仅供简单的 CRUD 操作演示。"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
<el-card v-loading="loading" shadow="never" class="search-wrapper">
|
<el-card v-loading="loading" shadow="never" class="search-wrapper">
|
||||||
<el-form ref="searchFormRef" :inline="true" :model="searchData">
|
<el-form ref="searchFormRef" :inline="true" :model="searchData">
|
||||||
<el-form-item prop="username" label="用户名">
|
<el-form-item prop="username" label="用户名">
|
||||||
@ -227,6 +233,10 @@ watch([() => paginationData.currentPage, () => paginationData.pageSize], getTabl
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.el-alert {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.search-wrapper {
|
.search-wrapper {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
:deep(.el-card__body) {
|
:deep(.el-card__body) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div uno-padding-20 h-full text-center flex select-none all:transition-400>
|
<div pa-20px h-full text-center flex select-none all:transition-400>
|
||||||
<div ma>
|
<div ma>
|
||||||
<div text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s>
|
<div text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s>
|
||||||
UnoCSS
|
UnoCSS
|
||||||
@ -7,7 +7,7 @@
|
|||||||
<div op30 text-lg fw300 m1 dark:op60>
|
<div op30 text-lg fw300 m1 dark:op60>
|
||||||
该页面是一个 UnoCSS 的使用案例,其他页面依旧采用 Scss
|
该页面是一个 UnoCSS 的使用案例,其他页面依旧采用 Scss
|
||||||
</div>
|
</div>
|
||||||
<div m2 uno-flex-x-center text-lg op30 hover="op80" dark:op60 dark:hover="op80">
|
<div m2 flex-x-center text-lg op30 hover="op80" dark:op60 dark:hover="op80">
|
||||||
<a href="https://antfu.me/posts/reimagine-atomic-css-zh" target="_blank">
|
<a href="https://antfu.me/posts/reimagine-atomic-css-zh" target="_blank">
|
||||||
推荐阅读:重新构想原子化 CSS
|
推荐阅读:重新构想原子化 CSS
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { TableResponseData } from "@@/apis/table/type"
|
import type { TableResponseData } from "@@/apis/tables/type"
|
||||||
import type { ElMessageBoxOptions } from "element-plus"
|
import type { ElMessageBoxOptions } from "element-plus"
|
||||||
import type { VxeFormInstance, VxeFormProps, VxeGridInstance, VxeGridProps, VxeModalInstance, VxeModalProps } from "vxe-table"
|
import type { VxeFormInstance, VxeFormProps, VxeGridInstance, VxeGridProps, VxeModalInstance, VxeModalProps } from "vxe-table"
|
||||||
import { deleteTableDataApi, getTableDataApi } from "@@/apis/table"
|
import { deleteTableDataApi, getTableDataApi } from "@@/apis/tables"
|
||||||
import { RoleColumnSlots } from "./tsx/RoleColumnSlots"
|
import { RoleColumnSlots } from "./tsx/RoleColumnSlots"
|
||||||
import { StatusColumnSlots } from "./tsx/StatusColumnSlots"
|
import { StatusColumnSlots } from "./tsx/StatusColumnSlots"
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ defineOptions({
|
|||||||
|
|
||||||
// #region vxe-grid
|
// #region vxe-grid
|
||||||
interface RowMeta {
|
interface RowMeta {
|
||||||
id: string
|
id: number
|
||||||
username: string
|
username: string
|
||||||
roles: string
|
roles: string
|
||||||
phone: string
|
phone: string
|
||||||
@ -164,8 +164,8 @@ const xGridOpt: VxeGridProps = reactive({
|
|||||||
}
|
}
|
||||||
// 接口需要的参数
|
// 接口需要的参数
|
||||||
const params = {
|
const params = {
|
||||||
username: form.username || undefined,
|
username: form.username || "",
|
||||||
phone: form.phone || undefined,
|
phone: form.phone || "",
|
||||||
size: page.pageSize,
|
size: page.pageSize,
|
||||||
currentPage: page.currentPage
|
currentPage: page.currentPage
|
||||||
}
|
}
|
||||||
@ -382,6 +382,12 @@ const crudStore = reactive({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
|
<el-alert
|
||||||
|
title="数据来源"
|
||||||
|
type="success"
|
||||||
|
description="由 Apifox 提供在线 Mock,数据不具备真实性,仅供简单的 CRUD 操作演示。"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<vxe-grid ref="xGridDom" v-bind="xGridOpt">
|
<vxe-grid ref="xGridDom" v-bind="xGridOpt">
|
||||||
<!-- 左侧按钮列表 -->
|
<!-- 左侧按钮列表 -->
|
||||||
@ -410,3 +416,9 @@ const crudStore = reactive({
|
|||||||
</vxe-modal>
|
</vxe-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.el-alert {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import type * as Login from "./type"
|
import type * as Auth from "./type"
|
||||||
import { request } from "@/http/axios"
|
import { request } from "@/http/axios"
|
||||||
|
|
||||||
/** 获取登录验证码 */
|
/** 获取登录验证码 */
|
||||||
export function getLoginCodeApi() {
|
export function getCaptchaApi() {
|
||||||
return request<Login.LoginCodeResponseData>({
|
return request<Auth.CaptchaResponseData>({
|
||||||
url: "login/code",
|
url: "auth/captcha",
|
||||||
method: "get"
|
method: "get"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录并返回 Token */
|
/** 登录并返回 Token */
|
||||||
export function loginApi(data: Login.LoginRequestData) {
|
export function loginApi(data: Auth.LoginRequestData) {
|
||||||
return request<Login.LoginResponseData>({
|
return request<Auth.LoginResponseData>({
|
||||||
url: "users/login",
|
url: "auth/login",
|
||||||
method: "post",
|
method: "post",
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
|
@ -7,6 +7,6 @@ export interface LoginRequestData {
|
|||||||
code: string
|
code: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LoginCodeResponseData = ApiResponseData<string>
|
export type CaptchaResponseData = ApiResponseData<string>
|
||||||
|
|
||||||
export type LoginResponseData = ApiResponseData<{ token: string }>
|
export type LoginResponseData = ApiResponseData<{ token: string }>
|
||||||
|
@ -5,7 +5,7 @@ import { useSettingsStore } from "@/pinia/stores/settings"
|
|||||||
import { useUserStore } from "@/pinia/stores/user"
|
import { useUserStore } from "@/pinia/stores/user"
|
||||||
import ThemeSwitch from "@@/components/ThemeSwitch/index.vue"
|
import ThemeSwitch from "@@/components/ThemeSwitch/index.vue"
|
||||||
import { Key, Loading, Lock, Picture, User } from "@element-plus/icons-vue"
|
import { Key, Loading, Lock, Picture, User } from "@element-plus/icons-vue"
|
||||||
import { getLoginCodeApi, loginApi } from "./apis"
|
import { getCaptchaApi, loginApi } from "./apis"
|
||||||
import Owl from "./components/Owl.vue"
|
import Owl from "./components/Owl.vue"
|
||||||
import { useFocus } from "./composables/useFocus"
|
import { useFocus } from "./composables/useFocus"
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ function createCode() {
|
|||||||
// 清空验证图片
|
// 清空验证图片
|
||||||
codeUrl.value = ""
|
codeUrl.value = ""
|
||||||
// 获取验证码图片
|
// 获取验证码图片
|
||||||
getLoginCodeApi().then((res) => {
|
getCaptchaApi().then((res) => {
|
||||||
codeUrl.value = res.data
|
codeUrl.value = res.data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { RouteLocationNormalized } from "vue-router"
|
import type { RouteLocationNormalizedGeneric } from "vue-router"
|
||||||
import { pinia } from "@/pinia"
|
import { pinia } from "@/pinia"
|
||||||
import { getCachedViews, getVisitedViews, setCachedViews, setVisitedViews } from "@@/utils/cache/local-storage"
|
import { getCachedViews, getVisitedViews, setCachedViews, setVisitedViews } from "@@/utils/cache/local-storage"
|
||||||
import { useSettingsStore } from "./settings"
|
import { useSettingsStore } from "./settings"
|
||||||
|
|
||||||
export type TagView = Partial<RouteLocationNormalized>
|
export type TagView = Partial<RouteLocationNormalizedGeneric>
|
||||||
|
|
||||||
export const useTagsViewStore = defineStore("tags-view", () => {
|
export const useTagsViewStore = defineStore("tags-view", () => {
|
||||||
const { cacheTagsView } = useSettingsStore()
|
const { cacheTagsView } = useSettingsStore()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { pinia } from "@/pinia"
|
import { pinia } from "@/pinia"
|
||||||
import { resetRouter } from "@/router"
|
import { resetRouter } from "@/router"
|
||||||
import { routerConfig } from "@/router/config"
|
import { routerConfig } from "@/router/config"
|
||||||
import { getUserInfoApi } from "@@/apis/user"
|
import { getCurrentUserApi } from "@@/apis/users"
|
||||||
import { setToken as _setToken, getToken, removeToken } from "@@/utils/cache/cookies"
|
import { setToken as _setToken, getToken, removeToken } from "@@/utils/cache/cookies"
|
||||||
import { useSettingsStore } from "./settings"
|
import { useSettingsStore } from "./settings"
|
||||||
import { useTagsViewStore } from "./tags-view"
|
import { useTagsViewStore } from "./tags-view"
|
||||||
@ -15,21 +15,21 @@ export const useUserStore = defineStore("user", () => {
|
|||||||
const settingsStore = useSettingsStore()
|
const settingsStore = useSettingsStore()
|
||||||
|
|
||||||
// 设置 Token
|
// 设置 Token
|
||||||
const setToken = async (value: string) => {
|
const setToken = (value: string) => {
|
||||||
_setToken(value)
|
_setToken(value)
|
||||||
token.value = value
|
token.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户详情
|
// 获取用户详情
|
||||||
const getInfo = async () => {
|
const getInfo = async () => {
|
||||||
const { data } = await getUserInfoApi()
|
const { data } = await getCurrentUserApi()
|
||||||
username.value = data.username
|
username.value = data.username
|
||||||
// 验证返回的 roles 是否为一个非空数组,否则塞入一个没有任何作用的默认角色,防止路由守卫逻辑进入无限循环
|
// 验证返回的 roles 是否为一个非空数组,否则塞入一个没有任何作用的默认角色,防止路由守卫逻辑进入无限循环
|
||||||
roles.value = data.roles?.length > 0 ? data.roles : routerConfig.defaultRoles
|
roles.value = data.roles?.length > 0 ? data.roles : routerConfig.defaultRoles
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟角色变化
|
// 模拟角色变化
|
||||||
const changeRoles = async (role: string) => {
|
const changeRoles = (role: string) => {
|
||||||
const newToken = `token-${role}`
|
const newToken = `token-${role}`
|
||||||
token.value = newToken
|
token.value = newToken
|
||||||
_setToken(newToken)
|
_setToken(newToken)
|
||||||
|
@ -14,7 +14,7 @@ const permission: Directive = {
|
|||||||
const hasPermission = roles.some(role => permissionRoles.includes(role))
|
const hasPermission = roles.some(role => permissionRoles.includes(role))
|
||||||
hasPermission || el.parentNode?.removeChild(el)
|
hasPermission || el.parentNode?.removeChild(el)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`参数必须是一个数组且长度大于 0,参考:v-permission="['admin','editor']"`)
|
throw new Error(`参数必须是一个数组且长度大于 0,参考:v-permission="['admin', 'editor']"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,23 +9,26 @@ import { getToken } from "@@/utils/cache/cookies"
|
|||||||
import NProgress from "nprogress"
|
import NProgress from "nprogress"
|
||||||
|
|
||||||
NProgress.configure({ showSpinner: false })
|
NProgress.configure({ showSpinner: false })
|
||||||
|
|
||||||
const { setTitle } = useTitle()
|
const { setTitle } = useTitle()
|
||||||
|
|
||||||
|
const LOGIN_PATH = "/login"
|
||||||
|
|
||||||
export function registerNavigationGuard(router: Router) {
|
export function registerNavigationGuard(router: Router) {
|
||||||
// 全局前置守卫
|
// 全局前置守卫
|
||||||
router.beforeEach(async (to, _from) => {
|
router.beforeEach(async (to, _from) => {
|
||||||
NProgress.start()
|
NProgress.start()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
// 如果没有登陆
|
// 如果没有登录
|
||||||
if (!getToken()) {
|
if (!getToken()) {
|
||||||
// 如果在免登录的白名单中,则直接进入
|
// 如果在免登录的白名单中,则直接进入
|
||||||
if (isWhiteList(to)) return true
|
if (isWhiteList(to)) return true
|
||||||
// 其他没有访问权限的页面将被重定向到登录页面
|
// 其他没有访问权限的页面将被重定向到登录页面
|
||||||
return "/login"
|
return LOGIN_PATH
|
||||||
}
|
}
|
||||||
// 如果已经登录,并准备进入 Login 页面,则重定向到主页
|
// 如果已经登录,并准备进入 Login 页面,则重定向到主页
|
||||||
if (to.path === "/login") return "/"
|
if (to.path === LOGIN_PATH) return "/"
|
||||||
// 如果用户已经获得其权限角色
|
// 如果用户已经获得其权限角色
|
||||||
if (userStore.roles.length !== 0) return true
|
if (userStore.roles.length !== 0) return true
|
||||||
// 否则要重新获取权限角色
|
// 否则要重新获取权限角色
|
||||||
@ -43,7 +46,7 @@ export function registerNavigationGuard(router: Router) {
|
|||||||
// 过程中发生任何错误,都直接重置 Token,并重定向到登录页面
|
// 过程中发生任何错误,都直接重置 Token,并重定向到登录页面
|
||||||
userStore.resetToken()
|
userStore.resetToken()
|
||||||
ElMessage.error((error as Error).message || "路由守卫发生错误")
|
ElMessage.error((error as Error).message || "路由守卫发生错误")
|
||||||
return "/login"
|
return LOGIN_PATH
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { RouteLocationNormalized, RouteRecordNameGeneric } from "vue-router"
|
import type { RouteLocationNormalizedGeneric, RouteRecordNameGeneric } from "vue-router"
|
||||||
|
|
||||||
/** 免登录白名单(匹配路由 path) */
|
/** 免登录白名单(匹配路由 path) */
|
||||||
const whiteListByPath: string[] = ["/login"]
|
const whiteListByPath: string[] = ["/login"]
|
||||||
@ -7,7 +7,7 @@ const whiteListByPath: string[] = ["/login"]
|
|||||||
const whiteListByName: RouteRecordNameGeneric[] = []
|
const whiteListByName: RouteRecordNameGeneric[] = []
|
||||||
|
|
||||||
/** 判断是否在白名单 */
|
/** 判断是否在白名单 */
|
||||||
export function isWhiteList(to: RouteLocationNormalized) {
|
export function isWhiteList(to: RouteLocationNormalizedGeneric) {
|
||||||
// path 和 name 任意一个匹配上即可
|
// path 和 name 任意一个匹配上即可
|
||||||
return whiteListByPath.includes(to.path) || whiteListByName.includes(to.name)
|
return whiteListByPath.includes(to.path) || whiteListByName.includes(to.name)
|
||||||
}
|
}
|
||||||
|
2
types/auto/auto-imports.d.ts
vendored
@ -87,6 +87,6 @@ declare global {
|
|||||||
// for type re-export
|
// for type re-export
|
||||||
declare global {
|
declare global {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
import('vue')
|
import('vue')
|
||||||
}
|
}
|
||||||
|
2
types/auto/components.d.ts
vendored
@ -2,11 +2,13 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// Generated by unplugin-vue-components
|
// Generated by unplugin-vue-components
|
||||||
// Read more: https://github.com/vuejs/core/pull/3399
|
// Read more: https://github.com/vuejs/core/pull/3399
|
||||||
|
// biome-ignore lint: disable
|
||||||
export {}
|
export {}
|
||||||
|
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||||
ElAside: typeof import('element-plus/es')['ElAside']
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
ElBacktop: typeof import('element-plus/es')['ElBacktop']
|
ElBacktop: typeof import('element-plus/es')['ElBacktop']
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
import { defineConfig, presetAttributify, presetUno } from "unocss"
|
import { defineConfig, presetAttributify, presetWind3 } from "unocss"
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// 预设
|
// 预设
|
||||||
presets: [
|
presets: [
|
||||||
// 属性化模式 & 无值的属性模式
|
// 属性化模式 & 无值的属性模式
|
||||||
presetAttributify(),
|
presetAttributify({
|
||||||
|
prefix: "un-",
|
||||||
|
prefixedOnly: false
|
||||||
|
}),
|
||||||
// 默认预设
|
// 默认预设
|
||||||
presetUno({
|
presetWind3({
|
||||||
important: "#app"
|
important: "#app"
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
// 自定义规则
|
// 自定义规则
|
||||||
rules: [["uno-padding-20", { padding: "20px" }]],
|
rules: [],
|
||||||
// 自定义快捷方式
|
// 自定义快捷方式
|
||||||
shortcuts: {
|
shortcuts: {
|
||||||
"uno-wh-full": "w-full h-full",
|
"wh-full": "w-full h-full",
|
||||||
"uno-flex-center": "flex justify-center items-center",
|
"flex-center": "flex justify-center items-center",
|
||||||
"uno-flex-x-center": "flex justify-center",
|
"flex-x-center": "flex justify-center",
|
||||||
"uno-flex-y-center": "flex items-center"
|
"flex-y-center": "flex items-center"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -38,7 +38,7 @@ export default defineConfig(({ mode }) => {
|
|||||||
// 反向代理
|
// 反向代理
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api/v1": {
|
"/api/v1": {
|
||||||
target: "https://mock.mengxuegu.com/mock/63218b5fb4c53348ed2bc212",
|
target: "https://apifoxmock.com/m1/2930465-2145633-default",
|
||||||
// 是否为 WebSocket
|
// 是否为 WebSocket
|
||||||
ws: false,
|
ws: false,
|
||||||
// 是否允许跨域
|
// 是否允许跨域
|
||||||
@ -96,7 +96,22 @@ export default defineConfig(({ mode }) => {
|
|||||||
// 支持 JSX、TSX 语法
|
// 支持 JSX、TSX 语法
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
// 支持将 SVG 文件导入为 Vue 组件
|
// 支持将 SVG 文件导入为 Vue 组件
|
||||||
svgLoader({ defaultImport: "url" }),
|
svgLoader({
|
||||||
|
defaultImport: "url",
|
||||||
|
svgoConfig: {
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: "preset-default",
|
||||||
|
params: {
|
||||||
|
overrides: {
|
||||||
|
// @see https://github.com/svg/svgo/issues/1128
|
||||||
|
removeViewBox: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}),
|
||||||
// 自动生成 SvgIcon 组件和 SVG 雪碧图
|
// 自动生成 SvgIcon 组件和 SVG 雪碧图
|
||||||
SvgComponent({
|
SvgComponent({
|
||||||
iconDir: [resolve(__dirname, "src/common/assets/icons")],
|
iconDir: [resolve(__dirname, "src/common/assets/icons")],
|
||||||
|