23 Commits

Author SHA1 Message Date
feff838650 release: v3.0.1 2024-05-03 15:43:20 +08:00
029dea0100 style: 优化部分样式 2024-05-03 10:34:41 +08:00
5b71369251 refactor: 优化 queryForm 的 Query 类型使用 2024-05-02 20:22:10 +08:00
秋帆
05ab89d03f refactor: 登录页面,h5端排版更换 2024-05-02 17:04:06 +08:00
秋帆
f49503a924 fix: 用户管理操作-更多显示问题 2024-05-02 12:51:32 +08:00
秋帆
39465dcaa3 fix: 首页卡片显示问题 2024-05-02 12:49:24 +08:00
秋帆
99c37d7de4 fix: 用户管理默认选中顶部 部门 2024-05-02 12:48:52 +08:00
秋帆
2642862569 fix: 账号登录与手机号登录切换验证码刷新 2024-05-02 12:48:11 +08:00
秋帆
9d570a808c fix: h5下登录页面错位显示 2024-05-02 12:46:43 +08:00
c0c5ba8efd fix: 修复账号管理、安全设置路由处理错误 2024-05-01 22:12:47 +08:00
ca25285237 style: 优化根据选中部门查询用户的点击效果 2024-05-01 22:10:15 +08:00
kils
c70d1adbf9 feat: 文件管理增加资源统计,统计总存储量、各类型文件存储占用 2024-04-30 18:04:22 +08:00
70510894ef chore: 优化部分代码 2024-04-29 22:18:39 +08:00
c0a5c2dffe fix: 修复修改公告时保存按钮点击无效的问题 2024-04-29 22:17:46 +08:00
3dfa97e785 fix: 修复文件重命名时不能回显原值的问题 2024-04-29 20:31:14 +08:00
kils
f99c8f1b5a feat: 优化验证码超时显示效果,超时后显示已过期请刷新 2024-04-29 17:59:34 +08:00
kils
724f60eaf6 fix: 修复文件管理数据不刷新和批量操作选中问题
1.删除后不刷新数据
2.Grid布局下批量操作,点击复选框触发两次函数导致无法选中
2024-04-29 15:29:27 +08:00
fcceaf8f51 docs: 新增项目结构介绍 2024-04-28 22:14:07 +08:00
b8a84a3a08 feat: 新增表格全屏、尺寸工具 2024-04-28 21:08:56 +08:00
81dbea8793 fix: 修复由于文件组件名称错误导致的侧边栏筛选功能失效 2024-04-28 20:33:36 +08:00
kils
b2a1658e37 fix: 一级部门不能修改上级部门 2024-04-28 11:23:45 +08:00
kils
5264cf226f fix: 统一性别约束/统一上级部门必填 2024-04-28 11:23:45 +08:00
kils
64648d0c1d fix: main引入md-editor-v3/lib/style.css 2024-04-28 11:23:45 +08:00
83 changed files with 896 additions and 332 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 KiB

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 849 KiB

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -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)
### ✨ 新特性

View File

@@ -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 AdminContinue 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
├─ .gitignoreGit 忽略文件相关配置文件)
├─ .giteeGitee 相关配置目录,实际开发时直接删除)
├─ .githubGitHub 相关配置目录,实际开发时直接删除)
├─ .idea
│ └─ icon.pngIDEA 项目图标,实际开发时直接删除)
├─ .image截图目录实际开发时直接删除
├─ .vscodeVSCode 配置目录)
├─ LICENSE开源协议文件
├─ CHANGELOG.md更新日志文件实际开发时直接删除
└─ README.md项目 README 文件,实际开发时替换为真实内容)
```
## 贡献指南
ContiNew Admin 致力于提供开箱即用持续舒适的开发体验。作为一个开源项目Creator 的初心是希望 ContiNew Admin 依托开源协作模式,提升技术透明度、放大集体智慧、共创优秀实践,源源不断地为企业级项目开发提供助力。

View File

@@ -1,6 +1,6 @@
{
"name": "continew-admin-ui",
"version": "3.0.0",
"version": "3.0.1",
"private": "true",
"scripts": {
"dev": "vite --host",

View File

@@ -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 获取短信验证码 */

View File

@@ -2,6 +2,7 @@
export interface ImageCaptchaResp {
uuid: string
img: string
expireTime: number
}
/** 仪表盘访问趋势类型 */

View File

@@ -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)
}

View File

@@ -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 {}

View File

@@ -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)
}

View File

@@ -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`)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -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;
}

View File

@@ -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'

View File

@@ -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)
}
})

View File

@@ -41,6 +41,7 @@ body[arco-theme='dark'] {
#app {
width: 100vw;
height: 100vh;
min-height: 800px;
overflow: hidden;
}

View File

@@ -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
View File

@@ -17,5 +17,4 @@ interface PageRes<T> {
interface PageQuery {
page: number
size: number
sort: Array<string>
}

View File

@@ -23,5 +23,5 @@ export interface DictState {
/** 状态1启用2禁用 */
type Status = 1 | 2
/** 性别123:未知) */
type Gender = 1 | 2 | 3
/** 性别120:未知) */
type Gender = 1 | 2 | 0

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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: '请输入验证码' }]
}

View File

@@ -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: '请输入验证码' }]
}

View File

@@ -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;
}
}
}
}

View File

@@ -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']
})

View File

@@ -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']
})

View File

@@ -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']
})

View File

@@ -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' ? '修改手机号' : '修改邮箱'))

View File

@@ -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 }}

View File

@@ -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: '请输入名称' }] },
{

View File

@@ -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']
})

View File

@@ -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']
})

View File

@@ -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']
})

View File

@@ -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 })

View File

@@ -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
}
})

View File

@@ -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>

View 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>&nbsp;{{ 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>

View File

@@ -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>

View File

@@ -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()
}

View File

@@ -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']
})

View File

@@ -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)

View File

@@ -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']
})

View File

@@ -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']
})

View File

@@ -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']
})

View File

@@ -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()
}

View File

@@ -8,7 +8,7 @@
:loading="loading"
:scroll="{ x: '100%', y: '100%', minWidth: 1000 }"
:pagination="pagination"
:disabledTools="['setting']"
:disabledTools="['size', 'setting']"
:disabledColumnKeys="['tableName']"
@refresh="search"
>