feat: migrate business modules to Vue3

- Migrate system management pages: user, role, menu, dept, dict, notice, post, config
- Migrate booksystem pages: account, book, statistics, operations
- Migrate stocksystem pages: stocks, trends, stockindex, stockbasic
- Create corresponding API modules with TypeScript types
- Update router configuration with all module routes
- Add permission control (v-hasPermi directive) to all pages
feature/20260628/refactor-frontend-modernization
Lxy 3 weeks ago
parent b77957c16e
commit b6dbf000e0

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询交易账户列表
export function getAccountList(params: any) {
return request({ url: '/booksystem/account/list', method: 'get', params })
}
// 查询交易账户详细
export function getAccount(id: any) {
return request({ url: `/booksystem/account/${id}`, method: 'get' })
}
// 新增交易账户
export function addAccount(data: any) {
return request({ url: '/booksystem/account', method: 'post', data })
}
// 修改交易账户
export function updateAccount(data: any) {
return request({ url: '/booksystem/account', method: 'put', data })
}
// 删除交易账户
export function delAccount(id: any) {
return request({ url: `/booksystem/account/${id}`, method: 'delete' })
}

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询交易记账列表
export function getBookList(params: any) {
return request({ url: '/booksystem/book/list', method: 'get', params })
}
// 查询交易记账详细
export function getBook(id: any) {
return request({ url: `/booksystem/book/${id}`, method: 'get' })
}
// 新增交易记账
export function addBook(data: any) {
return request({ url: '/booksystem/book', method: 'post', data })
}
// 修改交易记账
export function updateBook(data: any) {
return request({ url: '/booksystem/book', method: 'put', data })
}
// 删除交易记账
export function delBook(id: any) {
return request({ url: `/booksystem/book/${id}`, method: 'delete' })
}

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询当日操作列表
export function getOperationsList(params: any) {
return request({ url: '/booksystem/operations/list', method: 'get', params })
}
// 查询当日操作详细
export function getOperations(id: any) {
return request({ url: `/booksystem/operations/${id}`, method: 'get' })
}
// 新增当日操作
export function addOperations(data: any) {
return request({ url: '/booksystem/operations', method: 'post', data })
}
// 修改当日操作
export function updateOperations(data: any) {
return request({ url: '/booksystem/operations', method: 'put', data })
}
// 删除当日操作
export function delOperations(id: any) {
return request({ url: `/booksystem/operations/${id}`, method: 'delete' })
}

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询单笔操作统计列表
export function getStatisticsList(params: any) {
return request({ url: '/booksystem/statistics/list', method: 'get', params })
}
// 查询单笔操作统计详细
export function getStatistics(id: any) {
return request({ url: `/booksystem/statistics/${id}`, method: 'get' })
}
// 新增单笔操作统计
export function addStatistics(data: any) {
return request({ url: '/booksystem/statistics', method: 'post', data })
}
// 修改单笔操作统计
export function updateStatistics(data: any) {
return request({ url: '/booksystem/statistics', method: 'put', data })
}
// 删除单笔操作统计
export function delStatistics(id: any) {
return request({ url: `/booksystem/statistics/${id}`, method: 'delete' })
}

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询基础数据列表
export function getStockbasicList(params: any) {
return request({ url: '/stocksystem/stockbasic/list', method: 'get', params })
}
// 查询基础数据详细
export function getStockbasic(id: any) {
return request({ url: `/stocksystem/stockbasic/${id}`, method: 'get' })
}
// 新增基础数据
export function addStockbasic(data: any) {
return request({ url: '/stocksystem/stockbasic', method: 'post', data })
}
// 修改基础数据
export function updateStockbasic(data: any) {
return request({ url: '/stocksystem/stockbasic', method: 'put', data })
}
// 删除基础数据
export function delStockbasic(id: any) {
return request({ url: `/stocksystem/stockbasic/${id}`, method: 'delete' })
}

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询指数交易行情列表
export function getStockindexList(params: any) {
return request({ url: '/stocksystem/stockindex/list', method: 'get', params })
}
// 查询指数交易行情详细
export function getStockindex(id: any) {
return request({ url: `/stocksystem/stockindex/${id}`, method: 'get' })
}
// 新增指数交易行情
export function addStockindex(data: any) {
return request({ url: '/stocksystem/stockindex', method: 'post', data })
}
// 修改指数交易行情
export function updateStockindex(data: any) {
return request({ url: '/stocksystem/stockindex', method: 'put', data })
}
// 删除指数交易行情
export function delStockindex(id: any) {
return request({ url: `/stocksystem/stockindex/${id}`, method: 'delete' })
}

@ -0,0 +1,31 @@
import request from '@/api/request'
// 查询行情数据列表
export function getStocksList(params: any) {
return request({ url: '/stocksystem/stocks/listB', method: 'get', params })
}
// 查询行情数据详细
export function getStocks(id: any) {
return request({ url: `/stocksystem/stocks/${id}`, method: 'get' })
}
// 分析行情数据
export function analysisStocks(data: any) {
return request({ url: '/stocksystem/stocks/analysis', method: 'post', data })
}
// 新增行情数据
export function addStocks(data: any) {
return request({ url: '/stocksystem/stocks', method: 'post', data })
}
// 修改行情数据
export function updateStocks(data: any) {
return request({ url: '/stocksystem/stocks', method: 'put', data })
}
// 删除行情数据
export function delStocks(id: any) {
return request({ url: `/stocksystem/stocks/${id}`, method: 'delete' })
}

@ -0,0 +1,71 @@
import request from '@/api/request'
// 查询动量结果列表
export function getTrendsList(params: any) {
return request({ url: '/stocksystem/trends/list', method: 'get', params })
}
// 查询时间段内动量结果列表
export function getTrendsSection(params: any) {
return request({ url: '/stocksystem/trends/listSection', method: 'get', params })
}
// 查询时间段内成交量结果列表
export function getTradeVolume(params: any) {
return request({ url: '/stocksystem/trends/listTradeVolume', method: 'get', params })
}
// 查询指数内涨停板数量
export function getStockIndexLimitUp(params: any) {
return request({ url: '/stocksystem/trends/listStockIndexLimitUp', method: 'get', params })
}
// 查询板块内跌停板数量
export function getStockIndexLimitDown(params: any) {
return request({ url: '/stocksystem/trends/listStockIndexLimitDown', method: 'get', params })
}
// 查询创新高板块
export function getStockIndexHighRecord(params: any) {
return request({ url: '/stocksystem/trends/listStockIndexHighRocord', method: 'get', params })
}
// 查询创新低板块
export function getStockIndexLowRecord(params: any) {
return request({ url: '/stocksystem/trends/listStockIndexLowRocord', method: 'get', params })
}
// 查询时间段内动量结果列表(按板块)
export function getSectionByBlemind(params: any) {
return request({ url: '/stocksystem/trends/listSectionByBlemind', method: 'get', params })
}
// 查询个股历史k线数据
export function getStockHistory(params: any) {
return request({ url: '/stocksystem/stocks/stockHistory', method: 'get', params })
}
// 板块历史k线数据
export function getStockIndexHistory(params: any) {
return request({ url: '/stocksystem/stocks/stockIndexHistory', method: 'get', params })
}
// 查询动量结果详细
export function getTrends(id: any) {
return request({ url: `/stocksystem/trends/${id}`, method: 'get' })
}
// 新增动量结果
export function addTrends(data: any) {
return request({ url: '/stocksystem/trends', method: 'post', data })
}
// 修改动量结果
export function updateTrends(data: any) {
return request({ url: '/stocksystem/trends', method: 'put', data })
}
// 删除动量结果
export function delTrends(id: any) {
return request({ url: `/stocksystem/trends/${id}`, method: 'delete' })
}

@ -0,0 +1,36 @@
import request from '@/api/request'
// 查询参数列表
export function getConfigList(params: any) {
return request({ url: '/system/config/list', method: 'get', params })
}
// 查询参数详细
export function getConfig(configId: any) {
return request({ url: `/system/config/${configId}`, method: 'get' })
}
// 根据参数键名查询参数值
export function getConfigKey(configKey: string) {
return request({ url: `/system/config/configKey/${configKey}`, method: 'get' })
}
// 新增参数配置
export function addConfig(data: any) {
return request({ url: '/system/config', method: 'post', data })
}
// 修改参数配置
export function updateConfig(data: any) {
return request({ url: '/system/config', method: 'put', data })
}
// 删除参数配置
export function delConfig(configId: any) {
return request({ url: `/system/config/${configId}`, method: 'delete' })
}
// 刷新参数缓存
export function refreshConfigCache() {
return request({ url: '/system/config/refreshCache', method: 'delete' })
}

@ -0,0 +1,41 @@
import request from '@/api/request'
// 查询部门列表
export function getDeptList(params?: any) {
return request({ url: '/system/dept/list', method: 'get', params })
}
// 查询部门列表(排除节点)
export function getDeptListExcludeChild(deptId: any) {
return request({ url: `/system/dept/list/exclude/${deptId}`, method: 'get' })
}
// 查询部门详细
export function getDept(deptId: any) {
return request({ url: `/system/dept/${deptId}`, method: 'get' })
}
// 查询部门下拉树结构
export function getDeptTreeselect() {
return request({ url: '/system/dept/treeselect', method: 'get' })
}
// 根据角色ID查询部门树结构
export function roleDeptTreeselect(roleId: any) {
return request({ url: `/system/dept/roleDeptTreeselect/${roleId}`, method: 'get' })
}
// 新增部门
export function addDept(data: any) {
return request({ url: '/system/dept', method: 'post', data })
}
// 修改部门
export function updateDept(data: any) {
return request({ url: '/system/dept', method: 'put', data })
}
// 删除部门
export function delDept(deptId: any) {
return request({ url: `/system/dept/${deptId}`, method: 'delete' })
}

@ -0,0 +1,68 @@
import request from '@/api/request'
// 查询字典类型列表
export function getDictTypeList(params: any) {
return request({ url: '/system/dict/type/list', method: 'get', params })
}
// 查询字典类型详细
export function getDictType(dictId: any) {
return request({ url: `/system/dict/type/${dictId}`, method: 'get' })
}
// 新增字典类型
export function addDictType(data: any) {
return request({ url: '/system/dict/type', method: 'post', data })
}
// 修改字典类型
export function updateDictType(data: any) {
return request({ url: '/system/dict/type', method: 'put', data })
}
// 删除字典类型
export function delDictType(dictId: any) {
return request({ url: `/system/dict/type/${dictId}`, method: 'delete' })
}
// 刷新字典缓存
export function refreshDictCache() {
return request({ url: '/system/dict/type/refreshCache', method: 'delete' })
}
// 获取字典选择框列表
export function getDictTypeOptionselect() {
return request({ url: '/system/dict/type/optionselect', method: 'get' })
}
// ===== 字典数据 =====
// 查询字典数据列表
export function getDictDataList(params: any) {
return request({ url: '/system/dict/data/list', method: 'get', params })
}
// 查询字典数据详细
export function getDictData(dictCode: any) {
return request({ url: `/system/dict/data/${dictCode}`, method: 'get' })
}
// 根据字典类型查询字典数据信息
export function getDicts(dictType: string) {
return request({ url: `/system/dict/data/type/${dictType}`, method: 'get' })
}
// 新增字典数据
export function addDictData(data: any) {
return request({ url: '/system/dict/data', method: 'post', data })
}
// 修改字典数据
export function updateDictData(data: any) {
return request({ url: '/system/dict/data', method: 'put', data })
}
// 删除字典数据
export function delDictData(dictCode: any) {
return request({ url: `/system/dict/data/${dictCode}`, method: 'delete' })
}

@ -0,0 +1,36 @@
import request from '@/api/request'
// 查询菜单列表
export function getMenuList(params?: any) {
return request({ url: '/system/menu/list', method: 'get', params })
}
// 查询菜单详细
export function getMenu(menuId: any) {
return request({ url: `/system/menu/${menuId}`, method: 'get' })
}
// 查询菜单下拉树结构
export function getMenuTreeselect() {
return request({ url: '/system/menu/treeselect', method: 'get' })
}
// 根据角色ID查询菜单下拉树结构
export function roleMenuTreeselect(roleId: any) {
return request({ url: `/system/menu/roleMenuTreeselect/${roleId}`, method: 'get' })
}
// 新增菜单
export function addMenu(data: any) {
return request({ url: '/system/menu', method: 'post', data })
}
// 修改菜单
export function updateMenu(data: any) {
return request({ url: '/system/menu', method: 'put', data })
}
// 删除菜单
export function delMenu(menuId: any) {
return request({ url: `/system/menu/${menuId}`, method: 'delete' })
}

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询公告列表
export function getNoticeList(params: any) {
return request({ url: '/system/notice/list', method: 'get', params })
}
// 查询公告详细
export function getNotice(noticeId: any) {
return request({ url: `/system/notice/${noticeId}`, method: 'get' })
}
// 新增公告
export function addNotice(data: any) {
return request({ url: '/system/notice', method: 'post', data })
}
// 修改公告
export function updateNotice(data: any) {
return request({ url: '/system/notice', method: 'put', data })
}
// 删除公告
export function delNotice(noticeId: any) {
return request({ url: `/system/notice/${noticeId}`, method: 'delete' })
}

@ -0,0 +1,26 @@
import request from '@/api/request'
// 查询岗位列表
export function getPostList(params: any) {
return request({ url: '/system/post/list', method: 'get', params })
}
// 查询岗位详细
export function getPost(postId: any) {
return request({ url: `/system/post/${postId}`, method: 'get' })
}
// 新增岗位
export function addPost(data: any) {
return request({ url: '/system/post', method: 'post', data })
}
// 修改岗位
export function updatePost(data: any) {
return request({ url: '/system/post', method: 'put', data })
}
// 删除岗位
export function delPost(postId: any) {
return request({ url: `/system/post/${postId}`, method: 'delete' })
}

@ -0,0 +1,81 @@
import request from '@/api/request'
// 查询角色列表
export function getRoleList(params: any) {
return request({ url: '/system/role/list', method: 'get', params })
}
// 查询角色详细
export function getRole(roleId: any) {
return request({ url: `/system/role/${roleId}`, method: 'get' })
}
// 新增角色
export function addRole(data: any) {
return request({ url: '/system/role', method: 'post', data })
}
// 修改角色
export function updateRole(data: any) {
return request({ url: '/system/role', method: 'put', data })
}
// 角色数据权限
export function dataScope(data: any) {
return request({ url: '/system/role/dataScope', method: 'put', data })
}
// 角色状态修改
export function changeRoleStatus(roleId: any, status: string) {
return request({ url: '/system/role/changeStatus', method: 'put', data: { roleId, status } })
}
// 删除角色
export function delRole(roleId: any) {
return request({ url: `/system/role/${roleId}`, method: 'delete' })
}
// 查询角色已授权用户列表
export function allocatedUserList(params: any) {
return request({ url: '/system/role/authUser/allocatedList', method: 'get', params })
}
// 查询角色未授权用户列表
export function unallocatedUserList(params: any) {
return request({ url: '/system/role/authUser/unallocatedList', method: 'get', params })
}
// 取消用户授权角色
export function authUserCancel(data: any) {
return request({ url: '/system/role/authUser/cancel', method: 'put', data })
}
// 批量取消用户授权角色
export function authUserCancelAll(data: any) {
return request({ url: '/system/role/authUser/cancelAll', method: 'put', params: data })
}
// 授权用户选择
export function authUserSelectAll(data: any) {
return request({ url: '/system/role/authUser/selectAll', method: 'put', params: data })
}
// 查询菜单下拉树结构
export function getMenuTreeselect() {
return request({ url: '/system/menu/treeselect', method: 'get' })
}
// 根据角色ID查询菜单下拉树结构
export function roleMenuTreeselect(roleId: any) {
return request({ url: `/system/menu/roleMenuTreeselect/${roleId}`, method: 'get' })
}
// 查询部门下拉树结构
export function getDeptTreeselect() {
return request({ url: '/system/dept/treeselect', method: 'get' })
}
// 根据角色ID查询部门树结构
export function roleDeptTreeselect(roleId: any) {
return request({ url: `/system/dept/roleDeptTreeselect/${roleId}`, method: 'get' })
}

@ -1,88 +1,76 @@
import request from '@/api/request'
/**
*
*/
// 查询用户列表
export function getUserList(params: any) {
return request({
url: '/system/user/list',
method: 'get',
params,
})
return request({ url: '/system/user/list', method: 'get', params })
}
/**
*
*/
export function getUser(userId: number | string) {
return request({
url: `/system/user/${userId}`,
method: 'get',
})
// 查询用户详细
export function getUser(userId: any) {
return request({ url: `/system/user/${userId}`, method: 'get' })
}
/**
*
*/
// 新增用户
export function addUser(data: any) {
return request({
url: '/system/user',
method: 'post',
data,
})
return request({ url: '/system/user', method: 'post', data })
}
/**
*
*/
// 修改用户
export function updateUser(data: any) {
return request({
url: '/system/user',
method: 'put',
data,
})
return request({ url: '/system/user', method: 'put', data })
}
/**
*
*/
export function delUser(userId: number | string | (number | string)[]) {
return request({
url: `/system/user/${userId}`,
method: 'delete',
})
// 删除用户
export function delUser(userId: any) {
return request({ url: `/system/user/${userId}`, method: 'delete' })
}
/**
*
*/
export function resetUserPwd(userId: number | string, password: string) {
return request({
url: `/system/user/resetPwd`,
method: 'put',
data: { userId, password },
})
// 用户密码重置
export function resetUserPwd(userId: any, password: string) {
return request({ url: '/system/user/resetPwd', method: 'put', data: { userId, password } })
}
/**
*
*/
export function changeUserStatus(userId: number | string, status: string) {
return request({
url: `/system/user/changeStatus`,
method: 'put',
data: { userId, status },
})
// 用户状态修改
export function changeUserStatus(userId: any, status: string) {
return request({ url: '/system/user/changeStatus', method: 'put', data: { userId, status } })
}
/**
*
*/
export function exportUser(params: any) {
return request({
url: '/system/user/export',
method: 'post',
data: params,
responseType: 'blob',
})
// 查询用户个人信息
export function getUserProfile() {
return request({ url: '/system/user/profile', method: 'get' })
}
// 修改用户个人信息
export function updateUserProfile(data: any) {
return request({ url: '/system/user/profile', method: 'put', data })
}
// 用户密码重置
export function updateUserPwd(oldPassword: string, newPassword: string) {
return request({ url: '/system/user/profile/updatePwd', method: 'put', params: { oldPassword, newPassword } })
}
// 用户头像上传
export function uploadAvatar(data: any) {
return request({ url: '/system/user/profile/avatar', method: 'post', data })
}
// 查询授权角色
export function getAuthRole(userId: any) {
return request({ url: `/system/user/authRole/${userId}`, method: 'get' })
}
// 保存授权角色
export function updateAuthRole(data: any) {
return request({ url: '/system/user/authRole', method: 'put', params: data })
}
// 查询部门下拉树结构
export function getDeptTreeselect() {
return request({ url: '/system/dept/treeselect', method: 'get' })
}
// 查询配置键值
export function getConfigKey(configKey: string) {
return request({ url: `/system/config/configKey/${configKey}`, method: 'get' })
}

@ -11,11 +11,22 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AppMain: typeof import('./components/layout/AppMain.vue')['default']
BarChart: typeof import('./components/ui/Charts/BarChart.vue')['default']
Breadcrumb: typeof import('./components/layout/Breadcrumb/index.vue')['default']
Crontab: typeof import('./components/business/Crontab/index.vue')['default']
Day: typeof import('./components/business/Crontab/day.vue')['default']
DictTag: typeof import('./components/ui/DictTag/index.vue')['default']
Editor: typeof import('./components/ui/Editor/index.vue')['default']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCol: typeof import('element-plus/es')['ElCol']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
@ -23,13 +34,53 @@ declare module 'vue' {
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElRow: typeof import('element-plus/es')['ElRow']
ElSelect: typeof import('element-plus/es')['ElSelect']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElSwitch: typeof import('element-plus/es')['ElSwitch']
ElTable: typeof import('element-plus/es')['ElTable']
ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElTag: typeof import('element-plus/es')['ElTag']
ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
ElTree: typeof import('element-plus/es')['ElTree']
ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect']
ElUpload: typeof import('element-plus/es')['ElUpload']
FileUpload: typeof import('./components/ui/FileUpload/index.vue')['default']
HeaderSearch: typeof import('./components/ui/HeaderSearch/index.vue')['default']
HeatmapChart: typeof import('./components/ui/Charts/HeatmapChart.vue')['default']
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
Hour: typeof import('./components/business/Crontab/hour.vue')['default']
IconSelect: typeof import('./components/ui/IconSelect/index.vue')['default']
ImageUpload: typeof import('./components/ui/ImageUpload/index.vue')['default']
LineChart: typeof import('./components/ui/Charts/LineChart.vue')['default']
Min: typeof import('./components/business/Crontab/min.vue')['default']
Month: typeof import('./components/business/Crontab/month.vue')['default']
Navbar: typeof import('./components/layout/Navbar/index.vue')['default']
Pagination: typeof import('./components/ui/Pagination/index.vue')['default']
ParentView: typeof import('./components/ParentView.vue')['default']
PieChart: typeof import('./components/ui/Charts/PieChart.vue')['default']
Result: typeof import('./components/business/Crontab/result.vue')['default']
RightToolbar: typeof import('./components/business/RightToolbar/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Screenfull: typeof import('./components/ui/Screenfull/index.vue')['default']
ScrollPane: typeof import('./components/layout/TagsView/ScrollPane.vue')['default']
Second: typeof import('./components/business/Crontab/second.vue')['default']
Sidebar: typeof import('./components/layout/Sidebar/index.vue')['default']
SizeSelect: typeof import('./components/ui/SizeSelect/index.vue')['default']
TagsView: typeof import('./components/layout/TagsView/index.vue')['default']
Week: typeof import('./components/business/Crontab/week.vue')['default']
Year: typeof import('./components/business/Crontab/year.vue')['default']
}
export interface GlobalDirectives {
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
}
}

@ -363,27 +363,25 @@ function getCycleArr(rule: string, limit: number, status: boolean): number[] {
return arr
}
function formatDate(value: Date, type?: string): string | number {
function formatDate(value: Date, type?: string): number {
const time = typeof value === 'number' ? new Date(value) : value
const Y = time.getFullYear()
const M = time.getMonth() + 1
const D = time.getDate()
const h = time.getHours()
const m = time.getMinutes()
const s = time.getSeconds()
const week = time.getDay()
if (!type) {
return `${Y}-${M < 10 ? '0' + M : M}-${D < 10 ? '0' + D : D} ${h < 10 ? '0' + h : h}:${m < 10 ? '0' + m : m}:${s < 10 ? '0' + s : s}`
} else if (type === 'week') {
if (type === 'week') {
return week + 1
}
return ''
return 0
}
function checkDate(value: string): boolean {
const time = new Date(value)
const format = formatDate(time)
const Y = time.getFullYear()
const M = time.getMonth() + 1
const D = time.getDate()
const h = time.getHours()
const m = time.getMinutes()
const s = time.getSeconds()
const format = `${Y}-${M < 10 ? '0' + M : M}-${D < 10 ? '0' + D : D} ${h < 10 ? '0' + h : h}:${m < 10 ? '0' + m : m}:${s < 10 ? '0' + s : s}`
return value === format
}

@ -40,7 +40,7 @@
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { ref, computed, watch } from 'vue'
interface Props {
check: (value: number, min: number, max: number) => number

@ -26,7 +26,7 @@ interface BreadcrumbItem {
name?: string
}
const levelList = ref<BreadcrumbItem[] | null>(null)
const levelList = ref<BreadcrumbItem[]>([])
function getBreadcrumb() {
// Only show routes with meta.title

@ -53,10 +53,10 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { Fold, Expand, ArrowDown, Sunny, Moon } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ElMessageBox } from 'element-plus'
import Breadcrumb from '@/components/layout/Breadcrumb/index.vue'
import HeaderSearch from '@/components/ui/HeaderSearch/index.vue'
import Screenfull from '@/components/ui/Screenfull/index.vue'

@ -24,10 +24,6 @@ function handleScroll(e: WheelEvent) {
}
}
function emitScroll() {
// Emit scroll event to parent
}
function moveToTarget(currentTag: HTMLElement) {
const container = scrollContainerRef.value?.$el
if (!container) return

@ -5,7 +5,7 @@
v-for="tag in visitedViews"
:key="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
:to="{ path: tag.path, query: tag.query }"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle.prevent="!isAffix(tag) && closeSelectedTag(tag)"
@ -67,7 +67,7 @@ const tagRefs = ref<HTMLElement[]>([])
const visible = ref(false)
const top = ref(0)
const left = ref(0)
const selectedTag = ref<TagView>({})
const selectedTag = ref<TagView>({ path: '', fullPath: '' })
const affixTags = ref<TagView[]>([])
const visitedViews = computed(() => tagsViewStore.visitedViews)
@ -185,16 +185,16 @@ function closeSelectedTag(view: TagView) {
}
function closeRightTags() {
tagsViewStore.delViewsFrom(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
tagsViewStore.delViewsFrom(selectedTag.value).then(({ visitedViews }) => {
if (!visitedViews.find((i: TagView) => i.fullPath === route.fullPath)) {
toLastView(visitedViews)
}
})
}
function closeLeftTags() {
tagsViewStore.delViewsTo(selectedTag.value).then(visitedViews => {
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
tagsViewStore.delViewsTo(selectedTag.value).then(({ visitedViews }) => {
if (!visitedViews.find((i: TagView) => i.fullPath === route.fullPath)) {
toLastView(visitedViews)
}
})
@ -227,8 +227,9 @@ function toLastView(visitedViewsList: TagView[], _view?: TagView) {
function openMenu(tag: TagView, e: MouseEvent) {
const menuMinWidth = 105
const offsetLeft = (e.currentTarget as HTMLElement)?.closest('.tags-view-container')?.getBoundingClientRect().left || 0
const offsetWidth = (e.currentTarget as HTMLElement)?.closest('.tags-view-container')?.offsetWidth || 0
const container = (e.currentTarget as HTMLElement)?.closest('.tags-view-container') as HTMLElement | null
const offsetLeft = container?.getBoundingClientRect().left || 0
const offsetWidth = container?.offsetWidth || 0
const maxLeft = offsetWidth - menuMinWidth
const leftPos = e.clientX - offsetLeft + 15

@ -47,7 +47,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{
'update:content': [value: string]
'on-change': [data: { html: string; text: string; quill: Quill }]
'on-change': [data: { html: string; text: string; quill: any }]
'on-text-change': [delta: any, oldDelta: any, source: string]
'on-selection-change': [range: any, oldRange: any, source: string]
'on-editor-change': [eventName: string, ...args: any[]]
@ -61,7 +61,7 @@ const headers = {
Authorization: `Bearer ${getToken()}`
}
let quillInstance: Quill | null = null
let quillInstance: any = null
const currentValue = ref('')
const styles = computed(() => {
@ -120,7 +120,7 @@ function init() {
quillInstance.pasteHTML(currentValue.value)
quillInstance.on('text-change', (_delta, _oldDelta, source) => {
quillInstance.on('text-change', (_delta: any, _oldDelta: any, _source: string) => {
if (!editorRef.value) return
const html = editorRef.value.children[0]?.innerHTML || ''
const text = quillInstance!.getText()
@ -129,15 +129,15 @@ function init() {
emit('on-change', { html, text, quill: quillInstance! })
})
quillInstance.on('text-change', (delta, oldDelta, source) => {
quillInstance.on('text-change', (delta: any, oldDelta: any, source: string) => {
emit('on-text-change', delta, oldDelta, source)
})
quillInstance.on('selection-change', (range, oldRange, source) => {
quillInstance.on('selection-change', (range: any, oldRange: any, source: string) => {
emit('on-selection-change', range, oldRange, source)
})
quillInstance.on('editor-change', (eventName, ...args) => {
quillInstance.on('editor-change', (eventName: string, ...args: any[]) => {
emit('on-editor-change', eventName, ...args)
})
}

@ -70,7 +70,6 @@ const headers = {
Authorization: `Bearer ${getToken()}`
}
const fileList = ref<UploadFileItem[]>([])
const uploadRef = ref()
const showTip = computed(() => props.isShowTip && (props.fileType.length > 0 || props.fileSize > 0))

@ -23,31 +23,29 @@
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ref, computed, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import path from 'path-browserify'
import Fuse from 'fuse.js'
import type { ElSelect } from 'element-plus'
import { usePermissionStore } from '@/stores/modules/permission'
import type { SearchOption } from '@/types'
interface FuseResultItem {
path: string
title: string[]
}
const route = useRoute()
const router = useRouter()
const permissionStore = usePermissionStore()
const headerSearchSelectRef = ref<InstanceType<typeof ElSelect> | null>(null)
const search = ref('')
const options = ref<Fuse.FuseResult<FuseResultItem>[]>([])
const options = ref<any[]>([])
const searchPool = ref<FuseResultItem[]>([])
const showSearch = ref(false)
let fuse: Fuse<FuseResultItem> | undefined
let fuse: any = undefined
const routes = computed(() => permissionStore.routes)

@ -18,7 +18,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import { scrollTo } from '@/utils/scroll-to'
import type { PaginationProps, PaginationEvent } from '@/types'
import type { PaginationEvent } from '@/types'
interface Props {
total: number

@ -1,7 +1,7 @@
<template>
<el-dropdown trigger="click" @command="handleSetSize">
<div class="size-select-wrapper">
<el-icon :size="18"><Resize /></el-icon>
<el-icon :size="18"><Operation /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
@ -21,7 +21,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Resize } from '@element-plus/icons-vue'
import { Operation } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { useAppStore } from '@/stores/modules/app'
import { useTagsViewStore } from '@/stores/modules/tagsView'

@ -61,6 +61,139 @@ export const constantRoutes: RouteRecordRaw[] = [
},
],
},
// 系统管理模块
{
path: '/system',
component: () => import('@/layout/index.vue'),
redirect: '/system/user',
meta: { title: '系统管理', icon: 'Setting' },
children: [
{
path: 'user',
name: 'SystemUser',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理' },
},
{
path: 'role',
name: 'SystemRole',
component: () => import('@/views/system/role/index.vue'),
meta: { title: '角色管理' },
},
{
path: 'menu',
name: 'SystemMenu',
component: () => import('@/views/system/menu/index.vue'),
meta: { title: '菜单管理' },
},
{
path: 'dept',
name: 'SystemDept',
component: () => import('@/views/system/dept/index.vue'),
meta: { title: '部门管理' },
},
{
path: 'dict',
name: 'SystemDict',
component: () => import('@/views/system/dict/index.vue'),
meta: { title: '字典管理' },
},
{
path: 'dict-data/index/:dictId',
name: 'SystemDictData',
component: () => import('@/views/system/dict/data.vue'),
meta: { title: '字典数据', hidden: true },
},
{
path: 'notice',
name: 'SystemNotice',
component: () => import('@/views/system/notice/index.vue'),
meta: { title: '通知公告' },
},
{
path: 'post',
name: 'SystemPost',
component: () => import('@/views/system/post/index.vue'),
meta: { title: '岗位管理' },
},
{
path: 'config',
name: 'SystemConfig',
component: () => import('@/views/system/config/index.vue'),
meta: { title: '参数设置' },
},
],
},
// 账本系统模块
{
path: '/booksystem',
component: () => import('@/layout/index.vue'),
redirect: '/booksystem/account',
meta: { title: '账本系统', icon: 'Money' },
children: [
{
path: 'account',
name: 'BooksystemAccount',
component: () => import('@/views/booksystem/account/index.vue'),
meta: { title: '账户管理' },
},
{
path: 'book',
name: 'BooksystemBook',
component: () => import('@/views/booksystem/book/index.vue'),
meta: { title: '账本管理' },
},
{
path: 'statistics',
name: 'BooksystemStatistics',
component: () => import('@/views/booksystem/statistics/index.vue'),
meta: { title: '统计分析' },
},
{
path: 'operations',
name: 'BooksystemOperations',
component: () => import('@/views/booksystem/operations/index.vue'),
meta: { title: '操作记录' },
},
],
},
// 股票系统模块
{
path: '/stocksystem',
component: () => import('@/layout/index.vue'),
redirect: '/stocksystem/stocks',
meta: { title: '股票系统', icon: 'TrendCharts' },
children: [
{
path: 'stocks',
name: 'StocksystemStocks',
component: () => import('@/views/stocksystem/stocks/index.vue'),
meta: { title: '股票管理' },
},
{
path: 'trends',
name: 'StocksystemTrends',
component: () => import('@/views/stocksystem/trends/index.vue'),
meta: { title: '趋势数据' },
},
{
path: 'stockindex',
name: 'StocksystemStockindex',
component: () => import('@/views/stocksystem/stockindex/index.vue'),
meta: { title: '指数信息' },
},
{
path: 'stockbasic',
name: 'StocksystemStockbasic',
component: () => import('@/views/stocksystem/stockbasic/index.vue'),
meta: { title: '基础数据' },
},
],
},
// Catch-all 404 - must be added last
{
path: '/:pathMatch(.*)*',

@ -1,6 +1,5 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { RouteLocationNormalizedLoaded } from 'vue-router'
import type { TagView } from '@/types'
export const useTagsViewStore = defineStore('tagsView', () => {

@ -0,0 +1,28 @@
declare module 'quill' {
const Quill: any
export default Quill
}
declare module 'path-browserify' {
export function resolve(...paths: string[]): string
export function relative(from: string, to: string): string
export function normalize(path: string): string
export function join(...paths: string[]): string
export function dirname(path: string): string
export function basename(path: string, ext?: string): string
export function extname(path: string): string
export function format(pathObject: any): string
export function parse(path: string): any
export const sep: string
export const delimiter: string
}
declare module 'fuse.js' {
const Fuse: any
export default Fuse
}
declare module 'screenfull' {
const screenfull: any
export default screenfull
}

@ -8,20 +8,16 @@ function easeInOutQuad(t: number, b: number, c: number, d: number): number {
return (-c / 2) * (t * (t - 2) - 1) + b
}
interface AnimationFrameRequester {
(callback: FrameRequestCallback): number
}
const requestAnimFrame: AnimationFrameRequester =
const requestAnimFrame =
typeof window !== 'undefined'
? (window.requestAnimationFrame ||
(window as any).webkitRequestAnimationFrame ||
(window as any).mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60)
function (callback: FrameRequestCallback): number {
return window.setTimeout(callback, 1000 / 60) as unknown as number
})
: function (callback) {
setTimeout(callback, 1000 / 60)
: function (callback: FrameRequestCallback): number {
return setTimeout(callback, 1000 / 60) as unknown as number
}
/**
@ -48,13 +44,10 @@ function move(amountToScroll: number, start: number, increment: number, startTim
}
/**
* Scroll to a specified position
* Scroll to a specified element or position
* @param element - The target element or position
* @param duration - Scroll duration in ms
*/
export function scrollTo(element: number | Element, duration?: number): void
export function scrollTo(y: number, duration?: number): void
export function scrollTo(element: number | Element, duration: number = 500): void {
export function scrollTo(element: number | Element, _duration: number = 500): void {
if (typeof window === 'undefined') return
if (typeof element === 'number') {

@ -0,0 +1,95 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="交易日期" prop="tradeDay">
<el-date-picker v-model="queryParams.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['booksystem:account:add']"></el-button></el-col>
<el-col :span="1.5"><el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['booksystem:account:edit']"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['booksystem:account:remove']"></el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['booksystem:account:export']"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="accountList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="交易日期" align="center" prop="tradeDay" width="180" />
<el-table-column label="星期" align="center" prop="weekDay" width="180" />
<el-table-column label="净资产" align="center" prop="assets" />
<el-table-column label="总资产" align="center" prop="totalAssets" />
<el-table-column label="当日盈亏" align="center" prop="profit" />
<el-table-column label="当日净资产盈亏比例" align="center" prop="assetsDiff" />
<el-table-column label="当日总资产盈亏比例" align="center" prop="totalDiff" />
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="交易日期" prop="tradeDay">
<el-date-picker clearable size="small" v-model="form.tradeDay" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" @change="handleChange" />
</el-form-item>
<el-form-item label="净资产" prop="assets"><el-input v-model="form.assets" placeholder="请输入净资产" /></el-form-item>
<el-form-item label="总资产" prop="totalAssets"><el-input v-model="form.totalAssets" placeholder="请输入总资产" /></el-form-item>
<el-form-item label="当日盈亏" prop="profit"><el-input v-model="form.profit" placeholder="请输入当日盈亏" /></el-form-item>
<el-form-item label="净资产盈亏比例" prop="assetsDiff"><el-input v-model="form.assetsDiff" placeholder="请输入净资产盈亏比例" /></el-form-item>
<el-form-item label="总资产盈亏比例" prop="totalDiff"><el-input v-model="form.totalDiff" placeholder="请输入总资产盈亏比例" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getAccountList, getAccount, delAccount, addAccount, updateAccount } from '@/api/modules/booksystem/account'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const accountList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const weekOptions = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
const queryParams = reactive({ pageNum: 1, pageSize: 10, tradeDay: '' })
const form = ref<any>({ id: null, tradeDay: '', weekDay: '', assets: '', totalAssets: '', profit: '', assetsDiff: '', totalDiff: '' })
const rules = {}
async function getList() { loading.value = true; try { const res: any = await getAccountList(queryParams); accountList.value = res.data.rows; total.value = res.data.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.id); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { id: null, tradeDay: '', weekDay: '', assets: '', totalAssets: '', profit: '', assetsDiff: '', totalDiff: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加交易账户' }
async function handleUpdate(row?: any) { reset(); const id = row?.id || ids.value[0]; const res = await getAccount(id); form.value = res.data; open.value = true; title.value = '修改交易账户' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.id != null) { await updateAccount(form.value); ElMessage.success('修改成功') } else { await addAccount(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const idList = row?.id || ids.value; ElMessageBox.confirm(`是否确认删除交易账户编号为"${idList}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delAccount(idList); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/booksystem/account/export`, '_blank') }
function handleChange(value: any) { if (!value) return; const currentDate = new Date(value as string); const getWeek = currentDate.getDay(); form.value.weekDay = weekOptions[getWeek] }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,121 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="交易日期" prop="tradeDay">
<el-date-picker v-model="queryParams.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" />
</el-form-item>
<el-form-item label="股票代码" prop="code">
<el-input v-model="queryParams.code" placeholder="请输入股票代码" clearable size="small" />
</el-form-item>
<el-form-item label="股票名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入股票名称" clearable size="small" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="primary" plain size="small" @click="handleAdd"></el-button></el-col>
<el-col :span="1.5"><el-button type="success" plain size="small" :disabled="single" @click="handleUpdate"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete"></el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="bookList" @selection-change="handleSelectionChange">
<el-table-column label="交易日期" align="center" prop="tradeDay" width="180" />
<el-table-column label="股票代码" align="center" prop="code" />
<el-table-column label="股票名称" align="center" prop="name" />
<el-table-column label="操作" align="center" prop="operate" />
<el-table-column label="成交价格" align="center" prop="dealPrice" />
<el-table-column label="成交量" align="center" prop="volumn" />
<el-table-column label="成交额" align="center" prop="amount" />
<el-table-column label="印花税" align="center" prop="tax" />
<el-table-column label="手续费" align="center" prop="fee" />
<el-table-column label="其他费用" align="center" prop="other" />
<el-table-column label="操作逻辑" align="center" prop="dealLogic" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['booksystem:book:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['booksystem:book:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="70%" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="auto" label-position="left">
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="交易日期" prop="tradeDay"><el-date-picker v-model="form.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="股票代码" prop="code"><el-input v-model="form.code" placeholder="请输入股票代码" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="操作" prop="operate"><el-input v-model="form.operate" placeholder="请输入操作" /></el-form-item></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="成交量" prop="volumn"><el-input v-model="form.volumn" placeholder="请输入成交量" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="成交价格" prop="dealPrice"><el-input v-model="form.dealPrice" placeholder="请输入成交价格" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="成交额" prop="amount"><el-input v-model="form.amount" placeholder="请输入成交额" /></el-form-item></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="印花税" prop="tax"><el-input v-model="form.tax" placeholder="请输入印花税" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="手续费" prop="fee"><el-input v-model="form.fee" placeholder="请输入手续费" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="其他费用" prop="other"><el-input v-model="form.other" placeholder="请输入其他费用" /></el-form-item></el-col>
</el-row>
<el-row>
<el-col :span="24"><el-form-item label="交易逻辑" prop="dealLogic"><el-input v-model="form.dealLogic" type="textarea" autosize placeholder="请输入交易逻辑" /></el-form-item></el-col>
</el-row>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getBookList, getBook, delBook, addBook, updateBook } from '@/api/modules/booksystem/book'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const bookList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const queryParams = reactive({ pageNum: 1, pageSize: 10, code: '', name: '', tradeDay: '' })
const form = ref<any>({ id: null, code: '', name: '', tradeDay: '', operate: '', dealPrice: '', volumn: '', amount: '', tax: '', fee: '', other: '', dealLogic: '' })
const rules = {
code: [{ required: true, message: '股票代码不能为空', trigger: 'blur' }],
operate: [{ required: true, message: '操作不能为空', trigger: 'blur' }]
}
async function getList() { loading.value = true; try { const res: any = await getBookList(queryParams); bookList.value = res.data.rows; total.value = res.data.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.id); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { id: null, code: '', name: '', tradeDay: '', operate: '', dealPrice: '', volumn: '', amount: '', tax: '', fee: '', other: '', dealLogic: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加交易记账' }
async function handleUpdate(row?: any) { reset(); const id = row?.id || ids.value[0]; const res = await getBook(id); form.value = res.data; open.value = true; title.value = '修改交易记账' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.id != null) { await updateBook(form.value); ElMessage.success('修改成功') } else { await addBook(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const idList = row?.id || ids.value; ElMessageBox.confirm(`是否确认删除交易记账编号为"${idList}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delBook(idList); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/booksystem/book/export`, '_blank') }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,124 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="股票代码" prop="code"><el-input v-model="queryParams.code" placeholder="请输入股票代码" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="股票名称" prop="name"><el-input v-model="queryParams.name" placeholder="请输入股票名称" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="交易日期" prop="tradeDay"><el-date-picker v-model="queryParams.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" /></el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['booksystem:operations:add']"></el-button></el-col>
<el-col :span="1.5"><el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['booksystem:operations:edit']"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['booksystem:operations:remove']"></el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['booksystem:operations:export']"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="operationsList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="股票代码" align="center" prop="code" />
<el-table-column label="股票名称" align="center" prop="name" />
<el-table-column label="交易日期" align="center" prop="tradeDay" width="180" />
<el-table-column label="星期" align="center" prop="weekDay" width="180" />
<el-table-column label="操作" align="center" prop="operate" />
<el-table-column label="交易价格" align="center" prop="dealPrice" />
<el-table-column label="成交量" align="center" prop="volumn" />
<el-table-column label="成交额" align="center" prop="amount" />
<el-table-column label="印花税" align="center" prop="tax" />
<el-table-column label="手续费" align="center" prop="fee" />
<el-table-column label="其他费用" align="center" prop="other" />
<el-table-column label="操作时涨跌" align="center" prop="operateDiff" />
<el-table-column label="关联操作id" align="center" prop="preId" />
<el-table-column label="操作逻辑" align="center" prop="dealLogic" />
<el-table-column label="备注" align="center" prop="bz" />
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="70%" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="股票代码" prop="code"><el-input v-model="form.code" placeholder="请输入股票代码" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="股票名称" prop="name"><el-input v-model="form.name" placeholder="请输入股票名称" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="交易日期" prop="tradeDay"><el-date-picker v-model="form.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" @change="handleChange" /></el-form-item></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="操作" prop="operate">
<el-select v-model="form.operate" placeholder="请选择操作">
<el-option label="买入" value="买入" /><el-option label="卖出" value="卖出" />
</el-select>
</el-form-item></el-col>
<el-col :span="8"><el-form-item label="交易价格" prop="dealPrice"><el-input v-model="form.dealPrice" placeholder="请输入交易价格" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="成交量" prop="volumn"><el-input v-model="form.volumn" placeholder="请输入成交量" /></el-form-item></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="成交额" prop="amount"><el-input v-model="form.amount" placeholder="请输入成交额" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="印花税" prop="tax"><el-input v-model="form.tax" placeholder="请输入印花税" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="手续费" prop="fee"><el-input v-model="form.fee" placeholder="请输入手续费" /></el-form-item></el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8"><el-form-item label="其他费用" prop="other"><el-input v-model="form.other" placeholder="请输入其他费用" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="操作时涨跌" prop="operateDiff"><el-input v-model="form.operateDiff" placeholder="请输入操作时涨跌" /></el-form-item></el-col>
<el-col :span="8"><el-form-item label="关联操作id" prop="preId"><el-input v-model="form.preId" placeholder="请输入关联操作id" /></el-form-item></el-col>
</el-row>
<el-row><el-col :span="24"><el-form-item label="操作逻辑" prop="dealLogic"><el-input v-model="form.dealLogic" type="textarea" placeholder="请输入内容" /></el-form-item></el-col></el-row>
<el-row><el-col :span="24"><el-form-item label="备注" prop="bz"><el-input v-model="form.bz" type="textarea" placeholder="请输入内容" /></el-form-item></el-col></el-row>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getOperationsList, getOperations, delOperations, addOperations, updateOperations } from '@/api/modules/booksystem/operations'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const operationsList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const weekOptions = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
const queryParams = reactive({ pageNum: 1, pageSize: 10, code: '', name: '', tradeDay: '' })
const form = ref<any>({ id: null, code: '', name: '', tradeDay: '', weekDay: '', operate: '', dealPrice: '', volumn: '', amount: '', tax: '', fee: '', other: '', operateDiff: '', preId: '', dealLogic: '', bz: '' })
const rules = {
code: [{ required: true, message: '股票代码不能为空', trigger: 'blur' }],
operate: [{ required: true, message: '操作不能为空', trigger: 'blur' }]
}
async function getList() { loading.value = true; try { const res: any = await getOperationsList(queryParams); operationsList.value = res.data.rows; total.value = res.data.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.id); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { id: null, code: '', name: '', tradeDay: '', weekDay: '', operate: '', dealPrice: '', volumn: '', amount: '', tax: '', fee: '', other: '', operateDiff: '', preId: '', dealLogic: '', bz: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加当日操作' }
async function handleUpdate(row?: any) { reset(); const id = row?.id || ids.value[0]; const res = await getOperations(id); form.value = res.data; open.value = true; title.value = '修改当日操作' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.id != null) { await updateOperations(form.value); ElMessage.success('修改成功') } else { await addOperations(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const idList = row?.id || ids.value; ElMessageBox.confirm(`是否确认删除操作编号为"${idList}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delOperations(idList); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/booksystem/operations/export`, '_blank') }
function handleChange(value: any) { if (!value) return; const currentDate = new Date(value as string); form.value.weekDay = weekOptions[currentDate.getDay()] }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,96 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="股票代码" prop="code"><el-input v-model="queryParams.code" placeholder="请输入股票代码" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="股票名称" prop="name"><el-input v-model="queryParams.name" placeholder="请输入股票名称" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="交易日期" prop="tradeDay"><el-date-picker v-model="queryParams.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" /></el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['booksystem:statistics:add']"></el-button></el-col>
<el-col :span="1.5"><el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['booksystem:statistics:edit']"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['booksystem:statistics:remove']"></el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['booksystem:statistics:export']"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="statisticsList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="股票代码" align="center" prop="code" />
<el-table-column label="股票名称" align="center" prop="name" />
<el-table-column label="交易日期" align="center" prop="tradeDay" width="180" />
<el-table-column label="星期" align="center" prop="weekDay" />
<el-table-column label="操作id" align="center" prop="operationsId" />
<el-table-column label="当笔当日盈亏" align="center" prop="profit" />
<el-table-column label="盈亏比例" align="center" prop="diff" />
<el-table-column label="备注" align="center" prop="bz" />
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="股票代码" prop="code"><el-input v-model="form.code" placeholder="请输入股票代码" /></el-form-item>
<el-form-item label="股票名称" prop="name"><el-input v-model="form.name" placeholder="请输入股票名称" /></el-form-item>
<el-form-item label="交易日期" prop="tradeDay"><el-date-picker v-model="form.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" /></el-form-item>
<el-form-item label="操作id" prop="operationsId"><el-input v-model="form.operationsId" placeholder="请输入操作id" /></el-form-item>
<el-form-item label="当笔盈亏" prop="profit"><el-input v-model="form.profit" placeholder="请输入当笔当日盈亏" /></el-form-item>
<el-form-item label="盈亏比例" prop="diff"><el-input v-model="form.diff" placeholder="请输入盈亏比例" /></el-form-item>
<el-form-item label="备注" prop="bz"><el-input v-model="form.bz" type="textarea" placeholder="请输入内容" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getStatisticsList, getStatistics, delStatistics, addStatistics, updateStatistics } from '@/api/modules/booksystem/statistics'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const statisticsList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const queryParams = reactive({ pageNum: 1, pageSize: 10, code: '', name: '', tradeDay: '' })
const form = ref<any>({ id: null, code: '', name: '', tradeDay: '', weekDay: '', operationsId: '', profit: '', diff: '', bz: '' })
const rules = {
code: [{ required: true, message: '股票代码不能为空', trigger: 'blur' }],
operationsId: [{ required: true, message: '操作id不能为空', trigger: 'blur' }]
}
async function getList() { loading.value = true; try { const res: any = await getStatisticsList(queryParams); statisticsList.value = res.data.rows; total.value = res.data.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.id); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { id: null, code: '', name: '', tradeDay: '', weekDay: '', operationsId: '', profit: '', diff: '', bz: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加单笔操作统计' }
async function handleUpdate(row?: any) { reset(); const id = row?.id || ids.value[0]; const res = await getStatistics(id); form.value = res.data; open.value = true; title.value = '修改单笔操作统计' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.id != null) { await updateStatistics(form.value); ElMessage.success('修改成功') } else { await addStatistics(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const idList = row?.id || ids.value; ElMessageBox.confirm(`是否确认删除统计编号为"${idList}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delStatistics(idList); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/booksystem/statistics/export`, '_blank') }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -624,83 +624,83 @@ function initDetailChart() {
&:hover { background: var(--bg3); }
}
}
</style>
<style lang="scss">
// Modal styles (Teleport - must be global)
.modal-ov {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 200;
display: flex;
align-items: center;
justify-content: center;
}
// Modal styles (unscoped since Teleport)
:global {
.modal-ov {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 200;
.modal {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 8px;
width: 92%;
max-width: 980px;
max-height: 85vh;
display: flex;
flex-direction: column;
overflow: hidden;
.mhd {
padding: 10px 14px;
border-bottom: 1px solid var(--bd);
display: flex;
align-items: center;
justify-content: center;
justify-content: space-between;
h3 { font-size: 13px; color: var(--t1); }
}
.modal {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 8px;
width: 92%;
max-width: 980px;
max-height: 85vh;
.mcl {
width: 22px;
height: 22px;
border-radius: 4px;
border: none;
background: var(--bg2);
color: var(--t2);
cursor: pointer;
font-size: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
.mhd {
padding: 10px 14px;
border-bottom: 1px solid var(--bd);
display: flex;
align-items: center;
justify-content: space-between;
h3 { font-size: 13px; color: var(--t1); }
}
.mcl {
width: 22px;
height: 22px;
border-radius: 4px;
border: none;
background: var(--bg2);
color: var(--t2);
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
&:hover { background: var(--red); color: #fff; }
}
align-items: center;
justify-content: center;
.mbd {
flex: 1;
overflow-y: auto;
padding: 12px;
}
&:hover { background: var(--red); color: #fff; }
}
.detailrow {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 10px;
.mbd {
flex: 1;
overflow-y: auto;
padding: 12px;
}
}
.stat-card {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 5px;
padding: 8px 12px;
.detailrow {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 10px;
}
.sl { font-size: 9px; color: var(--tm); margin-bottom: 2px; }
.sv { font-size: 22px; font-weight: 700; color: var(--t1); }
}
.stat-card {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 5px;
padding: 8px 12px;
.kline-container {
width: 100%;
height: 180px;
}
.sl { font-size: 9px; color: var(--tm); margin-bottom: 2px; }
.sv { font-size: 22px; font-weight: 700; color: var(--t1); }
}
.kline-container {
width: 100%;
height: 180px;
}
</style>

@ -425,83 +425,83 @@ function initDetailChart() {
overflow-x: auto;
}
}
</style>
<style lang="scss">
// Modal styles (Teleport - must be global)
.modal-ov {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 200;
display: flex;
align-items: center;
justify-content: center;
}
// Modal styles (unscoped since Teleport)
:global {
.modal-ov {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 200;
.modal {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 8px;
width: 92%;
max-width: 980px;
max-height: 85vh;
display: flex;
flex-direction: column;
overflow: hidden;
.mhd {
padding: 10px 14px;
border-bottom: 1px solid var(--bd);
display: flex;
align-items: center;
justify-content: center;
justify-content: space-between;
h3 { font-size: 13px; color: var(--t1); }
}
.modal {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 8px;
width: 92%;
max-width: 980px;
max-height: 85vh;
.mcl {
width: 22px;
height: 22px;
border-radius: 4px;
border: none;
background: var(--bg2);
color: var(--t2);
cursor: pointer;
font-size: 12px;
display: flex;
flex-direction: column;
overflow: hidden;
.mhd {
padding: 10px 14px;
border-bottom: 1px solid var(--bd);
display: flex;
align-items: center;
justify-content: space-between;
h3 { font-size: 13px; color: var(--t1); }
}
.mcl {
width: 22px;
height: 22px;
border-radius: 4px;
border: none;
background: var(--bg2);
color: var(--t2);
cursor: pointer;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
&:hover { background: var(--red); color: #fff; }
}
align-items: center;
justify-content: center;
.mbd {
flex: 1;
overflow-y: auto;
padding: 12px;
}
&:hover { background: var(--red); color: #fff; }
}
.detailrow {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 10px;
.mbd {
flex: 1;
overflow-y: auto;
padding: 12px;
}
}
.stat-card {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 5px;
padding: 8px 12px;
.detailrow {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
margin-bottom: 10px;
}
.sl { font-size: 9px; color: var(--tm); margin-bottom: 2px; }
.sv { font-size: 22px; font-weight: 700; color: var(--t1); }
}
.stat-card {
background: var(--bg1);
border: 1px solid var(--bd);
border-radius: 5px;
padding: 8px 12px;
.kline-container {
width: 100%;
height: 180px;
}
.sl { font-size: 9px; color: var(--tm); margin-bottom: 2px; }
.sv { font-size: 22px; font-weight: 700; color: var(--t1); }
}
.kline-container {
width: 100%;
height: 180px;
}
</style>

@ -0,0 +1,87 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="股票名称" prop="name"><el-input v-model="queryParams.name" placeholder="请输入股票名称" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="东财行业2级" prop="blemind2"><el-input v-model="queryParams.blemind2" placeholder="请输入所属东财行业指数2级" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="东财行业3级" prop="blemind3"><el-input v-model="queryParams.blemind3" placeholder="请输入所属东财行业指数3级" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="首发上市日期" prop="listdate"><el-date-picker v-model="queryParams.listdate" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择首发上市日期" /></el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['stocksystem:stockbasic:add']"></el-button></el-col>
<el-col :span="1.5"><el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['stocksystem:stockbasic:edit']"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['stocksystem:stockbasic:remove']"></el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['stocksystem:stockbasic:export']"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="stockbasicList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="股票代码" align="center" prop="code" />
<el-table-column label="股票名称" align="center" prop="name" />
<el-table-column label="东财行业2级" align="center" prop="blemind2" />
<el-table-column label="东财行业3级" align="center" prop="blemind3" />
<el-table-column label="首发上市日期" align="center" prop="listdate" width="180" />
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="股票名称" prop="name"><el-input v-model="form.name" placeholder="请输入股票名称" /></el-form-item>
<el-form-item label="东财行业2级" prop="blemind2"><el-input v-model="form.blemind2" placeholder="请输入所属东财行业指数2级" /></el-form-item>
<el-form-item label="东财行业3级" prop="blemind3"><el-input v-model="form.blemind3" placeholder="请输入所属东财行业指数3级" /></el-form-item>
<el-form-item label="首发上市日期" prop="listdate"><el-date-picker v-model="form.listdate" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择首发上市日期" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { getStockbasicList, getStockbasic, delStockbasic, addStockbasic, updateStockbasic } from '@/api/modules/stocksystem/stockbasic'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const stockbasicList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const queryParams = reactive({ pageNum: 1, pageSize: 10, name: '', blemind2: '', blemind3: '', listdate: '' })
const form = ref<any>({ id: null, code: '', name: '', blemind2: '', blemind3: '', listdate: '' })
const rules = {}
async function getList() { loading.value = true; try { const res = await getStockbasicList(queryParams); stockbasicList.value = res.rows; total.value = res.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.id); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { id: null, code: '', name: '', blemind2: '', blemind3: '', listdate: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加基础数据' }
async function handleUpdate(row?: any) { reset(); const id = row?.id || ids.value[0]; const res = await getStockbasic(id); form.value = res.data; open.value = true; title.value = '修改基础数据' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.id != null) { await updateStockbasic(form.value); ElMessage.success('修改成功') } else { await addStockbasic(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const idList = row?.id || ids.value; ElMessageBox.confirm(`是否确认删除基础数据编号为"${idList}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delStockbasic(idList); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/stocksystem/stockbasic/export`, '_blank') }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,75 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="指数代码" prop="code"><el-input v-model="queryParams.code" placeholder="请输入指数代码" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="指数名称" prop="name"><el-input v-model="queryParams.name" placeholder="请输入指数名称" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="交易日期" prop="tradeDay"><el-date-picker v-model="queryParams.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" /></el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['stocksystem:stockindex:export']"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="stockindexList" @selection-change="handleSelectionChange">
<el-table-column label="指数代码" align="center" prop="code" />
<el-table-column label="指数名称" align="center" prop="name" />
<el-table-column label="交易日期" align="center" prop="tradeDay" width="180" />
<el-table-column label="开盘价" align="center" prop="open" />
<el-table-column label="收盘价" align="center" prop="close" />
<el-table-column label="最高价" align="center" prop="high" />
<el-table-column label="最低价" align="center" prop="low" />
<el-table-column label="涨跌幅" align="center" prop="differrange" />
<el-table-column label="成交量" align="center" prop="volume" />
<el-table-column label="成交额" align="center" prop="amount" />
<el-table-column label="涨停家数" align="center" prop="limitupnum" />
<el-table-column label="跌停家数" align="center" prop="limitdownnum" />
<el-table-column label="停牌家数" align="center" prop="suspendnum" />
<el-table-column label="上涨家数" align="center" prop="risenum" />
<el-table-column label="下跌家数" align="center" prop="fallnum" />
<el-table-column label="平盘家数" align="center" prop="flatnum" />
<el-table-column label="成分个数" align="center" prop="componentnum" />
<el-table-column label="总市值" align="center" prop="mv" />
<el-table-column label="市盈率PE" align="center" prop="pettm" />
<el-table-column label="PE中位值" align="center" prop="pettmMid" />
<el-table-column label="流通市值" align="center" prop="liqmv" />
<el-table-column label="近期新高" align="center" prop="rcnthigh" />
<el-table-column label="近期新低" align="center" prop="rcntlow" />
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { getStockindexList } from '@/api/modules/stocksystem/stockindex'
const queryFormRef = ref()
const stockindexList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const queryParams = reactive({ pageNum: 1, pageSize: 10, code: '', name: '', tradeDay: '' })
async function getList() { loading.value = true; try { const res = await getStockindexList(queryParams); stockindexList.value = res.rows; total.value = res.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.id); single.value = selection.length !== 1; multiple.value = !selection.length }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/stocksystem/stockindex/export`, '_blank') }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,72 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="股票代码" prop="code"><el-input v-model="queryParams.code" placeholder="请输入股票代码" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="交易日期" prop="tradeDay"><el-date-picker v-model="queryParams.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" /></el-form-item>
<el-form-item label="可交易日数" prop="tradeDays"><el-input v-model="queryParams.tradeDays" placeholder="请输入可交易日数" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="10日涨跌幅" prop="differrange10"><el-input v-model="queryParams.differrange10" placeholder="请输入10日区间涨跌幅" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="20日涨跌幅" prop="differrange20"><el-input v-model="queryParams.differrange20" placeholder="请输入20日区间涨跌幅" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="60日涨跌幅" prop="differrange60"><el-input v-model="queryParams.differrange60" placeholder="请输入60日区间涨跌幅" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="机构持仓" prop="agenciesHold"><el-input v-model="queryParams.agenciesHold" placeholder="请输入机构持仓合计" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['stocksystem:stocks:export']"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="stocksList" @selection-change="handleSelectionChange">
<el-table-column label="股票代码" align="center" prop="code" />
<el-table-column label="股票名称" align="center" prop="name" />
<el-table-column label="交易日期" align="center" prop="tradeDay" width="180" />
<el-table-column label="开盘价" align="center" prop="open" />
<el-table-column label="收盘价" align="center" prop="close" />
<el-table-column label="涨跌幅" align="center" prop="differrange" />
<el-table-column label="可交易日数" align="center" prop="tradeDays" />
<el-table-column label="成交量" align="center" prop="volumn" />
<el-table-column label="成交额" align="center" prop="amount" />
<el-table-column label="10日涨跌幅" align="center" prop="differrange10" />
<el-table-column label="20日涨跌幅" align="center" prop="differrange20" />
<el-table-column label="60日涨跌幅" align="center" prop="differrange60" />
<el-table-column label="20日均量" align="center" prop="avgVolume20" />
<el-table-column label="流通市值" align="center" prop="freefloatMarketValue" />
<el-table-column label="总市值" align="center" prop="totalMarketValue" />
<el-table-column label="机构持仓" align="center" prop="agenciesHold" />
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { getStocksList } from '@/api/modules/stocksystem/stocks'
const queryFormRef = ref()
const stocksList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const queryParams = reactive({ pageNum: 1, pageSize: 10, code: '', tradeDay: '', tradeDays: '', differrange10: '', differrange20: '', differrange60: '', agenciesHold: '' })
async function getList() { loading.value = true; try { const res: any = await getStocksList(queryParams); stocksList.value = res.data.rows; total.value = res.data.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.id); single.value = selection.length !== 1; multiple.value = !selection.length }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/stocksystem/stocks/export`, '_blank') }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,133 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="交易日期" prop="tradeDay">
<el-date-picker v-model="queryParams.tradeDay" clearable size="small" type="date" value-format="YYYY-MM-DD" placeholder="选择交易日期" @change="handleQuery" />
</el-form-item>
<el-form-item label="动量周期" prop="type">
<el-select v-model="queryParams.type" placeholder="动量周期" size="small" @change="handleQuery">
<el-option label="1日" :value="1" />
<el-option label="3日" :value="3" />
<el-option label="5日" :value="5" />
<el-option label="10日" :value="10" />
<el-option label="15日" :value="15" />
<el-option label="20日" :value="20" />
<el-option label="30日" :value="30" />
</el-select>
</el-form-item>
<el-form-item label="板块名称" prop="blemind2">
<el-input v-model="queryParams.blemind2" placeholder="请输入板块名称" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-tabs v-model="activeTab" type="border-card">
<el-tab-pane label="动量趋势" name="trend">
<div class="tab-header">
<span class="tab-title">{{ queryParams.type }}日趋势板块</span>
</div>
<el-table v-loading="loading" :data="trendData" style="width: 100%" height="560" @cell-click="handleCellClick">
<el-table-column v-for="col in trendColumns" :key="col.prop" :prop="col.prop" :label="col.label" />
</el-table>
</el-tab-pane>
<el-tab-pane label="成交量趋势" name="volume">
<el-tabs v-model="volumeSubTab">
<el-tab-pane label="排名" name="rank">
<el-table v-loading="loading" :data="volumeData" style="width: 100%" height="560">
<el-table-column v-for="col in volumeColumns" :key="col.prop" :prop="col.prop" :label="col.label" />
</el-table>
</el-tab-pane>
<el-tab-pane label="成交量乘数" name="raw">
<el-table v-loading="loading" :data="volumeRawData" style="width: 100%" height="560">
<el-table-column v-for="col in volumeColumns" :key="col.prop" :prop="col.prop" :label="col.label" />
</el-table>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
<el-tab-pane label="涨停板趋势" name="limitUp">
<el-table v-loading="loading" :data="limitUpData" style="width: 100%" height="560">
<el-table-column v-for="col in limitColumns" :key="col.prop" :prop="col.prop" :label="col.label" />
</el-table>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { getTrendsSection, getTradeVolume, getStockIndexLimitUp } from '@/api/modules/stocksystem/trends'
const queryFormRef = ref()
const loading = ref(false)
const showSearch = ref(true)
const activeTab = ref('trend')
const volumeSubTab = ref('rank')
const queryParams = reactive({ pageNum: 1, pageSize: 10, tradeDay: '', blemind2: '', type: '20' })
const trendData = ref<any[]>([])
const trendColumns = ref<{prop: string; label: string}[]>([])
const volumeData = ref<any[]>([])
const volumeColumns = ref<{prop: string; label: string}[]>([])
const volumeRawData = ref<any[]>([])
const limitUpData = ref<any[]>([])
const limitColumns = ref<{prop: string; label: string}[]>([])
function transformToColumnsAndRows(data: any[]) {
if (!data || data.length === 0) return { columns: [], rows: [] }
const columns = data.map((item: any) => ({ prop: item.title || item.key, label: item.title || item.key }))
const maxLen = Math.max(...data.map((item: any) => (item.sortDataList || []).length), 0)
const rows: any[] = []
for (let i = 0; i < maxLen; i++) {
const row: any = {}
data.forEach((item: any) => { row[item.title || item.key] = item.sortDataList?.[i] ?? '' })
rows.push(row)
}
return { columns, rows }
}
async function getList() {
loading.value = true
try {
const [trendRes, volumeRes, limitRes] = await Promise.all([
getTrendsSection(queryParams),
getTradeVolume(queryParams),
getStockIndexLimitUp(queryParams)
]) as any[]
const trendTransformed = transformToColumnsAndRows(trendRes.data.rows)
trendColumns.value = trendTransformed.columns
trendData.value = trendTransformed.rows
const volumeTransformed = transformToColumnsAndRows(volumeRes.data.rows)
volumeColumns.value = volumeTransformed.columns
volumeData.value = volumeTransformed.rows
const rawTransformed = transformToColumnsAndRows(volumeRes.data.rows?.map((item: any) => ({ ...item, sortDataList: item.rawDataList })) || [])
volumeRawData.value = rawTransformed.rows
const limitTransformed = transformToColumnsAndRows(limitRes.data.rows)
limitColumns.value = limitTransformed.columns
limitUpData.value = limitTransformed.rows
} finally {
loading.value = false
}
}
function handleQuery() { getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleCellClick(row: any, column: any) {
console.log('Cell clicked:', row, column)
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.app-container { padding: 20px; }
.tab-header { margin-bottom: 10px; border-bottom: 2px solid #A9A4A4; }
.tab-title { font-weight: bold; }
</style>

@ -0,0 +1,122 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="参数名称" prop="configName"><el-input v-model="queryParams.configName" placeholder="请输入参数名称" clearable size="small" style="width: 240px" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="参数键名" prop="configKey"><el-input v-model="queryParams.configKey" placeholder="请输入参数键名" clearable size="small" style="width: 240px" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-select v-model="queryParams.configType" placeholder="系统内置" clearable size="small">
<el-option label="是" value="Y" /><el-option label="否" value="N" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRange" size="small" style="width: 240px" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="" end-placeholder="" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:config:add']"></el-button></el-col>
<el-col :span="1.5"><el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['system:config:edit']"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:config:remove']"></el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['system:config:export']"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" @click="handleRefreshCache"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="configList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="参数主键" align="center" prop="configId" />
<el-table-column label="参数名称" align="center" prop="configName" :show-overflow-tooltip="true" />
<el-table-column label="参数键名" align="center" prop="configKey" :show-overflow-tooltip="true" />
<el-table-column label="参数键值" align="center" prop="configValue" />
<el-table-column label="系统内置" align="center" prop="configType">
<template #default="scope"><el-tag :type="scope.row.configType === 'Y' ? 'success' : 'warning'">{{ scope.row.configType === 'Y' ? '是' : '否' }}</el-tag></template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:config:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:config:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="参数名称" prop="configName"><el-input v-model="form.configName" placeholder="请输入参数名称" /></el-form-item>
<el-form-item label="参数键名" prop="configKey"><el-input v-model="form.configKey" placeholder="请输入参数键名" /></el-form-item>
<el-form-item label="参数键值" prop="configValue"><el-input v-model="form.configValue" placeholder="请输入参数键值" /></el-form-item>
<el-form-item label="系统内置" prop="configType">
<el-radio-group v-model="form.configType"><el-radio value="Y"></el-radio><el-radio value="N"></el-radio></el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getConfigList, getConfig, delConfig, addConfig, updateConfig, refreshConfigCache } from '@/api/modules/system/config'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const configList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const dateRange = ref<string[]>([])
const queryParams = reactive({ pageNum: 1, pageSize: 10, configName: '', configKey: '', configType: '' })
const form = ref<any>({ configId: undefined, configName: '', configKey: '', configValue: '', configType: 'Y', remark: '' })
const rules = {
configName: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
configKey: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
configValue: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }]
}
async function getList() {
loading.value = true
try {
const params = { ...queryParams }
if (dateRange.value && dateRange.value.length === 2) { Object.assign(params, { beginTime: dateRange.value[0], endTime: dateRange.value[1] }) }
const res: any = await getConfigList(queryParams); configList.value = res.data.rows; total.value = res.data.total
} finally { loading.value = false }
}
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { dateRange.value = []; queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.configId); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { configId: undefined, configName: '', configKey: '', configValue: '', configType: 'Y', remark: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加参数' }
async function handleUpdate(row?: any) { reset(); const configId = row?.configId || ids.value[0]; const res = await getConfig(configId); form.value = res.data; open.value = true; title.value = '修改参数' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.configId != undefined) { await updateConfig(form.value); ElMessage.success('修改成功') } else { await addConfig(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const configIds = row?.configId || ids.value; ElMessageBox.confirm(`是否确认删除参数编号为"${configIds}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delConfig(configIds); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/system/config/export`, '_blank') }
async function handleRefreshCache() { await refreshConfigCache(); ElMessage.success('刷新成功') }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,257 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="queryParams.deptName" placeholder="请输入部门名称" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable size="small">
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:dept:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain size="small" @click="toggleExpandAll">/</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table
v-if="refreshTable"
v-loading="loading"
:data="deptList"
row-key="deptId"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="deptName" label="部门名称" width="260" />
<el-table-column prop="orderNum" label="排序" width="200" />
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
{{ scope.row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="200" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']"></el-button>
<el-button v-if="scope.row.parentId != 0" size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改部门对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="20">
<el-col :span="24" v-if="form.parentId !== 0">
<el-form-item label="上级部门" prop="parentId">
<el-tree-select
v-model="form.parentId"
:data="deptOptions"
:props="{ label: 'deptName', children: 'children' } as any"
check-strictly
placeholder="选择上级部门"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入部门名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="leader">
<el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门状态">
<el-radio-group v-model="form.status">
<el-radio value="0">正常</el-radio>
<el-radio value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getDeptList, getDept, delDept, addDept, updateDept, getDeptListExcludeChild } from '@/api/modules/system/dept'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const deptList = ref<any[]>([])
const loading = ref(false)
const showSearch = ref(true)
const deptOptions = ref<any[]>([])
const title = ref('')
const open = ref(false)
const isExpandAll = ref(true)
const refreshTable = ref(true)
const queryParams = reactive({ deptName: '', status: '' })
const form = ref<any>({
deptId: undefined,
parentId: undefined,
deptName: '',
orderNum: undefined,
leader: '',
phone: '',
email: '',
status: '0'
})
const rules = {
parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],
deptName: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],
email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }]
}
function handleTree(data: any[], id: string, parentId?: string, children?: string) {
const config = { id: id || 'id', parentId: parentId || 'parentId', childrenList: children || 'children' }
const childrenListMap: any = {}
const nodeIds: any = {}
const tree: any[] = []
for (const d of data) {
const pId = d[config.parentId]
if (!childrenListMap[pId]) childrenListMap[pId] = []
nodeIds[d[config.id]] = d
childrenListMap[pId].push(d)
}
for (const d of data) {
const pId = d[config.parentId]
if (!nodeIds[pId]) tree.push(d)
}
for (const t of tree) {
adaptToChildrenList(t)
}
function adaptToChildrenList(o: any) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]]
}
if (o[config.childrenList]) {
for (const c of o[config.childrenList]) adaptToChildrenList(c)
}
}
return tree
}
async function getList() {
loading.value = true
try {
const res = await getDeptList(queryParams)
deptList.value = handleTree(res.data, 'deptId')
} finally {
loading.value = false
}
}
function handleQuery() { getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function toggleExpandAll() {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
nextTick(() => { refreshTable.value = true })
}
async function handleAdd(row?: any) {
reset()
if (row?.deptId) form.value.parentId = row.deptId
const res = await getDeptList()
deptOptions.value = handleTree(res.data, 'deptId')
open.value = true
title.value = '添加部门'
}
async function handleUpdate(row: any) {
reset()
const res = await getDept(row.deptId)
form.value = res.data
const deptRes = await getDeptListExcludeChild(row.deptId)
deptOptions.value = handleTree(deptRes.data, 'deptId')
open.value = true
title.value = '修改部门'
}
async function submitForm() {
const valid = await formRef.value?.validate().catch(() => false)
if (valid) {
if (form.value.deptId != undefined) {
await updateDept(form.value)
ElMessage.success('修改成功')
} else {
await addDept(form.value)
ElMessage.success('新增成功')
}
open.value = false
getList()
}
}
function handleDelete(row: any) {
ElMessageBox.confirm(`是否确认删除名称为"${row.deptName}"的数据项?`, '系统提示', {
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
}).then(async () => {
await delDept(row.deptId)
ElMessage.success('删除成功')
getList()
}).catch(() => {})
}
function reset() {
form.value = { deptId: undefined, parentId: undefined, deptName: '', orderNum: undefined, leader: '', phone: '', email: '', status: '0' }
formRef.value?.resetFields()
}
function cancel() { open.value = false; reset() }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
</style>

@ -0,0 +1,202 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="字典名称" prop="dictType">
<el-select v-model="queryParams.dictType" size="small">
<el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
</el-select>
</el-form-item>
<el-form-item label="字典标签" prop="dictLabel">
<el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="数据状态" clearable size="small">
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:dict:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['system:dict:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:dict:remove']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['system:dict:export']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain size="small" @click="handleClose"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="dataList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="字典编码" align="center" prop="dictCode" />
<el-table-column label="字典标签" align="center" prop="dictLabel" />
<el-table-column label="字典键值" align="center" prop="dictValue" />
<el-table-column label="字典排序" align="center" prop="dictSort" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '正常' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :page-sizes="[10, 20, 30, 50]" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典类型"><el-input v-model="form.dictType" :disabled="true" /></el-form-item>
<el-form-item label="数据标签" prop="dictLabel"><el-input v-model="form.dictLabel" placeholder="请输入数据标签" /></el-form-item>
<el-form-item label="数据键值" prop="dictValue"><el-input v-model="form.dictValue" placeholder="请输入数据键值" /></el-form-item>
<el-form-item label="显示排序" prop="dictSort"><el-input-number v-model="form.dictSort" controls-position="right" :min="0" /></el-form-item>
<el-form-item label="回显样式" prop="listClass">
<el-select v-model="form.listClass">
<el-option label="默认" value="default" /><el-option label="主要" value="primary" />
<el-option label="成功" value="success" /><el-option label="信息" value="info" />
<el-option label="警告" value="warning" /><el-option label="危险" value="danger" />
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status"><el-radio value="0">正常</el-radio><el-radio value="1"></el-radio></el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getDictDataList, getDictData, delDictData, addDictData, updateDictData } from '@/api/modules/system/dict'
import { getDictType, getDictTypeList } from '@/api/modules/system/dict'
const { proxy } = getCurrentInstance() as any
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const dataList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const defaultDictType = ref('')
const typeOptions = ref<any[]>([])
const queryParams = reactive({ pageNum: 1, pageSize: 10, dictType: '', dictLabel: '', status: '' })
const form = ref<any>({ dictCode: undefined, dictLabel: '', dictValue: '', dictSort: 0, listClass: 'default', status: '0', remark: '' })
const rules = {
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
}
async function getType(dictId: string) {
const res = await getDictType(dictId)
queryParams.dictType = res.data.dictType
defaultDictType.value = res.data.dictType
getList()
}
async function getTypeList() {
const res: any = await getDictTypeList({})
typeOptions.value = res.data.rows
}
async function getList() {
loading.value = true
try {
const res: any = await getDictDataList(queryParams)
dataList.value = res.data.rows
total.value = res.data.total
} finally { loading.value = false }
}
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); queryParams.dictType = defaultDictType.value; handleQuery() }
function handleSelectionChange(selection: any[]) {
ids.value = selection.map(item => item.dictCode)
single.value = selection.length !== 1
multiple.value = !selection.length
}
function reset() { form.value = { dictCode: undefined, dictLabel: '', dictValue: '', dictSort: 0, listClass: 'default', status: '0', remark: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加字典数据'; form.value.dictType = queryParams.dictType }
async function handleUpdate(row?: any) {
reset()
const dictCode = row?.dictCode || ids.value[0]
const res = await getDictData(dictCode)
form.value = res.data
open.value = true
title.value = '修改字典数据'
}
async function submitForm() {
const valid = await formRef.value?.validate().catch(() => false)
if (valid) {
if (form.value.dictCode != undefined) { await updateDictData(form.value); ElMessage.success('修改成功') }
else { await addDictData(form.value); ElMessage.success('新增成功') }
open.value = false; getList()
}
}
function handleDelete(row?: any) {
const dictCodes = row?.dictCode || ids.value
ElMessageBox.confirm(`是否确认删除字典编码为"${dictCodes}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => { await delDictData(dictCodes); ElMessage.success('删除成功'); getList() }).catch(() => {})
}
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/system/dict/data/export`, '_blank') }
function handleClose() {
const obj = { path: '/system/dict' }
proxy?.$router.push(obj)
}
onMounted(() => {
const dictId = proxy?.$route.params?.dictId as string
getType(dictId)
getTypeList()
})
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,184 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="字典名称" prop="dictName">
<el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable size="small" style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="字典类型" prop="dictType">
<el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable size="small" style="width: 240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="字典状态" clearable size="small" style="width: 240px">
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRange" size="small" style="width: 240px" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="" end-placeholder="" />
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:dict:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['system:dict:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:dict:remove']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['system:dict:export']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain size="small" @click="handleRefreshCache"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="typeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="字典编号" align="center" prop="dictId" />
<el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
<template #default="scope">
<router-link :to="`/system/dict-data/index/${scope.row.dictId}`" class="link-type">
<span>{{ scope.row.dictType }}</span>
</router-link>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '正常' : '停用' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:dict:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:dict:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :page-sizes="[10, 20, 30, 50]" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<!-- 添加或修改参数配置对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="字典名称" prop="dictName"><el-input v-model="form.dictName" placeholder="请输入字典名称" /></el-form-item>
<el-form-item label="字典类型" prop="dictType"><el-input v-model="form.dictType" placeholder="请输入字典类型" /></el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status"><el-radio value="0">正常</el-radio><el-radio value="1"></el-radio></el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getDictTypeList, getDictType, delDictType, addDictType, updateDictType, refreshDictCache } from '@/api/modules/system/dict'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const typeList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const dateRange = ref<string[]>([])
const queryParams = reactive({ pageNum: 1, pageSize: 10, dictName: '', dictType: '', status: '' })
const form = ref<any>({ dictId: undefined, dictName: '', dictType: '', status: '0', remark: '' })
const rules = {
dictName: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],
dictType: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }]
}
async function getList() {
loading.value = true
try {
const params = { ...queryParams }
if (dateRange.value && dateRange.value.length === 2) {
Object.assign(params, { beginTime: dateRange.value[0], endTime: dateRange.value[1] })
}
const res: any = await getDictTypeList(params)
typeList.value = res.data.rows
total.value = res.data.total
} finally { loading.value = false }
}
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { dateRange.value = []; queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) {
ids.value = selection.map(item => item.dictId)
single.value = selection.length !== 1
multiple.value = !selection.length
}
function reset() { form.value = { dictId: undefined, dictName: '', dictType: '', status: '0', remark: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加字典类型' }
async function handleUpdate(row?: any) {
reset()
const dictId = row?.dictId || ids.value[0]
const res = await getDictType(dictId)
form.value = res.data
open.value = true
title.value = '修改字典类型'
}
async function submitForm() {
const valid = await formRef.value?.validate().catch(() => false)
if (valid) {
if (form.value.dictId != undefined) { await updateDictType(form.value); ElMessage.success('修改成功') }
else { await addDictType(form.value); ElMessage.success('新增成功') }
open.value = false; getList()
}
}
function handleDelete(row?: any) {
const dictIds = row?.dictId || ids.value
ElMessageBox.confirm(`是否确认删除字典编号为"${dictIds}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' })
.then(async () => { await delDictType(dictIds); ElMessage.success('删除成功'); getList() }).catch(() => {})
}
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/system/dict/type/export`, '_blank') }
async function handleRefreshCache() {
await refreshDictCache()
ElMessage.success('刷新成功')
}
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
.link-type { color: #409eff; cursor: pointer; }
</style>

@ -0,0 +1,365 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch">
<el-form-item label="菜单名称" prop="menuName">
<el-input
v-model="queryParams.menuName"
placeholder="请输入菜单名称"
clearable
size="small"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable size="small">
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:menu:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain size="small" @click="toggleExpandAll">/</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table
v-if="refreshTable"
v-loading="loading"
:data="menuList"
row-key="menuId"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160" />
<el-table-column prop="icon" label="图标" align="center" width="100">
<template #default="scope">
<el-icon v-if="scope.row.icon"><component :is="scope.row.icon" /></el-icon>
</template>
</el-table-column>
<el-table-column prop="orderNum" label="排序" width="60" />
<el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true" />
<el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true" />
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
{{ scope.row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:menu:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleAdd(scope.row)" v-hasPermi="['system:menu:add']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:menu:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改菜单对话框 -->
<el-dialog :title="title" v-model="open" width="680px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="上级菜单">
<el-tree-select
v-model="form.parentId"
:data="menuOptions"
:props="{ label: 'menuName', children: 'children' } as any"
check-strictly
placeholder="选择上级菜单"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="菜单类型" prop="menuType">
<el-radio-group v-model="form.menuType">
<el-radio value="M">目录</el-radio>
<el-radio value="C">菜单</el-radio>
<el-radio value="F">按钮</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 'F'">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="form.menuName" placeholder="请输入菜单名称" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 'F'">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 'F'">
<el-form-item prop="path">
<template #label>
<span>路由地址</span>
<el-tooltip content="访问的路由地址" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="form.path" placeholder="请输入路由地址" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType == 'C'">
<el-form-item prop="component">
<template #label>
<span>组件路径</span>
<el-tooltip content="访问的组件路径system/user/index" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="form.component" placeholder="请输入组件路径" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 'M'">
<el-form-item>
<template #label>
<span>权限字符</span>
<el-tooltip content="控制器中定义的权限字符" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 'F'">
<el-form-item>
<template #label>
<span>显示状态</span>
<el-tooltip content="选择隐藏则路由将不会出现在侧边栏" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-radio-group v-model="form.visible">
<el-radio value="0">显示</el-radio>
<el-radio value="1">隐藏</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType != 'F'">
<el-form-item>
<template #label>
<span>菜单状态</span>
<el-tooltip content="选择停用则路由将不会出现在侧边栏" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-radio-group v-model="form.status">
<el-radio value="0">正常</el-radio>
<el-radio value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { QuestionFilled } from '@element-plus/icons-vue'
import { getMenuList, getMenu, delMenu, addMenu, updateMenu } from '@/api/modules/system/menu'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const menuList = ref<any[]>([])
const loading = ref(false)
const showSearch = ref(true)
const menuOptions = ref<any[]>([])
const title = ref('')
const open = ref(false)
const isExpandAll = ref(false)
const refreshTable = ref(true)
const queryParams = reactive({
menuName: '',
status: ''
})
const form = ref<any>({
menuId: undefined,
parentId: 0,
menuName: '',
icon: '',
menuType: 'M',
orderNum: undefined,
isFrame: '1',
isCache: '0',
visible: '0',
status: '0',
perms: '',
path: '',
component: ''
})
const rules = {
menuName: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }]
}
async function getList() {
loading.value = true
try {
const res = await getMenuList(queryParams)
menuList.value = handleTree(res.data, 'menuId')
} finally {
loading.value = false
}
}
function handleTree(data: any[], id: string, parentId?: string, children?: string) {
const config = { id: id || 'id', parentId: parentId || 'parentId', childrenList: children || 'children' }
const childrenListMap: any = {}
const nodeIds: any = {}
const tree: any[] = []
for (const d of data) {
const pId = d[config.parentId]
if (!childrenListMap[pId]) childrenListMap[pId] = []
nodeIds[d[config.id]] = d
childrenListMap[pId].push(d)
}
for (const d of data) {
const pId = d[config.parentId]
if (!nodeIds[pId]) tree.push(d)
}
for (const t of tree) {
adaptToChildrenList(t)
}
function adaptToChildrenList(o: any) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]]
}
if (o[config.childrenList]) {
for (const c of o[config.childrenList]) {
adaptToChildrenList(c)
}
}
}
return tree
}
async function getTreeselect() {
const res = await getMenuList()
menuOptions.value = []
const menu: any = { menuId: 0, menuName: '主类目', children: [] }
menu.children = handleTree(res.data, 'menuId')
menuOptions.value.push(menu)
}
function handleQuery() {
getList()
}
function resetQuery() {
queryFormRef.value?.resetFields()
handleQuery()
}
function toggleExpandAll() {
refreshTable.value = false
isExpandAll.value = !isExpandAll.value
nextTick(() => {
refreshTable.value = true
})
}
async function handleAdd(row?: any) {
reset()
await getTreeselect()
if (row != null && row.menuId) {
form.value.parentId = row.menuId
} else {
form.value.parentId = 0
}
open.value = true
title.value = '添加菜单'
}
async function handleUpdate(row: any) {
reset()
await getTreeselect()
const res = await getMenu(row.menuId)
form.value = res.data
open.value = true
title.value = '修改菜单'
}
async function submitForm() {
const valid = await formRef.value?.validate().catch(() => false)
if (valid) {
if (form.value.menuId != undefined) {
await updateMenu(form.value)
ElMessage.success('修改成功')
} else {
await addMenu(form.value)
ElMessage.success('新增成功')
}
open.value = false
getList()
}
}
function handleDelete(row: any) {
ElMessageBox.confirm(`是否确认删除名称为"${row.menuName}"的数据项?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await delMenu(row.menuId)
ElMessage.success('删除成功')
getList()
}).catch(() => {})
}
function reset() {
form.value = {
menuId: undefined,
parentId: 0,
menuName: '',
icon: '',
menuType: 'M',
orderNum: undefined,
isFrame: '1',
isCache: '0',
visible: '0',
status: '0',
perms: '',
path: '',
component: ''
}
formRef.value?.resetFields()
}
function cancel() {
open.value = false
reset()
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.app-container { padding: 20px; }
</style>

@ -0,0 +1,134 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="公告标题" prop="noticeTitle">
<el-input v-model="queryParams.noticeTitle" placeholder="请输入公告标题" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="操作人员" prop="createBy">
<el-input v-model="queryParams.createBy" placeholder="请输入操作人员" clearable size="small" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="类型" prop="noticeType">
<el-select v-model="queryParams.noticeType" placeholder="公告类型" clearable size="small">
<el-option label="通知" value="1" /><el-option label="公告" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:notice:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['system:notice:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:notice:remove']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="noticeList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="序号" align="center" prop="noticeId" width="100" />
<el-table-column label="公告标题" align="center" prop="noticeTitle" :show-overflow-tooltip="true" />
<el-table-column label="公告类型" align="center" prop="noticeType" width="100">
<template #default="scope">
<el-tag :type="scope.row.noticeType === '1' ? 'primary' : 'success'">{{ scope.row.noticeType === '1' ? '通知' : '公告' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '正常' : '关闭' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="创建者" align="center" prop="createBy" width="100" />
<el-table-column label="创建时间" align="center" prop="createTime" width="100" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:notice:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:notice:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="780px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="公告标题" prop="noticeTitle"><el-input v-model="form.noticeTitle" placeholder="请输入公告标题" /></el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公告类型" prop="noticeType">
<el-select v-model="form.noticeType" placeholder="请选择">
<el-option label="通知" value="1" /><el-option label="公告" value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="状态">
<el-radio-group v-model="form.status"><el-radio value="0">正常</el-radio><el-radio value="1"></el-radio></el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="内容"><el-input v-model="form.noticeContent" type="textarea" :autosize="{ minRows: 6, maxRows: 12 }" placeholder="请输入内容" /></el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getNoticeList, getNotice, delNotice, addNotice, updateNotice } from '@/api/modules/system/notice'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const noticeList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const queryParams = reactive({ pageNum: 1, pageSize: 10, noticeTitle: '', createBy: '', noticeType: '' })
const form = ref<any>({ noticeId: undefined, noticeTitle: '', noticeType: '', noticeContent: '', status: '0' })
const rules = {
noticeTitle: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],
noticeType: [{ required: true, message: '公告类型不能为空', trigger: 'change' }]
}
async function getList() { loading.value = true; try { const res: any = await getNoticeList(queryParams); noticeList.value = res.data.rows; total.value = res.data.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.noticeId); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { noticeId: undefined, noticeTitle: '', noticeType: '', noticeContent: '', status: '0' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加公告' }
async function handleUpdate(row?: any) { reset(); const noticeId = row?.noticeId || ids.value[0]; const res = await getNotice(noticeId); form.value = res.data; open.value = true; title.value = '修改公告' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.noticeId != undefined) { await updateNotice(form.value); ElMessage.success('修改成功') } else { await addNotice(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const noticeIds = row?.noticeId || ids.value; ElMessageBox.confirm(`是否确认删除公告编号为"${noticeIds}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delNotice(noticeIds); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,107 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="岗位编码" prop="postCode"><el-input v-model="queryParams.postCode" placeholder="请输入岗位编码" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="岗位名称" prop="postName"><el-input v-model="queryParams.postName" placeholder="请输入岗位名称" clearable size="small" @keyup.enter="handleQuery" /></el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="岗位状态" clearable size="small">
<el-option label="正常" value="0" /><el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5"><el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:post:add']"></el-button></el-col>
<el-col :span="1.5"><el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['system:post:edit']"></el-button></el-col>
<el-col :span="1.5"><el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:post:remove']"></el-button></el-col>
<el-col :span="1.5"><el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['system:post:export']"></el-button></el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="postList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="岗位编号" align="center" prop="postId" />
<el-table-column label="岗位编码" align="center" prop="postCode" />
<el-table-column label="岗位名称" align="center" prop="postName" />
<el-table-column label="岗位排序" align="center" prop="postSort" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope"><el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">{{ scope.row.status === '0' ? '正常' : '停用' }}</el-tag></template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:post:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:post:remove']"></el-button>
</template>
</el-table-column>
</el-table>
<el-pagination v-show="total > 0" :total="total" v-model:current-page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" layout="total, sizes, prev, pager, next, jumper" @current-change="getList" @size-change="getList" class="mt-4" />
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="岗位名称" prop="postName"><el-input v-model="form.postName" placeholder="请输入岗位名称" /></el-form-item>
<el-form-item label="岗位编码" prop="postCode"><el-input v-model="form.postCode" placeholder="请输入编码名称" /></el-form-item>
<el-form-item label="岗位顺序" prop="postSort"><el-input-number v-model="form.postSort" controls-position="right" :min="0" /></el-form-item>
<el-form-item label="岗位状态" prop="status">
<el-radio-group v-model="form.status"><el-radio value="0">正常</el-radio><el-radio value="1"></el-radio></el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark"><el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /></el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { getPostList, getPost, delPost, addPost, updatePost } from '@/api/modules/system/post'
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const postList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const queryParams = reactive({ pageNum: 1, pageSize: 10, postCode: '', postName: '', status: '' })
const form = ref<any>({ postId: undefined, postCode: '', postName: '', postSort: 0, status: '0', remark: '' })
const rules = {
postName: [{ required: true, message: '岗位名称不能为空', trigger: 'blur' }],
postCode: [{ required: true, message: '岗位编码不能为空', trigger: 'blur' }],
postSort: [{ required: true, message: '岗位顺序不能为空', trigger: 'blur' }]
}
async function getList() { loading.value = true; try { const res: any = await getPostList(queryParams); postList.value = res.data.rows; total.value = res.data.total } finally { loading.value = false } }
function handleQuery() { queryParams.pageNum = 1; getList() }
function resetQuery() { queryFormRef.value?.resetFields(); handleQuery() }
function handleSelectionChange(selection: any[]) { ids.value = selection.map(item => item.postId); single.value = selection.length !== 1; multiple.value = !selection.length }
function reset() { form.value = { postId: undefined, postCode: '', postName: '', postSort: 0, status: '0', remark: '' }; formRef.value?.resetFields() }
function cancel() { open.value = false; reset() }
function handleAdd() { reset(); open.value = true; title.value = '添加岗位' }
async function handleUpdate(row?: any) { reset(); const postId = row?.postId || ids.value[0]; const res = await getPost(postId); form.value = res.data; open.value = true; title.value = '修改岗位' }
async function submitForm() { const valid = await formRef.value?.validate().catch(() => false); if (valid) { if (form.value.postId != undefined) { await updatePost(form.value); ElMessage.success('修改成功') } else { await addPost(form.value); ElMessage.success('新增成功') } open.value = false; getList() } }
function handleDelete(row?: any) { const postIds = row?.postId || ids.value; ElMessageBox.confirm(`是否确认删除岗位编号为"${postIds}"的数据项?`, '系统提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { await delPost(postIds); ElMessage.success('删除成功'); getList() }).catch(() => {}) }
function handleExport() { window.open(`${import.meta.env.VITE_APP_BASE_API}/system/post/export`, '_blank') }
onMounted(() => { getList() })
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
</style>

@ -0,0 +1,514 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryFormRef" v-show="showSearch" :inline="true">
<el-form-item label="角色名称" prop="roleName">
<el-input
v-model="queryParams.roleName"
placeholder="请输入角色名称"
clearable
size="small"
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="权限字符" prop="roleKey">
<el-input
v-model="queryParams.roleKey"
placeholder="请输入权限字符"
clearable
size="small"
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="角色状态"
clearable
size="small"
style="width: 240px"
>
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain size="small" @click="handleAdd" v-hasPermi="['system:role:add']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain size="small" :disabled="single" @click="handleUpdate" v-hasPermi="['system:role:edit']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain size="small" :disabled="multiple" @click="handleDelete" v-hasPermi="['system:role:remove']"></el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain size="small" @click="handleExport" v-hasPermi="['system:role:export']"></el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
</el-row>
<el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<template v-if="scope.row.roleId !== 1">
<el-button size="small" type="primary" link @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
<el-button size="small" type="primary" link @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
<el-dropdown size="small" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['system:role:edit']">
<span class="el-dropdown-link">更多<el-icon class="el-icon--right"><arrow-down /></el-icon></span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="handleDataScope" v-hasPermi="['system:role:edit']"></el-dropdown-item>
<el-dropdown-item command="handleAuthUser" v-hasPermi="['system:role:edit']"></el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</template>
</el-table-column>
</el-table>
<el-pagination
v-show="total > 0"
:total="total"
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 30, 50]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="getList"
@size-change="getList"
class="mt-4"
/>
<!-- 添加或修改角色配置对话框 -->
<el-dialog :title="title" v-model="open" width="500px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item prop="roleKey">
<template #label>
<span>权限字符</span>
<el-tooltip content="控制器中定义的权限字符" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="form.roleKey" placeholder="请输入权限字符" />
</el-form-item>
<el-form-item label="角色顺序" prop="roleSort">
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio value="0">正常</el-radio>
<el-radio value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限">
<el-checkbox v-model="menuExpand" @change="(val: any) => handleCheckedTreeExpand(val, 'menu')">/</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="(val: any) => handleCheckedTreeNodeAll(val, 'menu')">/</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="(val: any) => handleCheckedTreeConnect(val, 'menu')"></el-checkbox>
<el-tree
class="tree-border"
:data="menuOptions"
show-checkbox
ref="menuRef"
node-key="id"
:check-strictly="!form.menuCheckStrictly"
empty-text="加载中,请稍候"
:props="{ children: 'children', label: 'label' }"
/>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
<!-- 分配角色数据权限对话框 -->
<el-dialog :title="title" v-model="openDataScope" width="500px" append-to-body>
<el-form :model="form" label-width="80px">
<el-form-item label="角色名称">
<el-input v-model="form.roleName" :disabled="true" />
</el-form-item>
<el-form-item label="权限字符">
<el-input v-model="form.roleKey" :disabled="true" />
</el-form-item>
<el-form-item label="权限范围">
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<el-option label="全部数据权限" value="1" />
<el-option label="自定数据权限" value="2" />
<el-option label="本部门数据权限" value="3" />
<el-option label="本部门及以下数据权限" value="4" />
<el-option label="仅本人数据权限" value="5" />
</el-select>
</el-form-item>
<el-form-item label="数据权限" v-show="form.dataScope == '2'">
<el-checkbox v-model="deptExpand" @change="(val: any) => handleCheckedTreeExpand(val, 'dept')">/</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="(val: any) => handleCheckedTreeNodeAll(val, 'dept')">/</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="(val: any) => handleCheckedTreeConnect(val, 'dept')"></el-checkbox>
<el-tree
class="tree-border"
:data="deptOptions"
show-checkbox
default-expand-all
ref="deptRef"
node-key="id"
:check-strictly="!form.deptCheckStrictly"
empty-text="加载中,请稍候"
:props="{ children: 'children', label: 'label' }"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button type="primary" @click="submitDataScope"> </el-button>
<el-button @click="cancelDataScope"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, nextTick, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { ArrowDown, QuestionFilled } from '@element-plus/icons-vue'
import {
getRoleList,
getRole,
delRole,
addRole,
updateRole,
dataScope,
changeRoleStatus,
getMenuTreeselect,
roleMenuTreeselect,
roleDeptTreeselect
} from '@/api/modules/system/role'
const { proxy } = getCurrentInstance() as any
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const menuRef = ref()
const deptRef = ref()
const roleList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const openDataScope = ref(false)
const menuExpand = ref(false)
const menuNodeAll = ref(false)
const deptExpand = ref(true)
const deptNodeAll = ref(false)
const dateRange = ref<string[]>([])
const menuOptions = ref<any[]>([])
const deptOptions = ref<any[]>([])
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
roleName: '',
roleKey: '',
status: ''
})
const form = ref<any>({
roleId: undefined,
roleName: '',
roleKey: '',
roleSort: 0,
status: '0',
menuIds: [],
deptIds: [],
menuCheckStrictly: true,
deptCheckStrictly: true,
remark: ''
})
const rules = {
roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
roleKey: [{ required: true, message: '权限字符不能为空', trigger: 'blur' }],
roleSort: [{ required: true, message: '角色顺序不能为空', trigger: 'blur' }]
}
async function getList() {
loading.value = true
try {
const params = { ...queryParams }
if (dateRange.value && dateRange.value.length === 2) {
Object.assign(params, { beginTime: dateRange.value[0], endTime: dateRange.value[1] })
}
const res: any = await getRoleList(params)
roleList.value = res.data.rows
total.value = res.data.total
} finally {
loading.value = false
}
}
function handleQuery() {
queryParams.pageNum = 1
getList()
}
function resetQuery() {
dateRange.value = []
queryFormRef.value?.resetFields()
handleQuery()
}
function handleSelectionChange(selection: any[]) {
ids.value = selection.map(item => item.roleId)
single.value = selection.length !== 1
multiple.value = !selection.length
}
function handleStatusChange(row: any) {
const text = row.status === '0' ? '启用' : '停用'
ElMessageBox.confirm(`确认要"${text}""${row.roleName}"角色吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await changeRoleStatus(row.roleId, row.status)
ElMessage.success(text + '成功')
}).catch(() => {
row.status = row.status === '0' ? '1' : '0'
})
}
function handleCommand(command: string, row: any) {
switch (command) {
case 'handleDataScope':
handleDataScope(row)
break
case 'handleAuthUser':
handleAuthUser(row)
break
}
}
function handleCheckedTreeExpand(value: boolean, type: string) {
if (type === 'menu' && menuRef.value) {
const treeList = menuOptions.value
for (let i = 0; i < treeList.length; i++) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value
}
} else if (type === 'dept' && deptRef.value) {
const treeList = deptOptions.value
for (let i = 0; i < treeList.length; i++) {
deptRef.value.store.nodesMap[treeList[i].id].expanded = value
}
}
}
function handleCheckedTreeNodeAll(value: boolean, type: string) {
if (type === 'menu' && menuRef.value) {
menuRef.value.setCheckedNodes(value ? menuOptions.value : [])
} else if (type === 'dept' && deptRef.value) {
deptRef.value.setCheckedNodes(value ? deptOptions.value : [])
}
}
function handleCheckedTreeConnect(value: boolean, type: string) {
if (type === 'menu') {
form.value.menuCheckStrictly = value
} else if (type === 'dept') {
form.value.deptCheckStrictly = value
}
}
function getMenuAllCheckedKeys() {
let checkedKeys = menuRef.value?.getCheckedKeys() || []
let halfCheckedKeys = menuRef.value?.getHalfCheckedKeys() || []
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
return checkedKeys
}
function getDeptAllCheckedKeys() {
let checkedKeys = deptRef.value?.getCheckedKeys() || []
let halfCheckedKeys = deptRef.value?.getHalfCheckedKeys() || []
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys)
return checkedKeys
}
function reset() {
menuRef.value?.setCheckedKeys([])
menuExpand.value = false
menuNodeAll.value = false
deptExpand.value = true
deptNodeAll.value = false
form.value = {
roleId: undefined,
roleName: '',
roleKey: '',
roleSort: 0,
status: '0',
menuIds: [],
deptIds: [],
menuCheckStrictly: true,
deptCheckStrictly: true,
remark: ''
}
formRef.value?.resetFields()
}
async function handleAdd() {
reset()
const res = await getMenuTreeselect()
menuOptions.value = res.data
open.value = true
title.value = '添加角色'
}
async function handleUpdate(row?: any) {
reset()
const roleId = row?.roleId || ids.value[0]
const roleMenu = roleMenuTreeselect(roleId)
const res = await getRole(roleId)
form.value = res.data
open.value = true
await nextTick()
const menuRes: any = await roleMenu
menuRes.data.checkedKeys.forEach((v: any) => {
nextTick(() => {
menuRef.value?.setChecked(v, true, false)
})
})
title.value = '修改角色'
}
async function submitForm() {
const valid = await formRef.value?.validate().catch(() => false)
if (valid) {
form.value.menuIds = getMenuAllCheckedKeys()
if (form.value.roleId != undefined) {
await updateRole(form.value)
ElMessage.success('修改成功')
} else {
await addRole(form.value)
ElMessage.success('新增成功')
}
open.value = false
getList()
}
}
function handleDataScope(row: any) {
reset()
const roleDept = roleDeptTreeselect(row.roleId)
getRole(row.roleId).then(response => {
form.value = response.data
openDataScope.value = true
nextTick(() => {
roleDept.then((res: any) => {
deptRef.value?.setCheckedKeys(res.data.checkedKeys)
})
})
title.value = '分配数据权限'
})
}
async function submitDataScope() {
if (form.value.roleId != undefined) {
form.value.deptIds = getDeptAllCheckedKeys()
await dataScope(form.value)
ElMessage.success('修改成功')
openDataScope.value = false
getList()
}
}
function cancel() {
open.value = false
reset()
}
function cancelDataScope() {
openDataScope.value = false
reset()
}
function dataScopeSelectChange(value: string) {
if (value !== '2') {
deptRef.value?.setCheckedKeys([])
}
}
function handleAuthUser(row: any) {
proxy?.$router.push(`/system/role-auth/user/${row.roleId}`)
}
function handleDelete(row?: any) {
const roleIds = row?.roleId || ids.value
ElMessageBox.confirm(`是否确认删除角色编号为"${roleIds}"的数据项?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await delRole(roleIds)
ElMessage.success('删除成功')
getList()
}).catch(() => {})
}
function handleExport() {
window.open(`${import.meta.env.VITE_APP_BASE_API}/system/role/export`, '_blank')
}
onMounted(() => {
getList()
})
</script>
<style scoped>
.app-container { padding: 20px; }
.mt-4 { margin-top: 16px; }
.tree-border { margin-top: 8px; border: 1px solid #dcdfe6; border-radius: 4px; padding: 8px; }
</style>

@ -0,0 +1,673 @@
<template>
<div class="app-container">
<el-row :gutter="20">
<!-- 部门数据 -->
<el-col :span="4" :xs="24">
<div class="head-container">
<el-input
v-model="deptName"
placeholder="请输入部门名称"
clearable
size="small"
style="margin-bottom: 20px"
@input="filterDept"
/>
</div>
<div class="head-container">
<el-tree
:data="deptOptions"
:props="{ children: 'children', label: 'label' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="deptTreeRef"
default-expand-all
@node-click="handleNodeClick"
/>
</div>
</el-col>
<!-- 用户数据 -->
<el-col :span="20" :xs="24">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
size="small"
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input
v-model="queryParams.phonenumber"
placeholder="请输入手机号码"
clearable
size="small"
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="用户状态"
clearable
size="small"
style="width: 240px"
>
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
size="small"
style="width: 240px"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" size="small" @click="handleQuery"></el-button>
<el-button size="small" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
size="small"
@click="handleAdd"
v-hasPermi="['system:user:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
size="small"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:user:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
size="small"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:user:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
size="small"
@click="handleImport"
v-hasPermi="['system:user:import']"
>导入</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
size="small"
@click="handleExport"
v-hasPermi="['system:user:export']"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns" />
</el-row>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="用户编号" align="center" key="userId" prop="userId" v-if="columns[0].visible" />
<el-table-column label="用户名称" align="center" key="userName" prop="userName" v-if="columns[1].visible" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns[2].visible" :show-overflow-tooltip="true" />
<el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns[3].visible" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns[4].visible" width="120" />
<el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" v-if="columns[6].visible" width="160" />
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template #default="scope">
<template v-if="scope.row.userId !== 1">
<el-button
size="small"
type="primary"
link
@click="handleUpdate(scope.row)"
v-hasPermi="['system:user:edit']"
>修改</el-button>
<el-button
size="small"
type="primary"
link
@click="handleDelete(scope.row)"
v-hasPermi="['system:user:remove']"
>删除</el-button>
<el-dropdown size="small" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['system:user:resetPwd', 'system:user:edit']">
<span class="el-dropdown-link">
更多<el-icon class="el-icon--right"><arrow-down /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="handleResetPwd" v-hasPermi="['system:user:resetPwd']"></el-dropdown-item>
<el-dropdown-item command="handleAuthRole" v-hasPermi="['system:user:edit']"></el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</template>
</el-table-column>
</el-table>
<el-pagination
v-show="total > 0"
:total="total"
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 30, 50]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="getList"
@size-change="getList"
class="mt-4"
/>
</el-col>
</el-row>
<!-- 添加或修改用户配置对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="归属部门" prop="deptId">
<el-tree-select
v-model="form.deptId"
:data="deptOptions"
:props="{ label: 'label', children: 'children' } as any"
check-strictly
placeholder="请选择归属部门"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="手机号码" prop="phonenumber">
<el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item v-if="form.userId == undefined" label="用户密码" prop="password">
<el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用户性别">
<el-select v-model="form.sex" placeholder="请选择">
<el-option label="男" value="0" />
<el-option label="女" value="1" />
<el-option label="未知" value="2" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio value="0">正常</el-radio>
<el-radio value="1">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="岗位">
<el-select v-model="form.postIds" multiple placeholder="请选择">
<el-option
v-for="item in postOptions"
:key="item.postId"
:label="item.postName"
:value="item.postId"
:disabled="item.status == '1'"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="角色">
<el-select v-model="form.roleIds" multiple placeholder="请选择">
<el-option
v-for="item in roleOptions"
:key="item.roleId"
:label="item.roleName"
:value="item.roleId"
:disabled="item.status == '1'"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</template>
</el-dialog>
<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
<el-upload
ref="uploadRef"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的用户数据
<br />
<span>仅允许导入xlsxlsx格式文件</span>
</div>
</template>
</el-upload>
<template #footer>
<el-button type="primary" @click="submitFileForm"> </el-button>
<el-button @click="upload.open = false"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance } from 'element-plus'
import { ArrowDown, UploadFilled } from '@element-plus/icons-vue'
import {
getUserList,
getUser,
delUser,
addUser,
updateUser,
resetUserPwd,
changeUserStatus,
getDeptTreeselect,
getConfigKey
} from '@/api/modules/system/user'
import { getToken } from '@/utils/auth'
import type { ColumnOption } from '@/types'
const { proxy } = getCurrentInstance() as any
const deptTreeRef = ref()
const queryFormRef = ref<FormInstance>()
const formRef = ref<FormInstance>()
const uploadRef = ref()
const userList = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const ids = ref<any[]>([])
const title = ref('')
const open = ref(false)
const deptName = ref('')
const deptOptions = ref<any[]>([])
const postOptions = ref<any[]>([])
const roleOptions = ref<any[]>([])
const dateRange = ref<string[]>([])
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
userName: '',
phonenumber: '',
status: '',
deptId: undefined as any
})
const form = ref<any>({
userId: undefined,
deptId: undefined,
userName: '',
nickName: '',
password: '',
phonenumber: '',
email: '',
sex: '',
status: '0',
remark: '',
postIds: [],
roleIds: []
})
const rules = {
userName: [
{ required: true, message: '用户名称不能为空', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名称长度必须介于 2 和 20 之间', trigger: 'blur' }
],
nickName: [
{ required: true, message: '用户昵称不能为空', trigger: 'blur' }
],
password: [
{ required: true, message: '用户密码不能为空', trigger: 'blur' },
{ min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' }
],
email: [
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
],
phonenumber: [
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
}
const columns = ref<ColumnOption[]>([
{ key: 0, label: '用户编号', visible: true },
{ key: 1, label: '用户名称', visible: true },
{ key: 2, label: '用户昵称', visible: true },
{ key: 3, label: '部门', visible: true },
{ key: 4, label: '手机号码', visible: true },
{ key: 5, label: '状态', visible: true },
{ key: 6, label: '创建时间', visible: true }
])
const upload = reactive({
open: false,
title: '',
isUploading: false,
updateSupport: 0,
headers: { Authorization: `Bearer ${getToken()}` },
url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData'
})
/** 查询用户列表 */
async function getList() {
loading.value = true
try {
const params = { ...queryParams }
if (dateRange.value && dateRange.value.length === 2) {
Object.assign(params, {
beginTime: dateRange.value[0],
endTime: dateRange.value[1]
})
}
const res: any = await getUserList(params)
userList.value = res.data.rows
total.value = res.data.total
} finally {
loading.value = false
}
}
/** 查询部门下拉树结构 */
async function getTreeselect() {
const res = await getDeptTreeselect()
deptOptions.value = res.data
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
queryFormRef.value?.resetFields()
handleQuery()
}
/** 部门筛选 */
function filterDept(val: string) {
deptTreeRef.value?.filter(val)
}
function filterNode(value: string, data: any) {
if (!value) return true
return data.label.indexOf(value) !== -1
}
/** 部门树节点单击 */
function handleNodeClick(data: any) {
queryParams.deptId = data.id
handleQuery()
}
/** 多选框选中数据 */
function handleSelectionChange(selection: any[]) {
ids.value = selection.map(item => item.userId)
single.value = selection.length !== 1
multiple.value = !selection.length
}
/** 用户状态修改 */
function handleStatusChange(row: any) {
const text = row.status === '0' ? '启用' : '停用'
ElMessageBox.confirm(`确认要"${text}""${row.userName}"用户吗?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await changeUserStatus(row.userId, row.status)
ElMessage.success(text + '成功')
}).catch(() => {
row.status = row.status === '0' ? '1' : '0'
})
}
/** 更多操作 */
function handleCommand(command: string, row: any) {
switch (command) {
case 'handleResetPwd':
handleResetPwd(row)
break
case 'handleAuthRole':
handleAuthRole(row)
break
}
}
/** 重置密码 */
function handleResetPwd(row: any) {
ElMessageBox.prompt(`请输入"${row.userName}"的新密码`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^.{5,20}$/,
inputErrorMessage: '用户密码长度必须介于 5 和 20 之间'
}).then(async ({ value }) => {
await resetUserPwd(row.userId, value)
ElMessage.success('修改成功,新密码是:' + value)
}).catch(() => {})
}
/** 分配角色操作 */
function handleAuthRole(row: any) {
proxy?.$router.push(`/system/user-auth/role/${row.userId}`)
}
/** 新增按钮操作 */
async function handleAdd() {
reset()
await getTreeselect()
const res: any = await getUser(undefined)
postOptions.value = res.data.posts
roleOptions.value = res.data.roles
open.value = true
title.value = '添加用户'
const configRes: any = await getConfigKey('sys.user.initPassword')
form.value.password = configRes.data.msg
}
/** 修改按钮操作 */
async function handleUpdate(row?: any) {
reset()
await getTreeselect()
const userId = row?.userId || ids.value[0]
const res: any = await getUser(userId)
form.value = res.data
postOptions.value = res.data.posts
roleOptions.value = res.data.roles
form.value.postIds = res.data.postIds
form.value.roleIds = res.data.roleIds
open.value = true
title.value = '修改用户'
form.value.password = ''
}
/** 提交按钮 */
async function submitForm() {
const valid = await formRef.value?.validate().catch(() => false)
if (valid) {
if (form.value.userId != undefined) {
await updateUser(form.value)
ElMessage.success('修改成功')
} else {
await addUser(form.value)
ElMessage.success('新增成功')
}
open.value = false
getList()
}
}
/** 删除按钮操作 */
function handleDelete(row?: any) {
const userIds = row?.userId || ids.value
ElMessageBox.confirm(`是否确认删除用户编号为"${userIds}"的数据项?`, '系统提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await delUser(userIds)
ElMessage.success('删除成功')
getList()
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
// 使 window.location download
window.open(`${import.meta.env.VITE_APP_BASE_API}/system/user/export?userName=${queryParams.userName}&phonenumber=${queryParams.phonenumber}&status=${queryParams.status}`, '_blank')
}
/** 导入按钮操作 */
function handleImport() {
upload.title = '用户导入'
upload.open = true
}
/** 文件上传中处理 */
function handleFileUploadProgress() {
upload.isUploading = true
}
/** 文件上传成功处理 */
function handleFileSuccess(response: any) {
upload.open = false
upload.isUploading = false
uploadRef.value?.clearFiles()
ElMessageBox.alert(`<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>${response.msg}</div>`, '导入结果', { dangerouslyUseHTMLString: true })
getList()
}
/** 提交上传文件 */
function submitFileForm() {
uploadRef.value?.submit()
}
/** 表单重置 */
function reset() {
form.value = {
userId: undefined,
deptId: undefined,
userName: '',
nickName: '',
password: '',
phonenumber: '',
email: '',
sex: '',
status: '0',
remark: '',
postIds: [],
roleIds: []
}
formRef.value?.resetFields()
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
onMounted(() => {
getList()
getTreeselect()
})
</script>
<style scoped>
.app-container {
padding: 20px;
}
.mt-4 {
margin-top: 16px;
}
</style>

@ -16,5 +16,5 @@
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/auto-imports.d.ts", "src/components.d.ts"]
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "src/auto-imports.d.ts", "src/components.d.ts", "src/types/global.d.ts"]
}

@ -35,7 +35,7 @@ export default defineConfig(({ mode }) => {
}
},
server: {
port: 80,
port: 5173,
host: true,
proxy: {
'/dev-api': {

Loading…
Cancel
Save