Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| feff838650 | |||
| 029dea0100 | |||
| 5b71369251 | |||
|
|
05ab89d03f | ||
|
|
f49503a924 | ||
|
|
39465dcaa3 | ||
|
|
99c37d7de4 | ||
|
|
2642862569 | ||
|
|
9d570a808c | ||
| c0c5ba8efd | |||
| ca25285237 | |||
|
|
c70d1adbf9 | ||
| 70510894ef | |||
| c0a5c2dffe | |||
| 3dfa97e785 | |||
|
|
f99c8f1b5a | ||
|
|
724f60eaf6 | ||
| fcceaf8f51 | |||
| b8a84a3a08 | |||
| 81dbea8793 | |||
|
|
b2a1658e37 | ||
|
|
5264cf226f | ||
|
|
64648d0c1d |
BIN
.image/screenshot/000登录页面-H5.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 286 KiB After Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 375 KiB After Width: | Height: | Size: 305 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 849 KiB After Width: | Height: | Size: 808 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 131 KiB |
28
CHANGELOG.md
@@ -1,3 +1,31 @@
|
||||
## [v3.0.1](https://github.com/Charles7c/continew-admin-ui/compare/v3.0.0...v3.0.1) (2024-05-03)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
* 新增表格全屏、尺寸工具 ([b8a84a3](https://github.com/Charles7c/continew-admin-ui/commit/b8a84a3a0890d4f6d0e39ecbe50c4f645bd0f106))
|
||||
* 新增验证码超时显示效果,超时后显示已过期请刷新 (GitHub#14) ([f99c8f1](https://github.com/Charles7c/continew-admin-ui/commit/f99c8f1b5a521f987b2822352f976fb0b1dc93b3))
|
||||
* 文件管理增加资源统计,统计总存储量、各类型文件存储占用 (GitHub#15) ([c70d1ad](https://github.com/Charles7c/continew-admin-ui/commit/c70d1adbf922d28853bf4e6cf8cc4e14ad5b0ac7))
|
||||
|
||||
### 💎 功能优化
|
||||
|
||||
- 统一性别约束/统一上级部门为必填 ([5264cf2](https://github.com/Charles7c/continew-admin-ui/commit/5264cf226fa3acd1398d9309e6a97d4d45b64850))
|
||||
- 一级部门不能修改上级部门 ([b2a1658](https://github.com/Charles7c/continew-admin-ui/commit/b2a1658e3730078cf2fbeb3032c23c0922544594))
|
||||
- 优化根据选中部门查询用户的点击效果 ([ca25285](https://github.com/Charles7c/continew-admin-ui/commit/ca252852373840b000c1f65ab925d18335a71fcb)) ([99c37d7](https://github.com/Charles7c/continew-admin-ui/commit/99c37d7de4a245836f89c29cf4b638032efae31f))
|
||||
- 登录页面,H5 端排版更换 ([05ab89d](https://github.com/Charles7c/continew-admin-ui/commit/05ab89d03fe6401994698ad9ecdeb8540ec49553))
|
||||
- 优化 queryForm 的 Query 类型使用 ([5b71369](https://github.com/Charles7c/continew-admin-ui/commit/5b713692516db586f2d401a163192c62a963137a))
|
||||
|
||||
|
||||
### 🐛 问题修复
|
||||
|
||||
- 修复 Markdown 样式加载错误,改为全局统一加载 (GitHub#9) ([64648d0](https://github.com/Charles7c/continew-admin-ui/commit/64648d0c1d897d6e426199e7924ede9dfb40e8b8))
|
||||
- 修复由于文件组件名称错误导致的侧边栏筛选功能失效 ([81dbea8](https://github.com/Charles7c/continew-admin-ui/commit/81dbea879377054e3646c2d07b51c3352501bbce))
|
||||
- 修复文件管理数据不刷新和批量操作选中问题 (GitHub#13) ([724f60e](https://github.com/Charles7c/continew-admin-ui/commit/724f60eaf6b076cfb165ca0b1028c461146495ad))
|
||||
- 修复文件重命名时不能回显原值的问题 ([3dfa97e](https://github.com/Charles7c/continew-admin-ui/commit/3dfa97e785acb42edd3798117f7e8eea326b4b64))
|
||||
- 修复修改公告时保存按钮点击无效的问题 ([c0a5c2d](https://github.com/Charles7c/continew-admin-ui/commit/c0a5c2dffe50905b8610fbd066b8eecd5a4cbe89))
|
||||
- 修复账号管理、安全设置路由处理错误 ([c0c5ba8](https://github.com/Charles7c/continew-admin-ui/commit/c0c5ba8efdab009e7e38ad9a8f68a655aba28718))
|
||||
- 修复首页卡片显示问题 ([39465dc](https://github.com/Charles7c/continew-admin-ui/commit/39465dcaa38c9d79c820583a1dd82978e5588dec))
|
||||
- 修复 H5 下登录页面错位显示 ([9d570a8](https://github.com/Charles7c/continew-admin-ui/commit/9d570a808ce1a15a1513eac0e9ec355d683febef))
|
||||
|
||||
## v3.0.0 (2024-04-27)
|
||||
|
||||
### ✨ 新特性
|
||||
|
||||
84
README.md
@@ -4,7 +4,7 @@
|
||||
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License" />
|
||||
</a>
|
||||
<a href="https://github.com/Charles7c/continew-admin-ui" target="_blank">
|
||||
<img src="https://img.shields.io/badge/RELEASE-v3.0.0-%23ff3f59.svg" alt="Release" />
|
||||
<img src="https://img.shields.io/badge/RELEASE-v3.0.1-%23ff3f59.svg" alt="Release" />
|
||||
</a>
|
||||
<a href="https://github.com/Charles7c/continew-admin" target="_blank">
|
||||
<img src="https://img.shields.io/github/stars/Charles7c/continew-admin?style=social" alt="GitHub stars" />
|
||||
@@ -90,7 +90,7 @@ ContiNew Admin(Continue New Admin)持续迭代优化的前后端分离中后
|
||||
## 系统截图
|
||||
|
||||
> [!TIP]
|
||||
> 受篇幅长度及功能更新频率影响,下方仅为系统 **部分** 功能于 **2024年4月27日** 进行的截图,更多新增功能及细节请登录演示环境或 clone 代码到本地启动查看。
|
||||
> 受篇幅长度及功能更新频率影响,下方仅为系统 **部分** 功能于 **2024年5月3日** 进行的截图,更多新增功能及细节请登录演示环境或 clone 代码到本地启动查看。
|
||||
|
||||
<table border="1" cellpadding="1" cellspacing="1" style="width: 500px">
|
||||
<tbody>
|
||||
@@ -180,6 +180,86 @@ pnpm i
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
continew-admin-ui # 前端项目
|
||||
├─ config # Vite 插件配置
|
||||
├─ public # 公共静态资源(favicon.ico、logo.svg)
|
||||
├─ src
|
||||
│ ├─ apis # 请求接口
|
||||
│ │ ├─ auth # 认证模块
|
||||
│ │ ├─ common # 公共模块
|
||||
│ │ ├─ monitor # 系统监控模块
|
||||
│ │ ├─ system # 系统管理模块
|
||||
│ │ └─ tool # 系统工具模块
|
||||
│ ├─ assets # 静态资源
|
||||
│ │ ├─ icons # 图标资源
|
||||
│ │ ├─ images # 图片资源
|
||||
│ │ └─ fonts # 字体资源
|
||||
│ ├─ components # 通用业务组件
|
||||
│ ├─ config # 全局配置(包含 echarts 主题)
|
||||
│ │ └─ settings.json # 配置文件
|
||||
│ ├─ directives # 指令集(如需,可自行补充)
|
||||
│ ├─ hooks # 全局 hooks
|
||||
│ ├─ layout # 布局
|
||||
│ ├─ mock # 模拟数据
|
||||
│ ├─ router # 路由配置
|
||||
│ ├─ stores # 状态管理中心
|
||||
│ ├─ types # TypeScript 类型
|
||||
│ ├─ utils # 工具库(mock 全局开启/关闭)
|
||||
│ ├─ views # 页面
|
||||
│ │ ├─ default # 默认页面
|
||||
│ │ ├─ home # 首页模块
|
||||
│ │ ├─ login # 登录模块
|
||||
│ │ ├─ monitor # 系统监控
|
||||
│ │ │ ├─ log # 系统日志
|
||||
│ │ │ │ ├─ login # 登录日志
|
||||
│ │ │ │ ├─ operation # 操作日志
|
||||
│ │ │ │ └─ index
|
||||
│ │ │ └─ online # 在线用户
|
||||
│ │ ├─ setting # 设置
|
||||
│ │ │ ├─ profile # 账号管理
|
||||
│ │ │ └─ security # 安全设置
|
||||
│ │ ├─ tool # 系统工具
|
||||
│ │ │ └─ generator # 代码生成
|
||||
│ │ └─ system # 系统管理
|
||||
│ │ ├─ config # 系统配置
|
||||
│ │ ├─ dept # 部门管理
|
||||
│ │ ├─ dict # 字典管理
|
||||
│ │ ├─ file # 文件管理
|
||||
│ │ ├─ menu # 菜单管理
|
||||
│ │ ├─ notice # 通知公告
|
||||
│ │ ├─ role # 角色管理
|
||||
│ │ ├─ storage # 存储管理
|
||||
│ │ └─ user # 用户管理
|
||||
│ ├─ App.vue
|
||||
│ └─ main.ts
|
||||
├─ .env.development
|
||||
├─ .env.production
|
||||
├─ .env.test
|
||||
├─ .eslintignore
|
||||
├─ .eslintrc.cjs
|
||||
├─ .prettierignore
|
||||
├─ .prettierrc.js
|
||||
├─ index.html
|
||||
├─ package.json
|
||||
├─ package-lock.json
|
||||
├─ pnpm-lock.yaml
|
||||
├─ tsconfig.json
|
||||
├─ vite.config.ts
|
||||
├─ .gitignore(Git 忽略文件相关配置文件)
|
||||
├─ .gitee(Gitee 相关配置目录,实际开发时直接删除)
|
||||
├─ .github(GitHub 相关配置目录,实际开发时直接删除)
|
||||
├─ .idea
|
||||
│ └─ icon.png(IDEA 项目图标,实际开发时直接删除)
|
||||
├─ .image(截图目录,实际开发时直接删除)
|
||||
├─ .vscode(VSCode 配置目录)
|
||||
├─ LICENSE(开源协议文件)
|
||||
├─ CHANGELOG.md(更新日志文件,实际开发时直接删除)
|
||||
└─ README.md(项目 README 文件,实际开发时替换为真实内容)
|
||||
```
|
||||
|
||||
## 贡献指南
|
||||
|
||||
ContiNew Admin 致力于提供开箱即用,持续舒适的开发体验。作为一个开源项目,Creator 的初心是希望 ContiNew Admin 依托开源协作模式,提升技术透明度、放大集体智慧、共创优秀实践,源源不断地为企业级项目开发提供助力。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "continew-admin-ui",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
|
||||
@@ -5,7 +5,7 @@ const BASE_URL = '/captcha'
|
||||
|
||||
/** @desc 获取图片验证码 */
|
||||
export function getImageCaptcha() {
|
||||
return http.get<Common.ImageCaptchaResp>(`${BASE_URL}/img`)
|
||||
return http.get<Common.ImageCaptchaResp>(`${BASE_URL}/image`)
|
||||
}
|
||||
|
||||
/** @desc 获取短信验证码 */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
export interface ImageCaptchaResp {
|
||||
uuid: string
|
||||
img: string
|
||||
expireTime: number
|
||||
}
|
||||
|
||||
/** 仪表盘访问趋势类型 */
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as Monitor from './type'
|
||||
const BASE_URL = '/monitor/online'
|
||||
|
||||
/** @desc 查询在线用户列表 */
|
||||
export function listOnlineUser(query: Monitor.OnlineUserQuery) {
|
||||
export function listOnlineUser(query: Monitor.OnlineUserPageQuery) {
|
||||
return http.get<PageRes<Monitor.OnlineUserResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,10 +13,12 @@ export interface OnlineUserResp {
|
||||
createUserString: string
|
||||
createTime: string
|
||||
}
|
||||
export interface OnlineUserQuery extends PageQuery {
|
||||
export interface OnlineUserQuery {
|
||||
nickname?: string
|
||||
loginTime?: string
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface OnlineUserPageQuery extends OnlineUserQuery, PageQuery {}
|
||||
|
||||
/** 系统日志类型 */
|
||||
export interface LogResp {
|
||||
@@ -52,4 +54,4 @@ export interface LogQuery {
|
||||
status?: number
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface LogPageQuery extends PageQuery, LogQuery {}
|
||||
export interface LogPageQuery extends LogQuery, PageQuery {}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as System from './type'
|
||||
const BASE_URL = '/system/dict'
|
||||
|
||||
/** @desc 查询字典列表 */
|
||||
export function listDict(query: System.DictQuery) {
|
||||
export function listDict(query: System.DictPageQuery) {
|
||||
return http.get<PageRes<System.DictResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export function deleteDict(id: string) {
|
||||
}
|
||||
|
||||
/** @desc 查询字典项列表 */
|
||||
export function listDictItem(query: System.DictItemQuery) {
|
||||
export function listDictItem(query: System.DictItemPageQuery) {
|
||||
return http.get<PageRes<System.DictItemResp[]>>(`${BASE_URL}/item`, query)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as System from './type'
|
||||
const BASE_URL = '/system/file'
|
||||
|
||||
/** @desc 查询文件列表 */
|
||||
export function listFile(query: System.FileQuery) {
|
||||
export function listFile(query: System.FilePageQuery) {
|
||||
return http.get<PageRes<System.FileItem[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
@@ -17,3 +17,8 @@ export function updateFile(data: any, id: string) {
|
||||
export function deleteFile(ids: string | Array<string>) {
|
||||
return http.del(`${BASE_URL}/${ids}`)
|
||||
}
|
||||
|
||||
/** @desc 查询文件资源统计统计 */
|
||||
export function getFileStatistics() {
|
||||
return http.get<System.FileStatisticsResp>(`${BASE_URL}/statistics`)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as System from './type'
|
||||
const BASE_URL = '/system/notice'
|
||||
|
||||
/** @desc 查询公告列表 */
|
||||
export function listNotice(query: System.NoticeQuery) {
|
||||
export function listNotice(query: System.NoticePageQuery) {
|
||||
return http.get<PageRes<System.NoticeResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as System from './type'
|
||||
const BASE_URL = '/system/role'
|
||||
|
||||
/** @desc 查询角色列表 */
|
||||
export function listRole(query: System.RoleQuery) {
|
||||
export function listRole(query: System.RolePageQuery) {
|
||||
return http.get<PageRes<System.RoleResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import type * as System from './type'
|
||||
const BASE_URL = '/system/storage'
|
||||
|
||||
/** @desc 查询存储列表 */
|
||||
export function listStorage(query: System.StorageQuery) {
|
||||
export function listStorage(query: System.StoragePageQuery) {
|
||||
return http.get<PageRes<System.StorageResp[]>>(`${BASE_URL}`, query)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface UserQuery {
|
||||
deptId?: string
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface UserPageQuery extends PageQuery, UserQuery {}
|
||||
export interface UserPageQuery extends UserQuery, PageQuery {}
|
||||
|
||||
/** 系统角色类型 */
|
||||
export interface RoleResp {
|
||||
@@ -62,9 +62,11 @@ export interface RoleDetailResp {
|
||||
updateTime: string
|
||||
disabled: boolean
|
||||
}
|
||||
export interface RoleQuery extends PageQuery {
|
||||
export interface RoleQuery {
|
||||
description?: string
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface RolePageQuery extends RoleQuery, PageQuery {}
|
||||
|
||||
/** 系统菜单类型 */
|
||||
export interface MenuResp {
|
||||
@@ -116,25 +118,6 @@ export interface DeptQuery {
|
||||
sort: Array<string>
|
||||
}
|
||||
|
||||
/** 系统公告类型 */
|
||||
export interface NoticeResp {
|
||||
id: string
|
||||
title: string
|
||||
content: string
|
||||
status: number
|
||||
type: string
|
||||
effectiveTime: string
|
||||
terminateTime: string
|
||||
createUserString: string
|
||||
createTime: string
|
||||
updateUserString: string
|
||||
updateTime: string
|
||||
}
|
||||
export interface NoticeQuery extends PageQuery {
|
||||
title?: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
/** 系统字典类型 */
|
||||
export interface DictResp {
|
||||
id: string
|
||||
@@ -147,9 +130,11 @@ export interface DictResp {
|
||||
updateUserString: string
|
||||
updateTime: string
|
||||
}
|
||||
export interface DictQuery extends PageQuery {
|
||||
export interface DictQuery {
|
||||
description?: string
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface DictPageQuery extends DictQuery, PageQuery {}
|
||||
export type DictItemResp = {
|
||||
id: string
|
||||
label: string
|
||||
@@ -164,11 +149,34 @@ export type DictItemResp = {
|
||||
updateUserString: string
|
||||
updateTime: string
|
||||
}
|
||||
export interface DictItemQuery extends PageQuery {
|
||||
export interface DictItemQuery {
|
||||
description?: string
|
||||
status?: number
|
||||
sort: Array<string>
|
||||
dictId: string
|
||||
}
|
||||
export interface DictItemPageQuery extends DictItemQuery, PageQuery {}
|
||||
|
||||
/** 系统公告类型 */
|
||||
export interface NoticeResp {
|
||||
id: string
|
||||
title: string
|
||||
content: string
|
||||
status: number
|
||||
type: string
|
||||
effectiveTime: string
|
||||
terminateTime: string
|
||||
createUserString: string
|
||||
createTime: string
|
||||
updateUserString: string
|
||||
updateTime: string
|
||||
}
|
||||
export interface NoticeQuery {
|
||||
title?: string
|
||||
type?: string
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface NoticePageQuery extends NoticeQuery, PageQuery {}
|
||||
|
||||
/** 系统文件类型 */
|
||||
export type FileItem = {
|
||||
@@ -184,10 +192,19 @@ export type FileItem = {
|
||||
updateUserString: string
|
||||
updateTime: string
|
||||
}
|
||||
export interface FileQuery extends PageQuery {
|
||||
/** 文件资源统计信息 */
|
||||
export interface FileStatisticsResp {
|
||||
type: string
|
||||
size: number
|
||||
number: number
|
||||
data: Array<FileStatisticsResp>
|
||||
}
|
||||
export interface FileQuery {
|
||||
name?: string
|
||||
type?: string
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface FilePageQuery extends FileQuery, PageQuery {}
|
||||
|
||||
/** 系统存储类型 */
|
||||
export type StorageResp = {
|
||||
@@ -209,10 +226,12 @@ export type StorageResp = {
|
||||
updateUserString: string
|
||||
updateTime: string
|
||||
}
|
||||
export interface StorageQuery extends PageQuery {
|
||||
export interface StorageQuery {
|
||||
description?: string
|
||||
status?: number
|
||||
sort: Array<string>
|
||||
}
|
||||
export interface StoragePageQuery extends StorageQuery, PageQuery {}
|
||||
|
||||
/** 系统参数类型 */
|
||||
export interface OptionResp {
|
||||
@@ -224,7 +243,6 @@ export interface OptionResp {
|
||||
export interface OptionQuery {
|
||||
code: Array<string>
|
||||
}
|
||||
|
||||
/** 基础配置类型 */
|
||||
export interface BasicConfigResp {
|
||||
site_favicon: string
|
||||
|
||||
BIN
src/assets/images/login_h5.jpg
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
@@ -7,10 +7,22 @@
|
||||
<a-space wrap class="gi-table__toolbar-right" :size="[8, 8]">
|
||||
<slot name="custom-right"></slot>
|
||||
<a-tooltip content="刷新">
|
||||
<a-button v-if="showRefreshBtn" @click="refresh">
|
||||
<a-button v-if="showRefreshBtn" class="gi_hover_btn-border" @click="refresh">
|
||||
<template #icon><icon-refresh /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-dropdown v-if="showSizeBtn" @select="handleSelect">
|
||||
<a-tooltip content="尺寸">
|
||||
<a-button class="gi_hover_btn-border">
|
||||
<template #icon><icon-table-size style="width: 14px; height: 14px" /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<template #content>
|
||||
<a-doption v-for="item in sizeList" :key="item.value" :value="item.value" :active="item.value === size">{{
|
||||
item.label
|
||||
}}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-popover
|
||||
v-if="showSettingColumnBtn"
|
||||
trigger="click"
|
||||
@@ -18,7 +30,7 @@
|
||||
:content-style="{ minWidth: '120px', padding: '6px 8px 10px' }"
|
||||
>
|
||||
<a-tooltip content="列设置">
|
||||
<a-button>
|
||||
<a-button class="gi_hover_btn-border">
|
||||
<template #icon>
|
||||
<icon-settings />
|
||||
</template>
|
||||
@@ -42,6 +54,14 @@
|
||||
</a-row>
|
||||
</template>
|
||||
</a-popover>
|
||||
<a-tooltip content="全屏">
|
||||
<a-button v-if="showFullscreenBtn" class="gi_hover_btn-border" @click="isFullscreen = !isFullscreen">
|
||||
<template #icon>
|
||||
<icon-fullscreen v-if="!isFullscreen" />
|
||||
<icon-fullscreen-exit v-else />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</a-row>
|
||||
<div class="gi-table__container">
|
||||
@@ -60,7 +80,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TableInstance, TableColumnData } from '@arco-design/web-vue'
|
||||
import type { TableInstance, TableColumnData, DropdownInstance } from '@arco-design/web-vue'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
|
||||
defineOptions({ name: 'GiTable', inheritAttrs: false })
|
||||
@@ -87,11 +107,23 @@ const size = ref<TableInstance['size']>('medium')
|
||||
const isBordered = ref(false)
|
||||
const isFullscreen = ref(false)
|
||||
|
||||
type SizeItem = { label: string; value: TableInstance['size'] }
|
||||
const sizeList: SizeItem[] = [
|
||||
{ label: '紧凑', value: 'small' },
|
||||
{ label: '默认', value: 'medium' }
|
||||
]
|
||||
|
||||
const handleSelect: DropdownInstance['onSelect'] = (value) => {
|
||||
size.value = value as TableInstance['size']
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
const showRefreshBtn = computed(() => !props.disabledTools.includes('refresh'))
|
||||
const showSizeBtn = computed(() => !props.disabledTools.includes('size'))
|
||||
const showFullscreenBtn = computed(() => !props.disabledTools.includes('fullscreen'))
|
||||
const showSettingColumnBtn = computed(
|
||||
() => !props.disabledTools.includes('setting') && attrs?.columns && (attrs?.columns as TableColumnData[])?.length
|
||||
)
|
||||
@@ -177,7 +209,6 @@ defineExpose({ tableRef })
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-form-layout-inline .arco-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import router from './router'
|
||||
// 引入 Arco Design 组件库以及自定义主题
|
||||
import ArcoVue from '@arco-design/web-vue'
|
||||
import '@/styles/arco-ui/index.less'
|
||||
import 'md-editor-v3/lib/style.css'
|
||||
// import '@arco-themes/vue-gi-demo/index.less'
|
||||
// import '@arco-design/web-vue/dist/arco.css'
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ export function resetRouter() {
|
||||
router.getRoutes().forEach((route) => {
|
||||
const { name } = route
|
||||
// console.log('name', name, path)
|
||||
if (name && name !== 'Home') {
|
||||
if (name && !['Home', 'Setting', 'SettingProfile', 'SettingSecurity'].includes(name.toString())) {
|
||||
router.hasRoute(name) && router.removeRoute(name)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -41,6 +41,7 @@ body[arco-theme='dark'] {
|
||||
#app {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
min-height: 800px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.gi_hover_btn-border {
|
||||
&:hover {
|
||||
background: var(--color-secondary-hover) !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: var(--color-secondary-active) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.gi_card {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
@@ -317,7 +327,6 @@
|
||||
-webkit-box-shadow: 0 2px 3px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 2px 3px rgba(0, 0, 0, .15);
|
||||
border-color: rgb(var(--primary-5));
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
src/types/api.d.ts
vendored
@@ -17,5 +17,4 @@ interface PageRes<T> {
|
||||
interface PageQuery {
|
||||
page: number
|
||||
size: number
|
||||
sort: Array<string>
|
||||
}
|
||||
|
||||
4
src/types/global.d.ts
vendored
@@ -23,5 +23,5 @@ export interface DictState {
|
||||
/** 状态(1:启用;2:禁用) */
|
||||
type Status = 1 | 2
|
||||
|
||||
/** 性别(1:男;2:女;3:未知) */
|
||||
type Gender = 1 | 2 | 3
|
||||
/** 性别(1:男;2:女;0:未知) */
|
||||
type Gender = 1 | 2 | 0
|
||||
|
||||
@@ -243,6 +243,8 @@ export const formatFileSize = (fileSize: number) => {
|
||||
const size = srcSize / 1024 ** index
|
||||
return `${size.toFixed(2)} ${unitArr[index]}`
|
||||
}
|
||||
|
||||
/** @desc 复制文本 */
|
||||
export const copyText = (text: any) => {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
>
|
||||
<a-carousel-item v-for="(image, idx) in imageList" :key="idx">
|
||||
<a :href="image.link" target="_blank" :title="image.title">
|
||||
<img :src="image.src" style="width: 100%" :alt="image.title" />
|
||||
<img :src="image.src" style="width: 100%; height: 100%" :alt="image.title" />
|
||||
</a>
|
||||
</a-carousel-item>
|
||||
</a-carousel>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="10" :lg="8" :xl="8" :xxl="6" style="margin: -8px -7px">
|
||||
<a-row justify="end">
|
||||
<SupportCard />
|
||||
<SupportCard v-if="isDesktop" />
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -26,10 +26,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import NowTime from './NowTime/index.vue'
|
||||
import { useDevice } from '@/hooks'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { goodTimeText } from '@/utils'
|
||||
import SupportCard from './SupportCard.vue'
|
||||
|
||||
const { isDesktop } = useDevice()
|
||||
const userStore = useUserStore()
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
<WorkCard />
|
||||
|
||||
<a-alert>
|
||||
全新版本 v3.0.0 发布预告,采用全新前端模板,提供更可靠、更舒适的前端开发体验,点击查看
|
||||
<span class="link" @click="open('https://gitee.com/continew/continew-admin-ui/commits/dev')">项目进展</span>。
|
||||
全新版本 v3.0.0 已发布,采用全新前端模板,提供更可靠、更舒适的前端开发体验,点击查看
|
||||
<span class="link" @click="open('https://continew.top/admin/other/changelog.html')">更新日志</span>。
|
||||
</a-alert>
|
||||
|
||||
<a-row class="home__content">
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="18" :xxl="20">
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="18" :xxl="18">
|
||||
<div class="home__item"><ProjectCard /></div>
|
||||
<div class="home__item"><AccessTrendCard /></div>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" :xxl="4">
|
||||
<a-col :xs="24" :sm="24" :md="24" :lg="12" :xl="6" :xxl="6">
|
||||
<div class="home__item"><FastCard /></div>
|
||||
<div class="home__item"><SponsorCard /></div>
|
||||
<div class="home__item"><NoticeCard /></div>
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
</a-form-item>
|
||||
<a-form-item field="captcha" hide-label>
|
||||
<a-input v-model="form.captcha" placeholder="请输入验证码" :max-length="4" allow-clear style="flex: 1 1" />
|
||||
<img :src="captchaImgBase64" alt="验证码" class="captcha" @click="getCaptcha" />
|
||||
<div class="captcha-container" @click="getCaptcha">
|
||||
<img :src="captchaImgBase64" alt="验证码" class="captcha" />
|
||||
<div v-if="form.expired" class="overlay">
|
||||
<p>已过期,请刷新</p>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-row justify="space-between" align="center" class="w-full">
|
||||
@@ -52,7 +57,8 @@ const form = reactive({
|
||||
username: loginConfig.value.username,
|
||||
password: loginConfig.value.password,
|
||||
captcha: '',
|
||||
uuid: ''
|
||||
uuid: '',
|
||||
expired: false
|
||||
})
|
||||
|
||||
const rules: FormInstance['rules'] = {
|
||||
@@ -98,11 +104,36 @@ const captchaImgBase64 = ref()
|
||||
// 获取验证码
|
||||
const getCaptcha = () => {
|
||||
getImageCaptcha().then((res) => {
|
||||
form.uuid = res.data.uuid
|
||||
captchaImgBase64.value = res.data.img
|
||||
const { uuid, img, expireTime } = res.data
|
||||
form.uuid = uuid
|
||||
captchaImgBase64.value = img
|
||||
form.expired = false
|
||||
startTimer(expireTime)
|
||||
})
|
||||
}
|
||||
|
||||
// 验证码过期定时器
|
||||
let timer
|
||||
const startTimer = (expireTime: number) => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
const remainingTime = expireTime - Date.now()
|
||||
if (remainingTime <= 0) {
|
||||
form.expired = true
|
||||
return
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
form.expired = true
|
||||
}, remainingTime)
|
||||
}
|
||||
// 组件销毁时清理定时器
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getCaptcha()
|
||||
})
|
||||
@@ -137,10 +168,37 @@ onMounted(() => {
|
||||
width: 111px;
|
||||
height: 36px;
|
||||
margin: 0 0 0 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.captcha-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.captcha-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(51, 51, 51, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.overlay p {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
// import { getEmailCaptcha } from '@/apis'
|
||||
import { Message, type FormInstance } from '@arco-design/web-vue'
|
||||
import { useUserStore } from '@/stores'
|
||||
import * as Regexp from '@/utils/regexp'
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
@@ -45,7 +46,10 @@ const form = reactive({
|
||||
})
|
||||
|
||||
const rules: FormInstance['rules'] = {
|
||||
email: [{ required: true, message: '请输入邮箱' }],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱' },
|
||||
{ match: Regexp.Email, message: '请输入正确的邮箱' }
|
||||
],
|
||||
captcha: [{ required: true, message: '请输入验证码' }]
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
// import { getSmsCaptcha } from '@/apis'
|
||||
import { Message, type FormInstance } from '@arco-design/web-vue'
|
||||
import { useUserStore } from '@/stores'
|
||||
import * as Regexp from '@/utils/regexp'
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
@@ -47,7 +48,7 @@ const form = reactive({
|
||||
const rules: FormInstance['rules'] = {
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号' },
|
||||
{ match: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
|
||||
{ match: Regexp.Phone, message: '请输入正确的手机号' }
|
||||
],
|
||||
captcha: [{ required: true, message: '请输入验证码' }]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<div class="login pc">
|
||||
<h3 class="login-logo">
|
||||
<img v-if="logo" :src="logo" alt="logo" />
|
||||
<img v-else src="/logo.svg" alt="logo" />
|
||||
@@ -15,8 +15,8 @@
|
||||
<a-col :xs="24" :sm="12" :md="11">
|
||||
<div class="login-right">
|
||||
<h3 class="login-right__title" v-if="isEmailLogin">邮箱登录</h3>
|
||||
<EmailLogin v-if="isEmailLogin" />
|
||||
<a-tabs v-else class="login-right__form">
|
||||
<EmailLogin v-show="isEmailLogin" />
|
||||
<a-tabs v-show="!isEmailLogin" class="login-right__form">
|
||||
<a-tab-pane title="账号登录" key="1">
|
||||
<AccountLogin />
|
||||
</a-tab-pane>
|
||||
@@ -50,6 +50,42 @@
|
||||
<GiThemeBtn class="theme-btn" />
|
||||
<Background />
|
||||
</div>
|
||||
<div class="login h5">
|
||||
<div class="login-logo">
|
||||
<img v-if="logo" :src="logo" alt="logo" />
|
||||
<img v-else src="/logo.svg" alt="logo" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<a-row align="stretch" class="login-box">
|
||||
<a-col :xs="24" :sm="12" :md="11">
|
||||
<div class="login-right">
|
||||
<h3 class="login-right__title" v-if="isEmailLogin">邮箱登录</h3>
|
||||
<EmailLogin v-show="isEmailLogin" />
|
||||
<a-tabs v-show="!isEmailLogin" class="login-right__form">
|
||||
<a-tab-pane title="账号登录" key="1">
|
||||
<AccountLogin />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane title="手机号登录" key="2">
|
||||
<PhoneLogin />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="login-right__oauth">
|
||||
<a-divider orientation="center">其他登录方式</a-divider>
|
||||
<div class="list">
|
||||
<div v-if="isEmailLogin" class="mode item" @click="toggleLoginMode"><icon-user /> 账号/手机号登录</div>
|
||||
<div v-else class="mode item" @click="toggleLoginMode"><icon-email /> 邮箱登录</div>
|
||||
<a class="item" title="使用 Gitee 账号登录" @click="onOauth('gitee')">
|
||||
<GiSvgIcon name="gitee" :size="24" />
|
||||
</a>
|
||||
<a class="item" title="使用 GitHub 账号登录" @click="onOauth('github')">
|
||||
<GiSvgIcon name="github" :size="24" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -82,181 +118,350 @@ const onOauth = async (source: string) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--color-bg-5);
|
||||
&-logo {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
z-index: 9999;
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 20px;
|
||||
@media screen and (max-width: 570px) {
|
||||
.pc {
|
||||
display: none !important;
|
||||
background-color: white !important;
|
||||
}
|
||||
.login {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
&-box {
|
||||
width: 86%;
|
||||
max-width: 850px;
|
||||
height: 490px;
|
||||
display: flex;
|
||||
z-index: 999;
|
||||
box-shadow: 0 2px 4px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.login-left {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(60deg, rgb(var(--primary-6)), rgb(var(--primary-3)));
|
||||
&__img {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
transition: all 0.3s;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.login-right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--color-bg-1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 30px 30px 0;
|
||||
box-sizing: border-box;
|
||||
&__title {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
&__form {
|
||||
:deep(.arco-tabs-nav-tab) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
:deep(.arco-tabs-tab) {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
:deep(.arco-tabs-tab-title) {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
}
|
||||
:deep(.arco-tabs-content) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
:deep(.arco-tabs-tab-active),
|
||||
:deep(.arco-tabs-tab-title:hover) {
|
||||
color: rgb(var(--arcoblue-6));
|
||||
}
|
||||
:deep(.arco-tabs-nav::before) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.arco-tabs-tab-title:before) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&__oauth {
|
||||
margin-top: auto;
|
||||
margin-bottom: 20px;
|
||||
:deep(.arco-divider-text) {
|
||||
color: var(--color-text-4);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
.list {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--color-bg-5);
|
||||
color: #121314;
|
||||
&-logo {
|
||||
width: 100%;
|
||||
.item {
|
||||
margin-right: 15px;
|
||||
height: 104px;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
display: flex;
|
||||
padding: 0 20px;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
background-image: url('/src/assets/images/login_h5.jpg');
|
||||
background-size: 100% 100%;
|
||||
box-sizing: border-box;
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.mode {
|
||||
}
|
||||
&-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
.login-right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 30px 30px 0;
|
||||
box-sizing: border-box;
|
||||
&__title {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
&__form {
|
||||
:deep(.arco-tabs-nav-tab) {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
:deep(.arco-tabs-tab) {
|
||||
color: var(--color-text-2);
|
||||
margin: 0 20px 0 0;
|
||||
}
|
||||
:deep(.arco-tabs-tab-title) {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
}
|
||||
:deep(.arco-tabs-content) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
:deep(.arco-tabs-tab-active),
|
||||
:deep(.arco-tabs-tab-title:hover) {
|
||||
color: rgb(var(--arcoblue-6));
|
||||
}
|
||||
:deep(.arco-tabs-nav::before) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.arco-tabs-tab-title:before) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&__oauth {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding-bottom: 20px;
|
||||
// margin-top: auto;
|
||||
// margin-bottom: 20px;
|
||||
:deep(.arco-divider-text) {
|
||||
color: var(--color-text-4);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
.list {
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-border-3);
|
||||
border-radius: 32px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
.icon {
|
||||
width: 21px;
|
||||
height: 20px;
|
||||
width: 100%;
|
||||
.item {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.mode {
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
padding: 6px 10px;
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-border-3);
|
||||
border-radius: 32px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
.icon {
|
||||
width: 21px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
.mode svg {
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.mode:hover,
|
||||
.mode svg:hover {
|
||||
background: rgba(var(--primary-6), 0.05);
|
||||
border: 1px solid rgb(var(--primary-3));
|
||||
color: rgb(var(--arcoblue-6));
|
||||
}
|
||||
}
|
||||
.mode svg {
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 30px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.footer {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
z-index: 999;
|
||||
.beian {
|
||||
.text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.2px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.mode:hover,
|
||||
.mode svg:hover {
|
||||
background: rgba(var(--primary-6), 0.05);
|
||||
border: 1px solid rgb(var(--primary-3));
|
||||
color: rgb(var(--arcoblue-6));
|
||||
.below {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 30px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.footer {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
z-index: 999;
|
||||
.beian {
|
||||
.text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.2px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.below {
|
||||
align-items: center;
|
||||
@media screen and (min-width: 571px) {
|
||||
.h5 {
|
||||
display: none !important;
|
||||
}
|
||||
.login {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--color-bg-5);
|
||||
&-logo {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 30px;
|
||||
z-index: 9999;
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
&-box {
|
||||
width: 86%;
|
||||
max-width: 850px;
|
||||
height: 490px;
|
||||
display: flex;
|
||||
z-index: 999;
|
||||
box-shadow: 0 2px 4px 2px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.login-left {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(60deg, rgb(var(--primary-6)), rgb(var(--primary-3)));
|
||||
&__img {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
transition: all 0.3s;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.login-right {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--color-bg-1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 30px 30px 0;
|
||||
box-sizing: border-box;
|
||||
&__title {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
&__form {
|
||||
:deep(.arco-tabs-nav-tab) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
:deep(.arco-tabs-tab) {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
:deep(.arco-tabs-tab-title) {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
}
|
||||
:deep(.arco-tabs-content) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
:deep(.arco-tabs-tab-active),
|
||||
:deep(.arco-tabs-tab-title:hover) {
|
||||
color: rgb(var(--arcoblue-6));
|
||||
}
|
||||
:deep(.arco-tabs-nav::before) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.arco-tabs-tab-title:before) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&__oauth {
|
||||
margin-top: auto;
|
||||
margin-bottom: 20px;
|
||||
:deep(.arco-divider-text) {
|
||||
color: var(--color-text-4);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
.list {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
.item {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.mode {
|
||||
color: var(--color-text-2);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
padding: 6px 10px;
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-border-3);
|
||||
border-radius: 32px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
.icon {
|
||||
width: 21px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
.mode svg {
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.mode:hover,
|
||||
.mode svg:hover {
|
||||
background: rgba(var(--primary-6), 0.05);
|
||||
border: 1px solid rgb(var(--primary-3));
|
||||
color: rgb(var(--arcoblue-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-btn {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 30px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.footer {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
z-index: 999;
|
||||
.beian {
|
||||
.text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.2px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.below {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['setting']"
|
||||
:disabledTools="['size', 'setting']"
|
||||
@filterChange="filterChange"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
<template #custom-right>
|
||||
<a-tooltip content="导出">
|
||||
<a-button v-permission="['monitor:log:export']" @click="onExport">
|
||||
<a-button v-permission="['monitor:log:export']" class="gi_hover_btn-border" @click="onExport">
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
</template>
|
||||
@@ -45,7 +45,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { exportLoginLog, listLog } from '@/apis'
|
||||
import { exportLoginLog, listLog, type LogQuery } from '@/apis'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import DateRangePicker from '@/components/DateRangePicker/index.vue'
|
||||
import { useTable, useDownload } from '@/hooks'
|
||||
@@ -90,15 +90,12 @@ const columns: TableInstanceColumns[] = [
|
||||
{ title: '终端系统', dataIndex: 'os', ellipsis: true, tooltip: true }
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
const queryForm = reactive<LogQuery>({
|
||||
module: '登录',
|
||||
ip: undefined,
|
||||
createUserString: undefined,
|
||||
createTime: [
|
||||
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||
],
|
||||
status: undefined,
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
column-resizable
|
||||
:disabledTools="['setting']"
|
||||
:disabledTools="['size', 'setting']"
|
||||
@filterChange="filterChange"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -23,7 +23,7 @@
|
||||
</template>
|
||||
<template #custom-right>
|
||||
<a-tooltip content="导出">
|
||||
<a-button v-permission="['monitor:log:export']" @click="onExport">
|
||||
<a-button v-permission="['monitor:log:export']" class="gi_hover_btn-border" @click="onExport">
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
</template>
|
||||
@@ -56,7 +56,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listLog, exportOperationLog, type LogResp } from '@/apis'
|
||||
import { listLog, exportOperationLog, type LogResp, type LogQuery } from '@/apis'
|
||||
import OperationLogDetailDrawer from './OperationLogDetailDrawer.vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import DateRangePicker from '@/components/DateRangePicker/index.vue'
|
||||
@@ -102,15 +102,11 @@ const columns: TableInstanceColumns[] = [
|
||||
{ title: '终端系统', dataIndex: 'os', ellipsis: true, tooltip: true }
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
description: undefined,
|
||||
ip: undefined,
|
||||
createUserString: undefined,
|
||||
const queryForm = reactive<LogQuery>({
|
||||
createTime: [
|
||||
dayjs().subtract(6, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss'),
|
||||
dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')
|
||||
],
|
||||
status: undefined,
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['setting']"
|
||||
:disabledTools="['size', 'setting']"
|
||||
@refresh="search"
|
||||
>
|
||||
<template #custom-left>
|
||||
@@ -44,7 +44,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listOnlineUser, kickout } from '@/apis'
|
||||
import { listOnlineUser, kickout, type OnlineUserQuery } from '@/apis'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import DateRangePicker from '@/components/DateRangePicker/index.vue'
|
||||
@@ -79,9 +79,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
nickname: undefined,
|
||||
loginTime: undefined,
|
||||
const queryForm = reactive<OnlineUserQuery>({
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
// import { getSmsCaptcha, getEmailCaptcha, updateUserEmail, updateUserPhone } from '@/apis'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
// import { encryptByRsa } from '@/utils/encrypt'
|
||||
import * as Regexp from '@/utils/regexp'
|
||||
import { useUserStore } from '@/stores'
|
||||
import { type Columns, GiForm } from '@/components/GiForm'
|
||||
import { useForm } from '@/hooks'
|
||||
import * as Regexp from '@/utils/regexp'
|
||||
|
||||
const verifyType = ref()
|
||||
const title = computed(() => (verifyType.value === 'phone' ? '修改手机号' : '修改邮箱'))
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</section>
|
||||
<footer>
|
||||
<a-descriptions column="4" size="large">
|
||||
<a-descriptions :column="4" size="large">
|
||||
<a-descriptions-item :span="4">
|
||||
<template #label> <icon-user /><span style="margin-left: 5px">用户名</span></template>
|
||||
{{ userInfo.username }}
|
||||
|
||||
@@ -40,6 +40,9 @@ const columns: Columns = [
|
||||
field: 'parentId',
|
||||
type: 'tree-select',
|
||||
data: deptList,
|
||||
hide: (form) => {
|
||||
return form.parentId === 0
|
||||
},
|
||||
props: {
|
||||
allowClear: true,
|
||||
allowSearch: true,
|
||||
@@ -50,7 +53,8 @@ const columns: Columns = [
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
rules: [{ required: true, message: '请选择上级部门' }]
|
||||
},
|
||||
{ label: '名称', field: 'name', type: 'input', rules: [{ required: true, message: '请输入名称' }] },
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="false"
|
||||
:disabledTools="['size']"
|
||||
:disabledColumnKeys="['name']"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -36,7 +37,7 @@
|
||||
<span>新增</span>
|
||||
</a-button>
|
||||
<a-tooltip content="导出">
|
||||
<a-button v-permission="['system:dept:export']" @click="onExport">
|
||||
<a-button v-permission="['system:dept:export']" class="gi_hover_btn-border" @click="onExport">
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
</template>
|
||||
@@ -111,9 +112,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
description: undefined,
|
||||
status: undefined,
|
||||
const queryForm = reactive<DeptQuery>({
|
||||
sort: ['parentId,asc', 'sort,asc', 'createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['size']"
|
||||
:disabledColumnKeys="['name']"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -51,7 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listDict, deleteDict, type DictResp } from '@/apis'
|
||||
import { listDict, deleteDict, type DictResp, type DictQuery } from '@/apis'
|
||||
import DictAddModal from './DictAddModal.vue'
|
||||
import DictItemModal from '@/views/system/dict/item/index.vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
@@ -86,8 +87,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
description: undefined,
|
||||
const queryForm = reactive<DictQuery>({
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 800 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['size']"
|
||||
:disabledColumnKeys="['label']"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -62,7 +63,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { listDictItem, deleteDictItem, type DictItemResp } from '@/apis'
|
||||
import { listDictItem, deleteDictItem, type DictItemResp, type DictItemQuery } from '@/apis'
|
||||
import DictItemAddModal from './DictItemAddModal.vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useTable } from '@/hooks'
|
||||
@@ -96,9 +97,7 @@ const columns: TableInstanceColumns[] = [
|
||||
{ title: '操作', slotName: 'action', width: 130, align: 'center', fixed: !isMobile() ? 'right' : undefined }
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
description: undefined,
|
||||
status: undefined,
|
||||
const queryForm = reactive<DictItemQuery>({
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -14,11 +14,17 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FileItem } from '@/apis'
|
||||
import type { FormInstance } from '@arco-design/web-vue'
|
||||
|
||||
interface Props {
|
||||
data: FileItem
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {})
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const form = reactive({
|
||||
name: ''
|
||||
name: props.data?.name || ''
|
||||
})
|
||||
|
||||
defineExpose({ formRef })
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, h } from 'vue'
|
||||
import { Modal, Message } from '@arco-design/web-vue'
|
||||
import ModalContent from './ModalContent.vue'
|
||||
|
||||
export function openFileRenameModal(data: FileItem) {
|
||||
export function openFileRenameModal(data: FileItem, callback?: () => void) {
|
||||
const ModalContentRef = ref<InstanceType<typeof ModalContent>>()
|
||||
return Modal.open({
|
||||
title: '重命名',
|
||||
@@ -12,6 +12,7 @@ export function openFileRenameModal(data: FileItem) {
|
||||
width: '90%',
|
||||
content: () =>
|
||||
h(ModalContent, {
|
||||
data,
|
||||
ref: (e) => {
|
||||
ModalContentRef.value = e as any
|
||||
}
|
||||
@@ -22,6 +23,9 @@ export function openFileRenameModal(data: FileItem) {
|
||||
if (isInvalid) return false
|
||||
await updateFile({ name: modelParams?.name }, data.id)
|
||||
Message.success('重命名成功')
|
||||
if (callback) {
|
||||
callback()
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
</a-sub-menu>
|
||||
</a-menu>
|
||||
</a-card>
|
||||
<FileAsideStatistics />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FileTypeList, type FileTypeListItem } from '@/constant/file'
|
||||
import FileAsideStatistics from '@/views/system/file/main/FileAsideStatistics.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -42,7 +44,7 @@ watch(
|
||||
|
||||
// 点击事件
|
||||
const onClickItem = (item: FileTypeListItem) => {
|
||||
router.push({ name: 'File', query: { type: item.value } })
|
||||
router.push({ name: 'SystemFile', query: { type: item.value } })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -55,19 +57,4 @@ const onClickItem = (item: FileTypeListItem) => {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.arco-progress) {
|
||||
.arco-progress-line,
|
||||
.arco-progress-line-bar-buffer,
|
||||
.arco-progress-line-bar {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.percent {
|
||||
margin-top: 10px;
|
||||
padding: 14px 12px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--color-bg-1);
|
||||
}
|
||||
</style>
|
||||
|
||||
124
src/views/system/file/main/FileAsideStatistics.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<section class="percent">
|
||||
<a-space class="statistic-space" align="center" size="medium" fill>
|
||||
<template #split>
|
||||
<a-divider direction="vertical" />
|
||||
</template>
|
||||
<a-statistic class="statistic-item" title="存储量" :value="totalData.size" :value-style="statisticValueStyle">
|
||||
<template #suffix> {{ totalData.unit }}</template>
|
||||
</a-statistic>
|
||||
<a-statistic class="statistic-item" title="数量" :value="totalData.number" :value-style="statisticValueStyle" />
|
||||
</a-space>
|
||||
<a-divider />
|
||||
<VCharts :option="option" autoresize :style="{ height: '120px', width: '150px' }" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getFileStatistics, type FileStatisticsResp } from '@/apis'
|
||||
import { useChart } from '@/hooks'
|
||||
import { FileTypeList } from '@/constant/file'
|
||||
import VCharts from 'vue-echarts'
|
||||
import { use } from 'echarts/core'
|
||||
import { PieChart } from 'echarts/charts'
|
||||
import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
|
||||
import { CanvasRenderer } from 'echarts/renderers'
|
||||
import { formatFileSize } from '@/utils'
|
||||
|
||||
use([TitleComponent, TooltipComponent, LegendComponent, PieChart, CanvasRenderer])
|
||||
|
||||
const totalData = ref<FileStatisticsResp>({})
|
||||
const chartData = ref<Array<FileStatisticsResp>>([])
|
||||
const statisticValueStyle = { color: '#5856D6', 'font-size': '18px' }
|
||||
const { option } = useChart(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
bottom: -5,
|
||||
icon: 'circle',
|
||||
itemWidth: 6,
|
||||
itemHeight: 6,
|
||||
textStyle: {
|
||||
color: '#4E5969'
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
formatter: function (params) {
|
||||
return `总计:${params.value}<br>${params.data.size}`
|
||||
}
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: true,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
data: chartData.value
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const getStatisticsData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
chartData.value = []
|
||||
totalData.value = {}
|
||||
const { data: resData } = await getFileStatistics()
|
||||
const formatSize = formatFileSize(resData.size).split(' ')
|
||||
totalData.value = {
|
||||
size: parseFloat(formatSize[0]),
|
||||
number: resData.number,
|
||||
unit: formatSize[1]
|
||||
}
|
||||
resData.data.forEach((fs: FileStatisticsResp) => {
|
||||
const matchedItem = FileTypeList.find((item) => item.value == fs.type)
|
||||
chartData.value.unshift({
|
||||
name: matchedItem ? matchedItem.name : '',
|
||||
value: fs.number,
|
||||
size: formatFileSize(fs.size)
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getStatisticsData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.statistic-space {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.statistic-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.percent {
|
||||
margin-top: 10px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--color-bg-1);
|
||||
}
|
||||
|
||||
:deep(.arco-divider-horizontal) {
|
||||
margin: 20px 0 0 0;
|
||||
}
|
||||
</style>
|
||||
@@ -27,11 +27,7 @@
|
||||
:class="{ checked: props.selectedFileIds.includes(item.id) }"
|
||||
@click.stop="handleCheckFile(item)"
|
||||
>
|
||||
<a-checkbox
|
||||
class="checkbox"
|
||||
:model-value="props.selectedFileIds.includes(item.id)"
|
||||
@change="handleCheckFile(item)"
|
||||
></a-checkbox>
|
||||
<a-checkbox class="checkbox" :model-value="props.selectedFileIds.includes(item.id)"></a-checkbox>
|
||||
</section>
|
||||
</div>
|
||||
</a-grid-item>
|
||||
|
||||
@@ -17,7 +17,13 @@
|
||||
</a-dropdown>
|
||||
|
||||
<a-input-group>
|
||||
<a-input v-model="queryForm.name" placeholder="请输入文件名" allow-clear @change="search" />
|
||||
<a-input
|
||||
v-model="queryForm.name"
|
||||
placeholder="请输入文件名"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
@change="search"
|
||||
/>
|
||||
<a-button type="primary" @click="search">
|
||||
<template #icon>
|
||||
<icon-search />
|
||||
@@ -47,11 +53,11 @@
|
||||
<template #default>{{ isBatchMode ? '取消批量' : '批量操作' }}</template>
|
||||
</a-button>
|
||||
<a-button-group>
|
||||
<a-tooltip content="视图" position="bottom">
|
||||
<a-button @click="toggleMode">
|
||||
<a-tooltip content="视图">
|
||||
<a-button class="gi_hover_btn-border" @click="toggleMode">
|
||||
<template #icon>
|
||||
<icon-apps v-if="mode === 'grid'" />
|
||||
<icon-list v-else />
|
||||
<icon-list v-if="mode === 'grid'" />
|
||||
<icon-apps v-else />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
@@ -88,7 +94,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listFile, uploadFile, deleteFile, type FileItem, type FileQuery } from '@/apis'
|
||||
import { listFile, uploadFile, deleteFile, type FileItem, type FileQuery, type FilePageQuery } from '@/apis'
|
||||
import { Message, Modal, type RequestOption } from '@arco-design/web-vue'
|
||||
import FileGrid from './FileGrid.vue'
|
||||
import {
|
||||
@@ -102,6 +108,7 @@ import { ImageTypes } from '@/constant/file'
|
||||
import { api as viewerApi } from 'v-viewer'
|
||||
import 'viewerjs/dist/viewer.css'
|
||||
import { downloadByUrl } from '@/utils/downloadFile'
|
||||
|
||||
const FileList = defineAsyncComponent(() => import('./FileList.vue'))
|
||||
onMounted(() => {
|
||||
const fileMainDom = document.getElementById('fileMain')
|
||||
@@ -132,7 +139,7 @@ const handleScroll = (event) => {
|
||||
const route = useRoute()
|
||||
const { mode, selectedFileIds, toggleMode, addSelectedFileItem } = useFileManage()
|
||||
|
||||
const queryForm = reactive({
|
||||
const queryForm = reactive<FileQuery>({
|
||||
name: undefined,
|
||||
type: route.query.type?.toString() || undefined,
|
||||
sort: ['updateTime,desc']
|
||||
@@ -145,7 +152,7 @@ const fileList = ref<FileItem[]>([])
|
||||
const isBatchMode = ref(false)
|
||||
const loading = ref(false)
|
||||
// 查询文件列表
|
||||
const getFileList = async (query: FileQuery = { ...queryForm, page: pagination.page, size: pagination.size }) => {
|
||||
const getFileList = async (query: FilePageQuery = { ...queryForm, page: pagination.page, size: pagination.size }) => {
|
||||
try {
|
||||
loading.value = true
|
||||
isBatchMode.value = false
|
||||
@@ -211,7 +218,7 @@ const handleRightMenuClick = async (mode: string, fileInfo: FileItem) => {
|
||||
}
|
||||
})
|
||||
} else if (mode === 'rename') {
|
||||
openFileRenameModal(fileInfo)
|
||||
openFileRenameModal(fileInfo, search)
|
||||
} else if (mode === 'detail') {
|
||||
openFileDetailModal(fileInfo)
|
||||
} else if (mode === 'download') {
|
||||
@@ -237,8 +244,8 @@ const handleMulDelete = () => {
|
||||
title: '提示',
|
||||
content: `是否确定删除所选的${selectedFileIds.value.length}个文件?`,
|
||||
hideCancel: false,
|
||||
onOk: () => {
|
||||
deleteFile(selectedFileIds.value)
|
||||
onOk: async () => {
|
||||
await deleteFile(selectedFileIds.value)
|
||||
Message.success('删除成功')
|
||||
search()
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<span>新增</span>
|
||||
</a-button>
|
||||
<a-tooltip content="展开/折叠">
|
||||
<a-button @click="onExpanded">
|
||||
<a-button class="gi_hover_btn-border" @click="onExpanded">
|
||||
<template #icon>
|
||||
<icon-list v-if="!isExpanded" />
|
||||
<icon-mind-mapping v-else />
|
||||
@@ -129,9 +129,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
title: undefined,
|
||||
status: undefined,
|
||||
const queryForm = reactive<MenuQuery>({
|
||||
sort: ['parentId,asc', 'sort,asc', 'createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ import { Message, type FormInstance } from '@arco-design/web-vue'
|
||||
import { useForm } from '@/hooks'
|
||||
import { useDict } from '@/hooks/app'
|
||||
import { MdEditor } from 'md-editor-v3'
|
||||
import 'md-editor-v3/lib/style.css'
|
||||
|
||||
const { notice_type } = useDict('notice_type')
|
||||
|
||||
@@ -155,7 +154,7 @@ const save = async () => {
|
||||
if (isInvalid) return false
|
||||
try {
|
||||
if (isUpdate.value) {
|
||||
await updateNotice(form, announcementId.value)
|
||||
await updateNotice(form, dataId.value)
|
||||
Message.success('修改成功')
|
||||
} else {
|
||||
await addNotice(form)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['size']"
|
||||
:disabledColumnKeys="['title']"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -56,7 +57,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { listNotice, deleteNotice, type NoticeResp } from '@/apis'
|
||||
import { listNotice, deleteNotice, type NoticeResp, type NoticeQuery } from '@/apis'
|
||||
import NoticeAddModal from './NoticeAddModal.vue'
|
||||
import NoticeDetailModal from './NoticeDetailModal.vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
@@ -76,9 +77,9 @@ const columns: TableInstanceColumns[] = [
|
||||
align: 'center',
|
||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize)
|
||||
},
|
||||
{ title: '标题', dataIndex: 'title', slotName: 'title', ellipsis: true, tooltip: true },
|
||||
{ title: '类型', slotName: 'type', align: 'center', width: 130 },
|
||||
{ title: '状态', slotName: 'status', align: 'center', width: 130 },
|
||||
{ title: '标题', dataIndex: 'title', slotName: 'title', width: 200, ellipsis: true, tooltip: true },
|
||||
{ title: '类型', slotName: 'type', align: 'center' },
|
||||
{ title: '状态', slotName: 'status', align: 'center' },
|
||||
{ title: '生效时间', dataIndex: 'effectiveTime', width: 180 },
|
||||
{ title: '终止时间', dataIndex: 'terminateTime', width: 180 },
|
||||
{ title: '创建人', dataIndex: 'createUserString', show: false, ellipsis: true, tooltip: true },
|
||||
@@ -93,9 +94,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
title: undefined,
|
||||
type: undefined,
|
||||
const queryForm = reactive<NoticeQuery>({
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['size']"
|
||||
:disabledColumnKeys="['name']"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -56,7 +57,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listRole, deleteRole, type RoleResp } from '@/apis'
|
||||
import { listRole, deleteRole, type RoleResp, type RoleQuery } from '@/apis'
|
||||
import RoleAddModal from './RoleAddModal.vue'
|
||||
import RoleDetailDrawer from './RoleDetailDrawer.vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
@@ -96,8 +97,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
description: undefined,
|
||||
const queryForm = reactive<RoleQuery>({
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1300 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['size']"
|
||||
:disabledColumnKeys="['name']"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -67,7 +68,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listStorage, deleteStorage, type StorageResp } from '@/apis'
|
||||
import { listStorage, deleteStorage, type StorageResp, type StorageQuery } from '@/apis'
|
||||
import StorageAddModal from './StorageAddModal.vue'
|
||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
||||
import { useTable } from '@/hooks'
|
||||
@@ -110,9 +111,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
description: undefined,
|
||||
status: undefined,
|
||||
const queryForm = reactive<StorageQuery>({
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
|
||||
@@ -6,7 +6,15 @@
|
||||
<a-input v-model="deptName" placeholder="请输入部门名称" allow-clear style="margin-bottom: 10px">
|
||||
<template #prefix><icon-search /></template>
|
||||
</a-input>
|
||||
<a-tree ref="treeRef" :data="deptList" default-expand-all show-line block-node @select="handleSelectDept">
|
||||
<a-tree
|
||||
ref="treeRef"
|
||||
:data="deptList"
|
||||
:selected-keys="selectedKeys"
|
||||
default-expand-all
|
||||
show-line
|
||||
block-node
|
||||
@select="handleSelectDept"
|
||||
>
|
||||
</a-tree>
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="20" :lg="20" :xl="20" :xxl="20">
|
||||
@@ -18,6 +26,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1500 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['size']"
|
||||
:disabledColumnKeys="['nickname']"
|
||||
@refresh="search"
|
||||
>
|
||||
@@ -41,17 +50,17 @@
|
||||
<span>新增</span>
|
||||
</a-button>
|
||||
<a-tooltip content="导出">
|
||||
<a-button v-permission="['system:user:export']" @click="onExport">
|
||||
<a-button v-permission="['system:user:export']" class="gi_hover_btn-border" @click="onExport">
|
||||
<template #icon>
|
||||
<icon-download />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #nickname="{ record }">
|
||||
<template #username="{ record }">
|
||||
<GiCellAvatar
|
||||
:avatar="getAvatar(record.avatar, record.gender)"
|
||||
:name="record.nickname"
|
||||
:name="record.username"
|
||||
is-link
|
||||
@click="onDetail(record)"
|
||||
/>
|
||||
@@ -79,7 +88,7 @@
|
||||
删除
|
||||
</a-link>
|
||||
<a-dropdown>
|
||||
<a-button v-if="has.hasPermOr(['system:user:resetPwd'])" type="text">更多</a-button>
|
||||
<a-link v-if="has.hasPermOr(['system:user:resetPwd'])" type="text">更多</a-link>
|
||||
<template #content>
|
||||
<a-doption v-permission="['system:user:resetPwd']" @click="onResetPwd(record)">重置密码</a-doption>
|
||||
</template>
|
||||
@@ -98,7 +107,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { listUser, deleteUser, exportUser, type UserResp } from '@/apis'
|
||||
import { listUser, deleteUser, exportUser, type UserResp, type UserQuery } from '@/apis'
|
||||
import UserAddModal from './UserAddModal.vue'
|
||||
import UserDetailDrawer from './UserDetailDrawer.vue'
|
||||
import UserResetPwdModal from './UserResetPwdModal.vue'
|
||||
@@ -122,19 +131,19 @@ const columns: TableInstanceColumns[] = [
|
||||
fixed: !isMobile() ? 'left' : undefined
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
slotName: 'nickname',
|
||||
width: 170,
|
||||
title: '用户名',
|
||||
slotName: 'username',
|
||||
width: 140,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
fixed: !isMobile() ? 'left' : undefined
|
||||
},
|
||||
{ title: '用户名', dataIndex: 'username', width: 120, ellipsis: true, tooltip: true },
|
||||
{ title: '昵称', dataIndex: 'nickname', width: 120, ellipsis: true, tooltip: true },
|
||||
{ title: '状态', slotName: 'status', align: 'center' },
|
||||
{ title: '性别', slotName: 'gender', align: 'center' },
|
||||
{ title: '所属部门', dataIndex: 'deptName', ellipsis: true, tooltip: true },
|
||||
{ title: '手机号', dataIndex: 'phone', width: 170, ellipsis: true, tooltip: true },
|
||||
{ title: '邮箱', dataIndex: 'email', width: 170, ellipsis: true, tooltip: true },
|
||||
{ title: '所属部门', dataIndex: 'deptName', ellipsis: true, tooltip: true },
|
||||
{ title: '系统内置', slotName: 'isSystem', width: 100, align: 'center', show: false },
|
||||
{ title: '描述', dataIndex: 'description', ellipsis: true, tooltip: true },
|
||||
{ title: '创建人', dataIndex: 'createUserString', ellipsis: true, tooltip: true, show: false },
|
||||
@@ -151,10 +160,7 @@ const columns: TableInstanceColumns[] = [
|
||||
}
|
||||
]
|
||||
|
||||
const queryForm = reactive({
|
||||
description: undefined,
|
||||
status: undefined,
|
||||
deptId: undefined,
|
||||
const queryForm = reactive<UserQuery>({
|
||||
sort: ['createTime,desc']
|
||||
})
|
||||
|
||||
@@ -164,7 +170,7 @@ const {
|
||||
pagination,
|
||||
search,
|
||||
handleDelete
|
||||
} = useTable((p) => listUser({ ...queryForm, page: p.page, size: p.size }), { immediate: true })
|
||||
} = useTable((p) => listUser({ ...queryForm, page: p.page, size: p.size }), { immediate: false })
|
||||
|
||||
// 重置
|
||||
const reset = () => {
|
||||
@@ -193,22 +199,21 @@ const { deptList, getDeptList } = useDept({
|
||||
onSuccess: () => {
|
||||
nextTick(() => {
|
||||
treeRef.value?.expandAll(true)
|
||||
queryForm.deptId = deptList.value[0]?.key as string
|
||||
search()
|
||||
})
|
||||
}
|
||||
})
|
||||
const selectedKeys = computed(() => {
|
||||
return [queryForm.deptId ? queryForm.deptId : '']
|
||||
})
|
||||
watch(deptName, (val) => {
|
||||
getDeptList(val)
|
||||
})
|
||||
|
||||
// 根据选中部门查询
|
||||
const handleSelectDept = (keys: Array<any>) => {
|
||||
if (queryForm.deptId === keys[0]) {
|
||||
queryForm.deptId = undefined
|
||||
// 如已选中,再次点击则取消选中
|
||||
treeRef.value?.selectNode(keys, false)
|
||||
} else {
|
||||
queryForm.deptId = keys.length === 1 ? keys[0] : undefined
|
||||
}
|
||||
queryForm.deptId = keys.length === 1 ? keys[0] : undefined
|
||||
search()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:loading="loading"
|
||||
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
|
||||
:pagination="pagination"
|
||||
:disabledTools="['setting']"
|
||||
:disabledTools="['size', 'setting']"
|
||||
:disabledColumnKeys="['tableName']"
|
||||
@refresh="search"
|
||||
>
|
||||
|
||||