fix: 系统对接接口

This commit is contained in:
huchengrui 2025-10-21 17:35:56 +08:00
parent 82f485385b
commit 8c4549892b
19 changed files with 2077 additions and 950 deletions

View File

@ -14,13 +14,23 @@ export interface MenuItem {
code: string
icon: string
path: string
parentId: string
parentId: string | null
sort: number
type: number
children?: MenuItem[]
children: MenuItem[]
}
// 用户信息接口
// 登录响应中的用户信息
export interface LoginUserInfo {
userId: string
username: string
realName: string
token: string
roles: string[]
permissions: string[]
}
// 完整的用户信息接口
export interface UserInfo {
userId: string
username: string
@ -28,22 +38,23 @@ export interface UserInfo {
email: string
phone: string
status: number
lastLoginIp: string
lastLoginTime: string
lastLoginIp: string | null
lastLoginTime: string | null
roles: string[]
permissions: string[]
menus: MenuItem[]
}
export interface LoginResponse {
token: string
userInfo?: UserInfo
code: number
message: string
data: LoginUserInfo
}
export interface UserInfoResponse {
code: number
data: UserInfo
message: string
data: UserInfo
}
// 用户登录
@ -53,7 +64,7 @@ export function login(data: LoginParams) {
// 获取当前用户信息
export function getUserInfo() {
return get<UserInfo>('/api/system/auth/info')
return get<UserInfoResponse>('/api/system/auth/info')
}
// 登出接口

109
src/api/dict.ts Normal file
View File

@ -0,0 +1,109 @@
import { get, post, put, del } from './request'
// 字典参数接口
export interface DictParam {
id: string
createBy: string
createTime: string
updateBy: string
updateTime: string
dataType: string
flag: number
paramName: string
paramType: string
paramValue: string
remark: string
sortOrder: number
status: number
}
// 字典参数表单数据
export interface DictParamFormData {
id?: string
createBy?: string
createTime?: string
updateBy?: string
updateTime?: string
dataType: string
flag?: number
paramName: string
paramType: string
paramValue: string
remark?: string
sortOrder: number
status: number
}
// 分页查询参数
export interface DictPageParams {
typeCode?: string
pageSize: number
pageNum: number
}
// 批量获取参数
export interface BatchParamsQuery {
typeCodes: string // 参数类型编码列表,多个用逗号分隔
}
// 获取参数值查询参数
export interface ParamValueQuery {
paramName: string
typeCode: string
}
// API响应接口
export interface ApiResponse<T = unknown> {
code: number
message: string
data: T
}
export interface PageResponse<T> {
records: T[]
total: number
size: number
current: number
pages: number
}
// 新增参数
export function addDictParam(data: DictParamFormData) {
return post<ApiResponse>('/system/param', data)
}
// 修改参数
export function updateDictParam(data: DictParamFormData) {
return put<ApiResponse>('/system/param', data)
}
// 批量获取多个参数类型的参数列表
export function getBatchDictParams(params: BatchParamsQuery) {
return get<ApiResponse<DictParam[]>>('/system/param/batch', params)
}
// 分页查询参数列表
export function getDictParamPage(params: DictPageParams) {
return get<ApiResponse<PageResponse<DictParam>>>('/system/param/list', params)
}
// 刷新参数缓存
export function refreshDictCache(typeCode?: string) {
const data = typeCode ? { typeCode } : {}
return post<ApiResponse>('/system/param/refresh', data)
}
// 根据参数类型获取参数列表
export function getDictParamsByType(typeCode: string) {
return get<ApiResponse<DictParam[]>>(`/system/param/type/${typeCode}`)
}
// 获取参数值
export function getDictParamValue(params: ParamValueQuery) {
return get<ApiResponse<string>>('/system/param/value', params)
}
// 删除参数
export function deleteDictParam(id: string) {
return del<ApiResponse>(`/system/param/${id}`)
}

50
src/api/permission.ts Normal file
View File

@ -0,0 +1,50 @@
import { get, post } from './request'
// 权限树节点接口
export interface PermissionTreeNode {
id: string
createTime?: string
createBy?: string | null
updateTime?: string
updateBy?: string | null
flag?: number
permissionName: string
permissionCode: string
permissionType: number
parentId: string | null
path: string
component: string
icon: string
sort: number
status: number | null
children?: PermissionTreeNode[]
}
// 更新角色权限参数
export interface UpdateRolePermissionsData {
roleId: string
permissionIds: string[]
}
// API响应接口
export interface ApiResponse<T = unknown> {
code: number
message: string
data: T
}
// 根据角色获取权限树
export function getPermissionTreeForRole(roleId?: string) {
const params = roleId ? { roleId } : {}
return get<ApiResponse<PermissionTreeNode[]>>('/api/permission/listForRoleTree', params)
}
// 获取角色已分配的权限ID列表
export function getRolePermissionIds(roleId: string) {
return get<ApiResponse<PermissionTreeNode[]>>(`/api/permission/role/${roleId}/permissionIds`)
}
// 更新角色权限
export function updateRolePermissions(data: UpdateRolePermissionsData) {
return post<ApiResponse>('/api/permission/updateRolePermissions', data)
}

103
src/api/role.ts Normal file
View File

@ -0,0 +1,103 @@
import { get, post, put, del } from './request'
// 角色信息接口
export interface Role {
id: string
roleCode: string
roleName: string
roleDesc: string
status: number
sort: number
loginType: number
flag: number
createBy: string
createTime: string
updateBy: string
updateTime: string
}
// 角色列表查询参数
export interface RoleListParams {
roleName?: string
status?: number
}
// 角色分页查询参数
export interface RolePageParams {
pageNum: number
pageSize: number
roleCode?: string
roleName?: string
status?: number
}
// 角色新增/更新参数
export interface RoleFormData {
id?: string
roleCode: string
roleName: string
roleDesc: string
status: number
sort: number
loginType?: number
flag?: number
createBy?: string
createTime?: string
updateBy?: string
updateTime?: string
}
// API响应接口
export interface ApiResponse<T = unknown> {
code: number
message: string
data: T
}
export interface PageResponse<T> {
records: T[]
total: number
size: number
current: number
pages: number
}
// 获取角色列表
export function getRoleList(params?: RoleListParams) {
return get<ApiResponse<Role[]>>('/api/role/list', params)
}
// 分页查询角色列表
export function getRolePage(params: RolePageParams) {
return get<ApiResponse<PageResponse<Role>>>('/api/role/page', params)
}
// 获取角色详情
export function getRoleDetail(roleId: string) {
return get<ApiResponse<Role>>(`/api/role/detail/${roleId}`)
}
// 新增角色
export function addRole(data: RoleFormData) {
return post<ApiResponse>('/api/role/add', data)
}
// 更新角色
export function updateRole(data: RoleFormData) {
return put<ApiResponse>('/api/role/update', data)
}
// 删除角色
export function deleteRole(roleId: string) {
return del<ApiResponse>(`/api/role/delete/${roleId}`)
}
// 启用角色
export function enableRole(roleId: string) {
return put<ApiResponse>(`/api/role/restore/${roleId}`)
}
// 禁用角色
export function disableRole(roleId: string) {
return put<ApiResponse>(`/api/role/disable/${roleId}`)
}

126
src/api/user.ts Normal file
View File

@ -0,0 +1,126 @@
import { get, post, put, del } from './request'
// 用户信息接口
export interface User {
id: string
createBy: string
createTime: string
updateBy: string
updateTime: string
email: string
flag: number
lastLoginIp: string | null
lastLoginTime: string | null
password?: string
phone: string
realName: string
status: number
username: string
roles?: string[] // 用户角色ID数组
}
// 用户分页查询参数
export interface UserPageParams {
pageNum: number
pageSize: number
realName?: string
status?: number
username?: string
}
// 用户新增/更新参数
export interface UserFormData {
id?: string
createBy?: string
createTime?: string
updateBy?: string
updateTime?: string
email: string
flag?: number
lastLoginIp?: string
lastLoginTime?: string
password?: string
phone: string
realName: string
status: number
username: string
}
// 分配角色参数
export interface AssignRolesData {
userId: string
roleIds: string[]
}
// 修改密码参数
export interface ChangePasswordData {
id: string
newPassword: string
oldPassword: string
}
// 修改用户状态参数
export interface UpdateUserStatusData {
id: string
status: number
}
// API响应接口
export interface ApiResponse<T = unknown> {
code: number
message: string
data: T
}
export interface PageResponse<T> {
records: T[]
total: number
size: number
current: number
pages: number
}
// 新增用户
export function addUser(data: UserFormData) {
return post<ApiResponse>('/system/user/add', data)
}
// 分配用户角色
export function assignUserRoles(data: AssignRolesData) {
return post<ApiResponse>('/system/user/assignRoles', data)
}
// 删除用户
export function deleteUser(id: string) {
return del<ApiResponse>(`/system/user/delete/${id}`)
}
// 分页查询用户列表
export function getUserPage(params: UserPageParams) {
return get<ApiResponse<PageResponse<User>>>('/system/user/page', params)
}
// 修改密码
export function changePassword(data: ChangePasswordData) {
return put<ApiResponse>('/system/user/password', data)
}
// 重置密码
export function resetUserPassword(id: string) {
return put<ApiResponse>(`/system/user/reset-password/${id}`)
}
// 修改用户状态
export function updateUserStatus(data: UpdateUserStatusData) {
return put<ApiResponse>('/system/user/status', data)
}
// 修改用户
export function updateUser(data: UserFormData) {
return put<ApiResponse>('/system/user/update', data)
}
// 获取用户详情
export function getUserDetail(id: string) {
return get<ApiResponse<User>>(`/system/user/${id}`)
}

61
src/config/menu.ts Normal file
View File

@ -0,0 +1,61 @@
// 菜单路径映射配置
export const MENU_PATH_MAP: Record<string, string> = {
// 系统管理
'/system': '/admin/system',
'/system/user': '/admin/users',
'/system/role': '/admin/roles', // 角色管理
'/system/permission': '/admin/permissions',
'/system/param': '/admin/dict',
// 人才管理
'/talent': '/admin/talent',
'/talent/basic': '/admin/talent-profile',
'/talent/unit': '/admin/talent-unit',
'/talent/resume': '/admin/talent-resume',
'/talent/domain': '/admin/talent-domain',
// 科研管理
'/research': '/admin/research',
'/research/project': '/admin/tech-projects',
'/research/initiation': '/admin/research-initiation',
'/research/report': '/admin/tech-reports',
// 成果管理
'/achievement': '/admin/achievement',
'/achievement/paper': '/admin/tech-achievements',
'/achievement/patent': '/admin/tech-patents',
'/achievement/award': '/admin/tech-awards',
'/achievement/honor': '/admin/tech-honors'
}
// 图标映射配置
export const MENU_ICON_MAP: Record<string, string> = {
'setting': 'Setting',
'user': 'User',
'peoples': 'UserFilled',
'tree-table': 'Lock',
'dict': 'Setting',
'profile': 'User',
'company': 'OfficeBuilding',
'education': 'Document',
'skill': 'Star',
'research': 'DataAnalysis',
'project': 'Folder',
'form': 'Document',
'documentation': 'DataAnalysis',
'trophy': 'Trophy',
'document': 'Document',
'patent': 'Files',
'award': 'Trophy',
'medal': 'Medal'
}
// 将后端菜单路径转换为前端路由路径
export function convertMenuPath(backendPath: string): string {
return MENU_PATH_MAP[backendPath] || backendPath
}
// 将后端图标转换为 Element Plus 图标
export function convertMenuIcon(backendIcon: string): string {
return MENU_ICON_MAP[backendIcon] || 'Menu'
}

View File

@ -33,71 +33,37 @@
active-text-color="#409eff"
router
>
<!-- 数据概览 -->
<el-menu-item index="/admin/dashboard">
<el-icon><Odometer /></el-icon>
<span>数据概览</span>
</el-menu-item>
<el-menu-item-group title="内容管理">
<el-menu-item index="/admin/news-policy">
<el-icon><Document /></el-icon>
<span>新闻政策</span>
</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="科技资源">
<el-menu-item index="/admin/smart-qa">
<el-icon><ChatDotRound /></el-icon>
<span>科技问答</span>
</el-menu-item>
<!-- 动态菜单 -->
<template v-for="menu in userMenus" :key="menu.id">
<el-menu-item-group :title="menu.name" v-if="menu.children && menu.children.length > 0">
<el-menu-item
v-for="child in menu.children"
:key="child.id"
:index="convertMenuPath(child.path)"
>
<el-icon>
<component :is="convertMenuIcon(child.icon)" />
</el-icon>
<span>{{ child.name }}</span>
</el-menu-item>
</el-menu-item-group>
<el-menu-item index="/admin/tech-resources">
<el-icon><Files /></el-icon>
<span>科技资源</span>
<el-menu-item
v-else
:index="convertMenuPath(menu.path)"
>
<el-icon>
<component :is="convertMenuIcon(menu.icon)" />
</el-icon>
<span>{{ menu.name }}</span>
</el-menu-item>
<el-menu-item index="/admin/talent-profile">
<el-icon><User /></el-icon>
<span>科技人才</span>
</el-menu-item>
<el-menu-item index="/admin/tech-projects">
<el-icon><Folder /></el-icon>
<span>科技项目</span>
</el-menu-item>
<el-menu-item index="/admin/tech-achievements">
<el-icon><Trophy /></el-icon>
<span>科技成果</span>
</el-menu-item>
<el-menu-item index="/admin/tech-reports">
<el-icon><DataAnalysis /></el-icon>
<span>科技报告</span>
</el-menu-item>
<el-menu-item index="/admin/tech-awards">
<el-icon><Trophy /></el-icon>
<span>科技奖励</span>
</el-menu-item>
</el-menu-item-group>
<el-menu-item-group title="系统管理">
<el-menu-item index="/admin/users">
<el-icon><UserFilled /></el-icon>
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="/admin/permissions">
<el-icon><Lock /></el-icon>
<span>权限管理</span>
</el-menu-item>
<el-menu-item index="/admin/dict">
<el-icon><Setting /></el-icon>
<span>字典管理</span>
</el-menu-item>
</el-menu-item-group>
</template>
</el-menu>
</aside>
@ -128,11 +94,15 @@ import { computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/store/user'
import { convertMenuPath, convertMenuIcon } from '@/config/menu'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
//
const userMenus = computed(() => userStore.menus)
//
onMounted(() => {
userStore.initUserInfo()
@ -158,7 +128,9 @@ const breadcrumbConfig: Record<string, { title: string; parent?: string }> = {
'/admin/tech-reports': { title: '科技报告', parent: '科技资源' },
'/admin/tech-awards': { title: '科技奖励', parent: '科技资源' },
'/admin/users': { title: '用户管理', parent: '系统管理' },
'/admin/roles': { title: '角色管理', parent: '系统管理' },
'/admin/permissions': { title: '权限管理', parent: '系统管理' },
'/admin/permission-config': { title: '权限配置', parent: '系统管理' },
'/admin/dict': { title: '字典管理', parent: '系统管理' }
}
@ -218,6 +190,8 @@ const handleCommand = (command: string) => {
break
}
}
</script>
<style scoped>

View File

@ -10,6 +10,17 @@ import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// 忽略 ResizeObserver 错误
const resizeObserverErrorHandler = (e: ErrorEvent) => {
if (e.message === 'ResizeObserver loop completed with undelivered notifications.') {
e.stopImmediatePropagation()
return false
}
return true
}
window.addEventListener('error', resizeObserverErrorHandler)
const app = createApp(App)
const pinia = createPinia()

View File

@ -159,22 +159,26 @@ const routes: Array<RouteRecordRaw> = [
name: 'admin-users',
component: () => import('@/views/admin/system/users.vue')
},
{
path: 'roles',
name: 'admin-roles',
component: () => import('@/views/admin/system/roles.vue')
},
{
path: 'permissions',
name: 'admin-permissions',
component: () => import('@/views/admin/system/permissions.vue')
},
{
path: 'permission-config',
name: 'admin-permission-config',
component: () => import('@/views/admin/system/permission-config.vue')
},
{
path: 'dict',
name: 'admin-dict',
component: () => import('@/views/admin/system/dict.vue')
},
{
path: 'test-proxy',
name: 'admin-test-proxy',
component: () => import('@/views/admin/test-proxy.vue')
},
}
]
}
]

View File

@ -110,10 +110,13 @@ export const useUserStore = defineStore('user', {
// 构建树结构
menuList.forEach(menu => {
const menuItem = menuMap.get(menu.id)!
const menuItem = menuMap.get(menu.id)
if (!menuItem) return
if (menu.parentId && menuMap.has(menu.parentId)) {
const parent = menuMap.get(menu.parentId)!
const parent = menuMap.get(menu.parentId)
if (!parent) return
if (!parent.children) {
parent.children = []
}

View File

@ -50,28 +50,22 @@
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { ref, reactive, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { login, getUserInfo, type LoginParams } from '@/api/auth'
defineOptions({
name: 'LoginPage'
})
const router = useRouter()
const loginFormRef = ref<FormInstance>()
const loading = ref(false)
const loginFormRef = ref<FormInstance>()
//
const loginForm = reactive<LoginParams>({
username: '',
password: '',
loginType: 2 // 2
loginType: 2
})
//
const loginRules: FormRules = {
const loginRules = computed((): FormRules => ({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度在 2 到 20 个字符', trigger: 'blur' }
@ -80,13 +74,12 @@ const loginRules: FormRules = {
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
]
}
}))
//
const handleLogin = async () => {
if (!loginFormRef.value) return
await loginFormRef.value.validate(async (valid) => {
await loginFormRef.value.validate(async (valid: boolean) => {
if (valid) {
loading.value = true
try {
@ -96,6 +89,12 @@ const handleLogin = async () => {
const loginRes = await login(loginForm)
console.log('登录响应:', loginRes)
//
if (loginRes.code !== 200) {
ElMessage.error(loginRes.message || '登录失败')
return
}
// token
if (loginRes.data.token) {
localStorage.setItem('token', loginRes.data.token)
@ -121,26 +120,16 @@ const handleLogin = async () => {
router.push('/admin/dashboard')
}
} catch (error: any) {
} catch (error: unknown) {
console.error('登录失败:', error)
ElMessage.error(error.message || '登录失败,请检查用户名和密码')
const errorMessage = error instanceof Error ? error.message : '登录失败,请检查用户名和密码'
ElMessage.error(errorMessage)
} finally {
loading.value = false
}
}
})
}
//
const setTestAccount = () => {
loginForm.username = 'admin'
loginForm.password = '123456'
}
//
if (process.env.NODE_ENV === 'development') {
// setTestAccount() //
}
</script>
<style scoped>

View File

@ -53,7 +53,7 @@
<div class="pagination-section">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
@ -122,16 +122,17 @@
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import {
getDictList,
createDict,
updateDict,
deleteDict,
refreshDictCache
} from '@/api/system'
import type { DictItem } from '@/api/system'
import {
addDictParam,
updateDictParam,
deleteDictParam,
getDictParamPage,
refreshDictCache,
type DictParam,
type DictParamFormData
} from '@/api/dict'
defineOptions({
name: 'DictAdmin'
@ -141,11 +142,11 @@ const searchForm = reactive({
typeCode: ''
})
const tableData = ref<DictItem[]>([])
const tableData = ref<DictParam[]>([])
const loading = ref(false)
const pagination = reactive({
currentPage: 1,
pageNum: 1,
pageSize: 10,
total: 0
})
@ -156,7 +157,7 @@ const isEdit = ref(false)
const formRef = ref<FormInstance>()
const submitLoading = ref(false)
const formData = reactive<DictItem>({
const formData = reactive<DictParamFormData>({
paramName: '',
paramType: '',
paramValue: '',
@ -178,15 +179,36 @@ const formRules: FormRules = {
const fetchData = async () => {
loading.value = true
try {
const res = await getDictList({
page: pagination.currentPage,
const params = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
typeCode: searchForm.typeCode
})
tableData.value = res.data.list
pagination.total = res.data.total
}
const response = await getDictParamPage(params)
if (response.code === 200) {
//
if (Array.isArray(response.data)) {
// data
tableData.value = response.data
pagination.total = response.data.length
} else if (response.data && response.data.records) {
// data
tableData.value = response.data.records
pagination.total = response.data.total || response.data.records.length
} else {
//
tableData.value = []
pagination.total = 0
}
console.log('字典数据:', response.data)
console.log('表格数据:', tableData.value)
} else {
ElMessage.error(response.message || '获取数据失败')
}
} catch (error) {
console.error('获取数据失败', error)
ElMessage.error('获取数据失败')
} finally {
loading.value = false
}
@ -194,14 +216,14 @@ const fetchData = async () => {
//
const handleSearch = () => {
pagination.currentPage = 1
pagination.pageNum = 1
fetchData()
}
//
const handleReset = () => {
searchForm.typeCode = ''
pagination.currentPage = 1
pagination.pageNum = 1
fetchData()
}
@ -212,14 +234,14 @@ const handleAdd = () => {
}
//
const handleEdit = (row: DictItem) => {
const handleEdit = (row: DictParam) => {
isEdit.value = true
Object.assign(formData, row)
dialogVisible.value = true
}
//
const handleDelete = (row: DictItem) => {
const handleDelete = (row: DictParam) => {
ElMessageBox.confirm(
`确定要删除字典"${row.paramName}"吗?删除后无法恢复!`,
'删除确认',
@ -230,11 +252,18 @@ const handleDelete = (row: DictItem) => {
}
).then(async () => {
try {
await deleteDict(row.id!)
ElMessage.success('删除成功')
fetchData()
if (row.id) {
const response = await deleteDictParam(row.id)
if (response.code === 200) {
ElMessage.success('删除成功')
fetchData()
} else {
ElMessage.error(response.message || '删除失败')
}
}
} catch (error) {
console.error('删除失败', error)
ElMessage.error('删除失败')
}
}).catch(() => {
ElMessage.info('已取消删除')
@ -253,10 +282,15 @@ const handleRefreshCache = () => {
}
).then(async () => {
try {
await refreshDictCache()
ElMessage.success('缓存刷新成功')
const response = await refreshDictCache(searchForm.typeCode)
if (response.code === 200) {
ElMessage.success('缓存刷新成功')
} else {
ElMessage.error(response.message || '刷新缓存失败')
}
} catch (error) {
console.error('刷新缓存失败', error)
ElMessage.error('刷新缓存失败')
}
})
}
@ -265,21 +299,24 @@ const handleRefreshCache = () => {
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
await formRef.value.validate(async (valid: boolean) => {
if (valid) {
submitLoading.value = true
try {
if (isEdit.value) {
await updateDict(formData)
ElMessage.success('更新成功')
const response = isEdit.value
? await updateDictParam(formData)
: await addDictParam(formData)
if (response.code === 200) {
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
dialogVisible.value = false
fetchData()
} else {
await createDict(formData)
ElMessage.success('创建成功')
ElMessage.error(response.message || '操作失败')
}
dialogVisible.value = false
fetchData()
} catch (error) {
console.error('提交失败', error)
ElMessage.error('操作失败')
} finally {
submitLoading.value = false
}
@ -305,12 +342,12 @@ const handleDialogClose = () => {
//
const handleSizeChange = (size: number) => {
pagination.pageSize = size
pagination.currentPage = 1
pagination.pageNum = 1
fetchData()
}
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
pagination.pageNum = page
fetchData()
}

View File

@ -0,0 +1,398 @@
<template>
<div class="permission-config">
<div class="page-header">
<h2>权限配置</h2>
<p class="page-description" v-if="currentRoleName">
正在为角色 <el-tag type="primary">{{ currentRoleName }}</el-tag>
</p>
<p class="page-description" v-else>
配置系统权限控制功能访问范围
</p>
</div>
<!-- 权限树区域 -->
<div class="permission-tree-section">
<el-card>
<template #header>
<div class="card-header">
<span>权限配置</span>
<div class="header-actions">
<el-button size="small" @click="handleExpandAll">全部展开</el-button>
<el-button size="small" @click="handleCollapseAll">全部收起</el-button>
<el-button size="small" @click="handleCheckAll">全选</el-button>
<el-button size="small" @click="handleUncheckAll">全不选</el-button>
</div>
</div>
</template>
<div class="tree-container" v-loading="treeLoading">
<el-tree
v-if="permissionTree.length > 0"
ref="permissionTreeRef"
:data="permissionTree"
:props="treeProps"
show-checkbox
node-key="id"
:default-expanded-keys="defaultExpandedKeys"
:default-checked-keys="checkedPermissionIds"
check-strictly
@check="handleTreeCheck"
>
<template #default="{ data }">
<span class="tree-node">
<el-icon v-if="data.permissionType === 1" class="node-icon"><Folder /></el-icon>
<el-icon v-else class="node-icon"><Document /></el-icon>
<span class="node-label">{{ data.permissionName }}</span>
<span class="node-code" v-if="data.permissionCode">({{ data.permissionCode }})</span>
</span>
</template>
</el-tree>
</div>
<div class="tree-actions">
<el-button type="primary" @click="handleSavePermissions" :loading="saveLoading">
保存权限配置
</el-button>
<el-button @click="handleResetPermissions">重置</el-button>
<el-button @click="handleGoBack">返回</el-button>
</div>
</el-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElTree } from 'element-plus'
import {
getPermissionTreeForRole,
getRolePermissionIds,
updateRolePermissions,
type PermissionTreeNode
} from '@/api/permission'
defineOptions({
name: 'PermissionConfig'
})
const route = useRoute()
const router = useRouter()
//
const currentRoleId = ref<string>('')
const currentRoleName = ref<string>('')
const permissionTree = ref<PermissionTreeNode[]>([])
const checkedPermissionIds = ref<string[]>([])
const defaultExpandedKeys = ref<string[]>([])
const treeLoading = ref(false)
const saveLoading = ref(false)
//
const permissionTreeRef = ref<InstanceType<typeof ElTree>>()
//
const treeProps = {
children: 'children',
label: 'permissionName'
}
//
const fetchPermissionTree = async (roleId?: string) => {
treeLoading.value = true
try {
const response = await getPermissionTreeForRole(roleId)
if (response.code === 200) {
permissionTree.value = response.data
//
defaultExpandedKeys.value = response.data.map((item: PermissionTreeNode) => item.id)
} else {
ElMessage.error(response.message || '获取权限树失败')
}
} catch (error) {
console.error('获取权限树失败:', error)
ElMessage.error('获取权限树失败')
} finally {
treeLoading.value = false
}
}
//
const fetchRolePermissions = async (roleId: string) => {
try {
const response = await getRolePermissionIds(roleId)
if (response.code === 200) {
// status1ID
const checkedIds = response.data
.filter((permission: PermissionTreeNode) => permission.status === 1)
.map((permission: PermissionTreeNode) => permission.id)
checkedPermissionIds.value = checkedIds
console.log('已选中的权限ID:', checkedIds)
//
await nextTick()
setTimeout(() => {
if (permissionTreeRef.value) {
permissionTreeRef.value.setCheckedKeys(checkedIds)
}
}, 100)
} else {
ElMessage.error(response.message || '获取角色权限失败')
}
} catch (error) {
console.error('获取角色权限失败:', error)
ElMessage.error('获取角色权限失败')
}
}
//
const initPermissionConfig = async (roleId: string) => {
currentRoleId.value = roleId
// roleId
await fetchPermissionTree(roleId)
//
await fetchRolePermissions(roleId)
}
//
const handleTreeCheck = (data: PermissionTreeNode, checked: unknown): void => {
//
console.log('节点选中变化:', data, checked)
}
//
const handleExpandAll = (): void => {
if (permissionTreeRef.value) {
// key
const allKeys: string[] = []
const getAllKeys = (nodes: PermissionTreeNode[]) => {
nodes.forEach(node => {
allKeys.push(node.id)
if (node.children && node.children.length > 0) {
getAllKeys(node.children)
}
})
}
getAllKeys(permissionTree.value)
//
allKeys.forEach(key => {
permissionTreeRef.value?.store.nodesMap[key]?.expand()
})
}
}
//
const handleCollapseAll = (): void => {
if (permissionTreeRef.value) {
// key
const allKeys: string[] = []
const getAllKeys = (nodes: PermissionTreeNode[]) => {
nodes.forEach(node => {
allKeys.push(node.id)
if (node.children && node.children.length > 0) {
getAllKeys(node.children)
}
})
}
getAllKeys(permissionTree.value)
//
allKeys.forEach(key => {
permissionTreeRef.value?.store.nodesMap[key]?.collapse()
})
}
}
//
const handleCheckAll = (): void => {
if (permissionTreeRef.value) {
// key
const allKeys: string[] = []
const getAllKeys = (nodes: PermissionTreeNode[]) => {
nodes.forEach(node => {
allKeys.push(node.id)
if (node.children && node.children.length > 0) {
getAllKeys(node.children)
}
})
}
getAllKeys(permissionTree.value)
permissionTreeRef.value.setCheckedKeys(allKeys)
}
}
//
const handleUncheckAll = (): void => {
if (permissionTreeRef.value) {
permissionTreeRef.value.setCheckedKeys([])
}
}
//
const handleSavePermissions = async () => {
if (!currentRoleId.value) {
ElMessage.warning('角色ID不存在')
return
}
if (!permissionTreeRef.value) {
ElMessage.error('权限树未加载')
return
}
saveLoading.value = true
try {
// ID
const checkedKeys = permissionTreeRef.value.getCheckedKeys() as string[]
const halfCheckedKeys = permissionTreeRef.value.getHalfCheckedKeys() as string[]
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys]
const response = await updateRolePermissions({
roleId: currentRoleId.value,
permissionIds: allCheckedKeys
})
if (response.code === 200) {
ElMessage.success('权限配置保存成功')
//
await fetchRolePermissions(currentRoleId.value)
} else {
ElMessage.error(response.message || '保存权限配置失败')
}
} catch (error) {
console.error('保存权限配置失败:', error)
ElMessage.error('保存权限配置失败')
} finally {
saveLoading.value = false
}
}
//
const handleResetPermissions = async () => {
if (!currentRoleId.value) return
//
await fetchRolePermissions(currentRoleId.value)
ElMessage.info('已重置为原始权限配置')
}
//
const handleGoBack = () => {
router.push('/admin/permissions')
}
//
onMounted(async () => {
// URL
const roleIdFromQuery = route.query.roleId as string
const roleNameFromQuery = route.query.roleName as string
if (roleIdFromQuery) {
currentRoleName.value = roleNameFromQuery || ''
await initPermissionConfig(roleIdFromQuery)
} else {
ElMessage.error('缺少角色参数')
router.push('/admin/permissions')
}
})
</script>
<style scoped>
.permission-config {
padding: 20px;
}
.page-header {
margin-bottom: 20px;
}
.page-header h2 {
color: #303133;
font-size: 24px;
font-weight: 600;
margin: 0 0 8px 0;
}
.page-description {
color: #606266;
font-size: 14px;
margin: 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header-actions {
display: flex;
gap: 8px;
}
.permission-tree-section {
margin-bottom: 20px;
}
.tree-container {
min-height: 400px;
max-height: 600px;
overflow-y: auto;
border: 1px solid #dcdfe6;
border-radius: 4px;
padding: 10px;
}
.tree-node {
display: flex;
align-items: center;
gap: 6px;
}
.node-icon {
color: #409eff;
}
.node-label {
font-weight: 500;
}
.node-code {
color: #909399;
font-size: 12px;
margin-left: 8px;
}
.tree-actions {
margin-top: 16px;
text-align: right;
}
.tree-actions .el-button {
margin-left: 12px;
}
/* 自定义树节点样式 */
:deep(.el-tree-node__content) {
height: 36px;
line-height: 36px;
}
:deep(.el-tree-node__label) {
font-size: 14px;
}
/* 选中状态样式 */
:deep(.el-tree-node.is-checked > .el-tree-node__content) {
background-color: #f0f9ff;
}
/* 半选中状态样式 */
:deep(.el-tree-node.is-indeterminate > .el-tree-node__content) {
background-color: #fef7e0;
}
</style>

View File

@ -2,223 +2,195 @@
<div class="permissions-admin">
<div class="page-header">
<h2>权限管理</h2>
<p class="page-description">管理系统角色权限为不同角色分配相应的功能权限</p>
</div>
<div class="search-section">
<div class="search-form">
<el-form :inline="true" :model="searchForm" class="search-form-inline">
<el-form-item label="角色名称">
<el-input
v-model="searchForm.roleName"
placeholder="请输入角色名称"
clearable
style="width: 150px"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="全部" clearable style="width: 120px">
<el-option label="全部" value="" />
<el-option label="启用" value="启用" />
<el-option label="禁用" value="禁用" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置搜索</el-button>
</el-form-item>
</el-form>
</div>
<div class="action-buttons">
<el-button type="primary" @click="handleAdd">新增角色</el-button>
</div>
<!-- 搜索表单 -->
<div class="search-form">
<el-form :model="searchForm" inline>
<el-form-item label="角色名称">
<el-input
v-model="searchForm.roleName"
placeholder="请输入角色名称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="角色编码">
<el-input
v-model="searchForm.roleCode"
placeholder="请输入角色编码"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="状态">
<el-select
v-model="searchForm.status"
placeholder="请选择状态"
clearable
style="width: 120px"
>
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</div>
<!-- 角色列表 -->
<div class="table-section">
<el-table :data="tableData" style="width: 100%" stripe border v-loading="loading">
<el-table-column prop="roleName" label="角色名称" width="150" align="center" />
<el-table-column prop="roleCode" label="角色编码" width="150" align="center" />
<el-table-column prop="description" label="角色描述" min-width="200" />
<el-table-column prop="permissions" label="权限" min-width="300">
<template #default="scope">
<el-tag
v-for="permission in scope.row.permissions"
:key="permission"
size="small"
style="margin-right: 8px; margin-bottom: 4px;"
>
{{ permission }}
<el-table :data="roleList" v-loading="loading" stripe>
<el-table-column prop="id" label="角色ID" width="200" />
<el-table-column prop="roleName" label="角色名称" />
<el-table-column prop="roleCode" label="角色编码" />
<el-table-column prop="roleDesc" label="角色描述" />
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === '启用' ? 'success' : 'danger'">
{{ scope.row.status }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="updateTime" label="更新时间" width="180" align="center" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="warning" size="small" @click="handlePermissions(scope.row)">权限</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleConfigPermissions(row)">
配置权限
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="pagination-section">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { getRolePage, type Role } from '@/api/role'
defineOptions({
name: 'PermissionsAdmin'
})
interface RoleItem {
id: string
roleName: string
roleCode: string
description: string
permissions: string[]
status: string
updateTime: string
}
const router = useRouter()
//
const loading = ref(false)
const roleList = ref<Role[]>([])
//
const searchForm = reactive({
roleName: '',
status: ''
roleCode: '',
status: undefined as number | undefined
})
const tableData = ref<RoleItem[]>([])
const loading = ref(false)
//
const pagination = reactive({
currentPage: 1,
pageNum: 1,
pageSize: 10,
total: 0
})
const mockData: RoleItem[] = [
{
id: '1',
roleName: '超级管理员',
roleCode: 'super_admin',
description: '拥有系统所有权限',
permissions: ['用户管理', '内容管理', '系统设置', '数据统计'],
status: '启用',
updateTime: '2024-10-13 10:00:00'
},
{
id: '2',
roleName: '内容编辑',
roleCode: 'editor',
description: '负责内容的编辑和发布',
permissions: ['新闻政策', '科技问答', '科技资源'],
status: '启用',
updateTime: '2024-10-12 15:30:00'
},
{
id: '3',
roleName: '普通用户',
roleCode: 'user',
description: '只能查看内容',
permissions: ['内容查看'],
status: '启用',
updateTime: '2024-10-11 09:15:00'
}
]
const fetchData = () => {
//
const getRoleList = async () => {
loading.value = true
setTimeout(() => {
tableData.value = mockData
pagination.total = mockData.length
try {
const params = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
...searchForm
}
const response = await getRolePage(params)
if (response.code === 200) {
roleList.value = response.data.records
// total0使recordstotal
pagination.total = response.data.total || response.data.records.length
console.log('角色列表数据:', response.data)
console.log('roleList:', roleList.value)
console.log('total:', pagination.total)
} else {
ElMessage.error(response.message || '获取角色列表失败')
}
} catch (error) {
console.error('获取角色列表失败:', error)
ElMessage.error('获取角色列表失败')
} finally {
loading.value = false
}, 500)
}
}
//
const handleSearch = () => {
fetchData()
pagination.pageNum = 1
getRoleList()
}
//
const handleReset = () => {
searchForm.roleName = ''
searchForm.status = ''
fetchData()
Object.assign(searchForm, {
roleName: '',
roleCode: '',
status: undefined
})
pagination.pageNum = 1
getRoleList()
}
const handleAdd = () => {
ElMessage.info('新增角色功能开发中')
}
const handleEdit = (row: RoleItem) => {
ElMessage.info('编辑功能开发中')
console.log(row)
}
const handlePermissions = (row: RoleItem) => {
ElMessage.info('权限配置功能开发中')
console.log(row)
}
const handleDelete = (row: RoleItem) => {
ElMessageBox.confirm(
`确定要删除角色"${row.roleName}"吗?删除后无法恢复!`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: false
//
const handleConfigPermissions = (row: Role) => {
//
router.push({
path: '/admin/permission-config',
query: {
roleId: row.id,
roleName: row.roleName
}
).then(() => {
//
const index = tableData.value.findIndex(item => item.id === row.id)
if (index > -1) {
tableData.value.splice(index, 1)
pagination.total = tableData.value.length
//
if (tableData.value.length === 0 && pagination.currentPage > 1) {
pagination.currentPage--
}
ElMessage.success('删除成功')
}
}).catch(() => {
ElMessage.info('已取消删除')
})
}
//
const handleSizeChange = (size: number) => {
pagination.pageSize = size
fetchData()
pagination.pageNum = 1
getRoleList()
}
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
fetchData()
pagination.pageNum = page
getRoleList()
}
//
onMounted(() => {
fetchData()
getRoleList()
})
</script>
@ -237,31 +209,30 @@ onMounted(() => {
color: #303133;
font-size: 24px;
font-weight: 600;
margin: 0 0 8px 0;
}
.page-description {
color: #606266;
font-size: 14px;
margin: 0;
}
.search-section {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
padding: 20px;
background: #f8f9fa;
.search-form {
background: #ffffff;
border-radius: 8px;
}
.search-form-inline {
display: flex;
flex-wrap: wrap;
gap: 16px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #ebeef5;
}
.table-section {
margin-bottom: 20px;
}
.pagination-section {
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
justify-content: center;
}
</style>

View File

@ -0,0 +1,471 @@
<template>
<div class="roles-page">
<div class="page-header">
<h2>角色管理</h2>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增角色
</el-button>
</div>
<!-- 搜索表单 -->
<div class="search-form">
<el-form :model="searchForm" inline>
<el-form-item label="角色名称">
<el-input
v-model="searchForm.roleName"
placeholder="请输入角色名称"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="角色编码">
<el-input
v-model="searchForm.roleCode"
placeholder="请输入角色编码"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="状态">
<el-select
v-model="searchForm.status"
placeholder="请选择状态"
clearable
style="width: 120px"
>
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</div>
<div class="page-content">
<el-table :data="roleList" v-loading="loading">
<el-table-column prop="id" label="角色ID" width="200" />
<el-table-column prop="roleName" label="角色名称" />
<el-table-column prop="roleCode" label="角色编码" />
<el-table-column prop="roleDesc" label="角色描述" />
<el-table-column prop="sort" label="排序" width="80" />
<el-table-column prop="status" label="状态" width="100">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" />
<el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)">
编辑
</el-button>
<el-button type="info" size="small" @click="handlePermissions(row)">
权限
</el-button>
<el-button
v-if="row.status === 0"
type="success"
size="small"
@click="handleEnable(row)"
>
启用
</el-button>
<el-button
v-if="row.status === 1"
type="warning"
size="small"
@click="handleDisable(row)"
>
禁用
</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper">
<el-pagination
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
<!-- 角色表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑角色' : '新增角色'"
width="600px"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="formData.roleName" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="角色编码" prop="roleCode">
<el-input v-model="formData.roleCode" placeholder="请输入角色编码" />
</el-form-item>
<el-form-item label="角色描述" prop="roleDesc">
<el-input
v-model="formData.roleDesc"
type="textarea"
:rows="3"
placeholder="请输入角色描述"
/>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="formData.sort"
:min="0"
:max="999"
placeholder="请输入排序值"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</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, FormRules } from 'element-plus'
import {
getRolePage,
addRole,
updateRole,
deleteRole,
enableRole,
disableRole,
type Role,
type RoleFormData
} from '@/api/role'
import { useRouter } from 'vue-router'
const router = useRouter()
//
const loading = ref(false)
const submitLoading = ref(false)
const dialogVisible = ref(false)
const isEdit = ref(false)
const roleList = ref<Role[]>([])
//
const searchForm = reactive({
roleName: '',
roleCode: '',
status: undefined as number | undefined
})
//
const pagination = reactive({
pageNum: 1,
pageSize: 10,
total: 0
})
//
const formData = reactive<RoleFormData>({
id: '',
roleName: '',
roleCode: '',
roleDesc: '',
sort: 0,
status: 1
})
//
const formRef = ref<FormInstance>()
//
const formRules: FormRules = {
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
],
roleCode: [
{ required: true, message: '请输入角色编码', trigger: 'blur' }
],
sort: [
{ required: true, message: '请输入排序值', trigger: 'blur' }
]
}
//
const getRoleList = async () => {
loading.value = true
try {
const params = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
...searchForm
}
const response = await getRolePage(params)
if (response.code === 200) {
roleList.value = response.data.records
pagination.total = response.data.total || response.data.records.length
} else {
ElMessage.error(response.message || '获取角色列表失败')
}
} catch (error) {
console.error('获取角色列表失败:', error)
ElMessage.error('获取角色列表失败')
} finally {
loading.value = false
}
}
//
const handleSearch = () => {
pagination.pageNum = 1
getRoleList()
}
//
const handleReset = () => {
Object.assign(searchForm, {
roleName: '',
roleCode: '',
status: undefined
})
pagination.pageNum = 1
getRoleList()
}
//
const handleAdd = () => {
isEdit.value = false
//
dialogVisible.value = false
resetForm()
// 使 nextTick
nextTick(() => {
dialogVisible.value = true
})
}
//
const handleEdit = (row: Role) => {
isEdit.value = true
Object.assign(formData, row)
dialogVisible.value = true
}
//
const handleDelete = async (row: Role) => {
try {
await ElMessageBox.confirm(
`确定要删除角色"${row.roleName}"吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
const response = await deleteRole(row.id)
if (response.code === 200) {
ElMessage.success('删除成功')
getRoleList()
} else {
ElMessage.error(response.message || '删除失败')
}
} catch (error: unknown) {
if (error !== 'cancel') {
console.error('删除角色失败:', error)
ElMessage.error('删除失败')
}
}
}
//
const handleEnable = async (row: Role) => {
try {
const response = await enableRole(row.id)
if (response.code === 200) {
ElMessage.success('启用成功')
getRoleList()
} else {
ElMessage.error(response.message || '启用失败')
}
} catch (error) {
console.error('启用角色失败:', error)
ElMessage.error('启用失败')
}
}
//
const handleDisable = async (row: Role) => {
try {
await ElMessageBox.confirm(
`确定要禁用角色"${row.roleName}"吗?`,
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
const response = await disableRole(row.id)
if (response.code === 200) {
ElMessage.success('禁用成功')
getRoleList()
} else {
ElMessage.error(response.message || '禁用失败')
}
} catch (error: unknown) {
if (error !== 'cancel') {
console.error('禁用角色失败:', error)
ElMessage.error('禁用失败')
}
}
}
//
const handlePermissions = (row: Role) => {
// ID
router.push({
path: '/admin/permissions',
query: { roleId: row.id, roleName: row.roleName }
})
}
//
const handleCancel = () => {
dialogVisible.value = false
resetForm()
}
//
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
submitLoading.value = true
const response = isEdit.value
? await updateRole(formData)
: await addRole(formData)
if (response.code === 200) {
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
dialogVisible.value = false
getRoleList()
} else {
ElMessage.error(response.message || '操作失败')
}
} catch (error) {
console.error('提交表单失败:', error)
ElMessage.error('操作失败')
} finally {
submitLoading.value = false
}
}
//
const resetForm = () => {
//
formRef.value?.resetFields()
//
Object.assign(formData, {
id: '',
roleName: '',
roleCode: '',
roleDesc: '',
sort: 0,
status: 1
})
}
//
const handleSizeChange = (size: number) => {
pagination.pageSize = size
pagination.pageNum = 1
getRoleList()
}
const handleCurrentChange = (page: number) => {
pagination.pageNum = page
getRoleList()
}
//
onMounted(() => {
getRoleList()
})
</script>
<style scoped>
.roles-page {
background: #ffffff;
border-radius: 8px;
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.page-header h2 {
margin: 0;
color: #303133;
}
.page-content {
margin-top: 20px;
}
.search-form {
background: #ffffff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: center;
}
</style>

View File

@ -15,56 +15,92 @@
style="width: 150px"
/>
</el-form-item>
<el-form-item label="角色">
<el-select v-model="searchForm.role" placeholder="全部" clearable style="width: 120px">
<el-option label="全部" value="" />
<el-option label="管理员" value="管理员" />
<el-option label="编辑" value="编辑" />
<el-option label="普通用户" value="普通用户" />
<el-form-item label="真实姓名">
<el-input
v-model="searchForm.realName"
placeholder="请输入真实姓名"
clearable
style="width: 150px"
/>
</el-form-item>
<el-form-item label="邮箱">
<el-input
v-model="searchForm.email"
placeholder="请输入邮箱"
clearable
style="width: 180px"
/>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="全部" clearable style="width: 120px">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="handleReset">重置搜索</el-button>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">
<el-icon><Refresh /></el-icon>
重置
</el-button>
</el-form-item>
</el-form>
</div>
<div class="action-buttons">
<el-button type="primary" @click="handleAdd">新增用户</el-button>
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增用户
</el-button>
</div>
</div>
<div class="table-section">
<el-table :data="tableData" style="width: 100%" stripe border v-loading="loading">
<el-table :data="tableData" style="width: 100%; height: 100%;" stripe border v-loading="loading">
<el-table-column prop="username" label="用户名" width="120" align="center" />
<el-table-column prop="realName" label="真实姓名" width="120" align="center" />
<el-table-column prop="email" label="邮箱" min-width="180" />
<el-table-column prop="role" label="角色" width="100" align="center">
<el-table-column prop="email" label="邮箱" width="200" />
<el-table-column prop="phone" label="手机号" width="130" />
<el-table-column prop="roles" label="角色" width="150" align="center">
<template #default="scope">
<el-tag :type="getRoleType(scope.row.role)">
{{ scope.row.role }}
<el-tag
v-for="role in scope.row.roles"
:key="role"
size="small"
style="margin-right: 4px"
>
{{ role }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === '正常' ? 'success' : 'danger'">
{{ scope.row.status }}
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="lastLogin" label="最后登录" width="180" align="center" />
<el-table-column label="操作" width="200" align="center" fixed="right">
<el-table-column prop="lastLoginTime" label="最后登录" width="180" align="center" />
<el-table-column label="操作" align="center" fixed="right">
<template #default="scope">
<el-button type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button
:type="scope.row.status === '正常' ? 'warning' : 'success'"
size="small"
@click="handleToggleStatus(scope.row)"
>
{{ scope.row.status === '正常' ? '禁用' : '启用' }}
</el-button>
<el-button type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
<div class="action-links">
<el-link type="primary" @click="handleEdit(scope.row)">编辑</el-link>
<el-divider direction="vertical" />
<el-link type="primary" @click="handleAssignRoles(scope.row)">分配角色</el-link>
<el-divider direction="vertical" />
<el-link
:type="scope.row.status === 1 ? 'warning' : 'success'"
@click="handleToggleStatus(scope.row)"
>
{{ scope.row.status === 1 ? '禁用' : '启用' }}
</el-link>
<el-divider direction="vertical" />
<el-link type="primary" @click="handleResetPassword(scope.row)">重置密码</el-link>
<el-divider direction="vertical" />
<el-link type="danger" @click="handleDelete(scope.row)">删除</el-link>
</div>
</template>
</el-table-column>
</el-table>
@ -72,7 +108,7 @@
<div class="pagination-section">
<el-pagination
v-model:current-page="pagination.currentPage"
v-model:current-page="pagination.pageNum"
v-model:page-size="pagination.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="pagination.total"
@ -82,147 +118,405 @@
@current-change="handleCurrentChange"
/>
</div>
<!-- 用户表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="isEdit ? '编辑用户' : '新增用户'"
width="600px"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="用户名" prop="username">
<el-input v-model="formData.username" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="真实姓名" prop="realName">
<el-input v-model="formData.realName" placeholder="请输入真实姓名" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item v-if="!isEdit" label="密码" prop="password">
<el-input v-model="formData.password" type="password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
</template>
</el-dialog>
<!-- 角色分配对话框 -->
<el-dialog
v-model="roleDialogVisible"
title="分配角色"
width="500px"
>
<el-checkbox-group v-model="selectedRoleIds">
<el-checkbox
v-for="role in roleList"
:key="role.id"
:label="role.id"
style="display: block; margin-bottom: 10px;"
>
{{ role.roleName }} ({{ role.roleCode }})
</el-checkbox>
</el-checkbox-group>
<template #footer>
<el-button @click="roleDialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleSubmitRoles">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
import {
getUserPage,
addUser,
updateUser,
deleteUser,
updateUserStatus,
resetUserPassword,
assignUserRoles,
type User,
type UserFormData
} from '@/api/user'
import { getRoleList, type Role } from '@/api/role'
import { getUserDetail } from '@/api'
defineOptions({
name: 'UsersAdmin'
})
interface UserItem {
id: string
username: string
realName: string
email: string
role: string
status: string
lastLogin: string
}
const searchForm = reactive({
username: '',
role: ''
realName: '',
status: undefined as number | undefined
})
const tableData = ref<UserItem[]>([])
const tableData = ref<User[]>([])
const loading = ref(false)
const submitLoading = ref(false)
const dialogVisible = ref(false)
const roleDialogVisible = ref(false)
const isEdit = ref(false)
const pagination = reactive({
currentPage: 1,
pageNum: 1,
pageSize: 10,
total: 0
})
const mockData: UserItem[] = [
{
id: '1',
username: 'admin',
realName: '系统管理员',
email: 'admin@example.com',
role: '管理员',
status: '正常',
lastLogin: '2024-10-13 09:30:00'
},
{
id: '2',
username: 'editor',
realName: '内容编辑',
email: 'editor@example.com',
role: '编辑',
status: '正常',
lastLogin: '2024-10-12 16:45:00'
}
]
//
const formRef = ref<FormInstance>()
const formData = reactive<UserFormData>({
username: '',
realName: '',
email: '',
phone: '',
password: '',
status: 1
})
const getRoleType = (role: string) => {
switch (role) {
case '管理员': return 'danger'
case '编辑': return 'warning'
case '普通用户': return 'info'
default: return 'info'
//
const roleList = ref<Role[]>([])
const selectedUserId = ref<string>('')
const selectedRoleIds = ref<string[]>([])
//
const formRules: FormRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度在 2 到 20 个字符', trigger: 'blur' }
],
realName: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
]
}
//
const fetchData = async () => {
loading.value = true
try {
const params = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
...searchForm
}
const response = await getUserPage(params)
if (response.code === 200) {
tableData.value = response.data.records
pagination.total = response.data.total || response.data.records.length
} else {
ElMessage.error(response.message || '获取用户列表失败')
}
} catch (error) {
console.error('获取用户列表失败:', error)
ElMessage.error('获取用户列表失败')
} finally {
loading.value = false
}
}
const fetchData = () => {
loading.value = true
setTimeout(() => {
tableData.value = mockData
pagination.total = mockData.length
loading.value = false
}, 500)
//
const fetchRoleList = async () => {
try {
const response = await getRoleList()
if (response.code === 200) {
roleList.value = response.data
}
} catch (error) {
console.error('获取角色列表失败:', error)
}
}
const handleSearch = () => {
pagination.pageNum = 1
fetchData()
}
const handleReset = () => {
searchForm.username = ''
searchForm.role = ''
Object.assign(searchForm, {
username: '',
realName: '',
status: undefined
})
pagination.pageNum = 1
fetchData()
}
//
const handleAdd = () => {
ElMessage.info('新增用户功能开发中')
isEdit.value = false
resetForm()
dialogVisible.value = true
}
const handleEdit = (row: UserItem) => {
ElMessage.info('编辑功能开发中')
console.log(row)
//
const handleEdit = (row: User) => {
isEdit.value = true
Object.assign(formData, row)
//
delete formData.password
dialogVisible.value = true
}
const handleToggleStatus = (row: UserItem) => {
const action = row.status === '正常' ? '禁用' : '启用'
ElMessageBox.confirm(`确定要${action}该用户吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
ElMessage.success(`${action}成功`)
fetchData()
})
}
const handleDelete = (row: UserItem) => {
ElMessageBox.confirm(
`确定要删除用户"${row.username}"吗?删除后无法恢复!`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: false
}
).then(() => {
//
const index = tableData.value.findIndex(item => item.id === row.id)
if (index > -1) {
tableData.value.splice(index, 1)
pagination.total = tableData.value.length
//
const handleAssignRoles = async (row: User) => {
selectedUserId.value = row.id
selectedRoleIds.value = []
try {
//
await fetchRoleList()
//
const userDetailResponse = await getUserDetail(row.id)
if (userDetailResponse.code === 200) {
const userDetail = userDetailResponse.data
console.log('用户详情:', userDetail)
//
if (tableData.value.length === 0 && pagination.currentPage > 1) {
pagination.currentPage--
//
if (userDetail.roles && Array.isArray(userDetail.roles)) {
selectedRoleIds.value = userDetail.roles
console.log('已选中的角色ID:', selectedRoleIds.value)
}
ElMessage.success('删除成功')
} else {
ElMessage.error(userDetailResponse.message || '获取用户详情失败')
}
}).catch(() => {
ElMessage.info('已取消删除')
//
roleDialogVisible.value = true
} catch (error) {
console.error('获取用户详情失败:', error)
ElMessage.error('获取用户角色信息失败')
}
}
// /
const handleToggleStatus = async (row: User) => {
const newStatus = row.status === 1 ? 0 : 1
const action = newStatus === 1 ? '启用' : '禁用'
try {
await ElMessageBox.confirm(`确定要${action}该用户吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const response = await updateUserStatus({
id: row.id,
status: newStatus
})
if (response.code === 200) {
ElMessage.success(`${action}成功`)
fetchData()
} else {
ElMessage.error(response.message || `${action}失败`)
}
} catch (error: any) {
if (error !== 'cancel') {
console.error(`${action}用户失败:`, error)
ElMessage.error(`${action}失败`)
}
}
}
//
const handleResetPassword = async (row: User) => {
try {
await ElMessageBox.confirm(`确定要重置用户"${row.username}"的密码吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const response = await resetUserPassword(row.id)
if (response.code === 200) {
ElMessage.success('密码重置成功')
} else {
ElMessage.error(response.message || '密码重置失败')
}
} catch (error: any) {
if (error !== 'cancel') {
console.error('重置密码失败:', error)
ElMessage.error('重置密码失败')
}
}
}
//
const handleDelete = async (row: User) => {
try {
await ElMessageBox.confirm(
`确定要删除用户"${row.username}"吗?删除后无法恢复!`,
'删除确认',
{
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning'
}
)
const response = await deleteUser(row.id)
if (response.code === 200) {
ElMessage.success('删除成功')
fetchData()
} else {
ElMessage.error(response.message || '删除失败')
}
} catch (error: unknown) {
if (error !== 'cancel') {
console.error('删除用户失败:', error)
ElMessage.error('删除失败')
}
}
}
//
const handleSubmit = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
submitLoading.value = true
const response = isEdit.value
? await updateUser(formData)
: await addUser(formData)
if (response.code === 200) {
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
dialogVisible.value = false
fetchData()
} else {
ElMessage.error(response.message || '操作失败')
}
} catch (error) {
console.error('提交表单失败:', error)
ElMessage.error('操作失败')
} finally {
submitLoading.value = false
}
}
//
const handleSubmitRoles = async () => {
try {
const response = await assignUserRoles({
userId: selectedUserId.value,
roleIds: selectedRoleIds.value
})
if (response.code === 200) {
ElMessage.success('角色分配成功')
roleDialogVisible.value = false
fetchData()
} else {
ElMessage.error(response.message || '角色分配失败')
}
} catch (error) {
console.error('角色分配失败:', error)
ElMessage.error('角色分配失败')
}
}
//
const resetForm = () => {
formRef.value?.resetFields()
Object.assign(formData, {
username: '',
realName: '',
email: '',
phone: '',
password: '',
status: 1
})
}
const handleSizeChange = (size: number) => {
pagination.pageSize = size
pagination.pageNum = 1
fetchData()
}
const handleCurrentChange = (page: number) => {
pagination.currentPage = page
pagination.pageNum = page
fetchData()
}
@ -236,6 +530,9 @@ onMounted(() => {
background: #ffffff;
border-radius: 8px;
padding: 20px;
height: calc(100vh - 120px);
display: flex;
flex-direction: column;
}
.page-header {
@ -266,11 +563,30 @@ onMounted(() => {
}
.table-section {
flex: 1;
margin-bottom: 20px;
overflow: hidden;
}
.pagination-section {
display: flex;
justify-content: flex-end;
}
.action-links {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 2px;
}
.action-links .el-link {
font-size: 13px;
}
.action-links .el-divider--vertical {
height: 12px;
margin: 0 4px;
}
</style>

View File

@ -1,133 +0,0 @@
<template>
<div class="test-proxy">
<h2>代理测试页面</h2>
<div class="test-section">
<h3>环境信息</h3>
<p>当前环境: {{ currentEnv }}</p>
<p>Base URL: {{ baseURL }}</p>
<p>完整请求地址: {{ fullURL }}</p>
</div>
<div class="test-section">
<h3>测试请求</h3>
<el-button type="primary" @click="testRequest" :loading="loading">
测试 API 请求
</el-button>
<div v-if="result" class="result">
<h4>请求结果:</h4>
<pre>{{ result }}</pre>
</div>
<div v-if="error" class="error">
<h4>错误信息:</h4>
<pre>{{ error }}</pre>
</div>
</div>
<div class="test-section">
<h3>说明</h3>
<ul>
<li>如果看到跨域错误说明代理没有生效</li>
<li>请求应该发送到: http://localhost:8080/brain/...</li>
<li>而不是: http://47.110.148.47:8090/brain/...</li>
<li>如果代理正常请求会成功或返回后端错误非跨域错误</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { getEnvConfig } from '@/config/env'
import { getDictList } from '@/api/system'
defineOptions({
name: 'TestProxy'
})
const loading = ref(false)
const result = ref('')
const error = ref('')
const envConfig = getEnvConfig()
const currentEnv = process.env.NODE_ENV
const baseURL = envConfig.baseURL
const fullURL = computed(() => `${baseURL}/system/param/list`)
const testRequest = async () => {
loading.value = true
result.value = ''
error.value = ''
try {
console.log('发起测试请求...')
console.log('Base URL:', baseURL)
console.log('完整地址:', fullURL.value)
const res = await getDictList({
page: 1,
pageSize: 10
})
result.value = JSON.stringify(res, null, 2)
console.log('请求成功:', res)
} catch (err: any) {
error.value = err.message || '请求失败'
console.error('请求失败:', err)
//
if (err.message === 'Network Error' || err.code === 'ERR_NETWORK') {
error.value += '\n\n这可能是跨域错误请检查\n1. 开发服务器是否重启\n2. 代理配置是否正确\n3. baseURL 是否使用相对路径'
}
} finally {
loading.value = false
}
}
</script>
<style scoped>
.test-proxy {
padding: 20px;
max-width: 800px;
}
.test-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #eee;
border-radius: 8px;
}
.result {
margin-top: 20px;
padding: 15px;
background: #f0f9ff;
border: 1px solid #0ea5e9;
border-radius: 4px;
}
.error {
margin-top: 20px;
padding: 15px;
background: #fef2f2;
border: 1px solid #ef4444;
border-radius: 4px;
color: #dc2626;
}
pre {
white-space: pre-wrap;
word-break: break-all;
}
ul {
margin: 10px 0;
padding-left: 20px;
}
li {
margin: 5px 0;
}
</style>

View File

@ -1,374 +0,0 @@
{
"tool_call_id": "tool_4af0d1db-1bf4-4a48-8566-32bef59a73e8",
"status": "succeeded",
"content": "获取订舱列表成功,分页数据{'total': 60, 'page': 1, 'page_size': 10, 'total_pages': 6, 'has_next': True, 'has_prev': False}",
"raw": {
"code": 200,
"msg": "获取订舱列表成功,分页数据{'total': 60, 'page': 1, 'page_size': 10, 'total_pages': 6, 'has_next': True, 'has_prev': False}",
"data": {
"preview": {
"title": "",
"type": 1,
"id": "",
"content": {
"mcpName": "订舱列表",
"type": 1,
"id": "",
"fullscreen": false,
"formData": [
{
"name": "订舱列表",
"tag": "table",
"key": "booking_list",
"headerColumns": [
{
"title": "id",
"key": "id",
"align": "left",
"sortable": false,
"hidden": true,
"width": "",
"isExternal": false,
"externalType": 1
},
{
"title": "文件名",
"key": "file_name",
"align": "left",
"hidden": false,
"width": "",
"isExternal": false,
"externalType": 1
},
{
"title": "订舱编号",
"key": "booking_number",
"align": "left",
"hidden": false,
"width": "",
"isExternal": false,
"externalType": 1
},
{
"title": "状态",
"key": "status",
"align": "left",
"hidden": false,
"width": "",
"isExternal": false,
"externalType": 1
},
{
"title": "汇率",
"key": "usd",
"align": "left",
"hidden": false,
"width": "",
"isExternal": false,
"externalType": 1
},
{
"title": "实例id",
"key": "instance_id",
"align": "left",
"hidden": true,
"width": "",
"isExternal": false,
"externalType": 1
},
{
"title": "创建人",
"key": "create_by",
"align": "left",
"hidden": false,
"width": "",
"isExternal": false,
"externalType": 1
},
{
"title": "审核文件",
"key": "audit_file",
"align": "left",
"hidden": false,
"width": "",
"isExternal": true,
"externalType": 2
},
{
"title": "合同文件",
"key": "final_file",
"align": "left",
"hidden": false,
"width": "",
"isExternal": true,
"externalType": 2
},
{
"title": "操作",
"key": "operations",
"type": "operations",
"align": "center"
}
],
"contentColumns": [
"id",
"file_name",
"booking_number",
"status",
"usd",
"instance_id",
"create_by",
"audit_file",
"final_file",
"operations"
],
"dataRows": [
{
"id": "fd096220-da85-4c2b-99f6-b9d0962ee273",
"file_name": "高泰出口货订单表.xlsx",
"booking_number": "HO547781",
"status": "已完成",
"usd": "3.0000",
"instance_id": 694,
"create_by": "test123",
"audit_file": [
{
"fileName": "订舱明细HO54778120250920161525.xlsx",
"fileId": "cfb70f5f-f503-4674-a796-81d21365cbd7",
"fileType": "xlsx"
}
],
"final_file": [
{
"fileName": "订舱合同_HO547781_20250920161532_98601.xlsx",
"fileId": "a6136963-b75d-4753-aed6-be67ae28890f",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_HO547781_20250920161534_94197.xlsx",
"fileId": "19e49396-605e-469c-9aed-e494c4514849",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_HO547781_20250920161536_69715.xlsx",
"fileId": "47ed34a3-8f51-4c63-96b4-9adf425bd432",
"fileType": "xlsx"
}
]
},
{
"id": "8de4b287-e5f0-460d-bdb7-8bcaa4e618d8",
"file_name": "高泰出口货订单.xlsx",
"booking_number": null,
"status": "待审核",
"usd": null,
"instance_id": 692,
"create_by": "test123",
"audit_file": null,
"final_file": null
},
{
"id": "a54db8cc-c92d-4018-b57e-ebc64337baa1",
"file_name": "高泰出口货订单表.xlsx",
"booking_number": null,
"status": "待处理",
"usd": null,
"instance_id": 691,
"create_by": "test123",
"audit_file": null,
"final_file": null
},
{
"id": "f46ec726-97a6-4e19-b009-27c585444158",
"file_name": "高泰出口货订单.xlsx",
"booking_number": null,
"status": "待审核",
"usd": null,
"instance_id": 689,
"create_by": "test123",
"audit_file": null,
"final_file": null
},
{
"id": "aa75c1d8-3d63-4090-94a4-bcf340d7879d",
"file_name": "高泰出口货订单表.xlsx",
"booking_number": "LA158968",
"status": "已完成",
"usd": "3.0000",
"instance_id": 688,
"create_by": "test123",
"audit_file": [
{
"fileName": "订舱明细LA15896820250920155818.xlsx",
"fileId": "fa01de0a-af07-49c1-b101-30c4e98aa0f0",
"fileType": "xlsx"
}
],
"final_file": [
{
"fileName": "订舱合同_LA158968_20250920155824_68312.xlsx",
"fileId": "eb235513-f136-495e-9ee4-34e811a1d6a8",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_LA158968_20250920155826_42587.xlsx",
"fileId": "5c6b39aa-4f06-4136-937b-7ef0f642b7a4",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_LA158968_20250920155828_15180.xlsx",
"fileId": "4c83559c-182f-4e28-8acb-67796bac1703",
"fileType": "xlsx"
}
]
},
{
"id": "d7ab299b-d455-4b6e-8e43-507ea1a6f462",
"file_name": "高泰出口货订单.xlsx",
"booking_number": null,
"status": "待审核",
"usd": null,
"instance_id": 686,
"create_by": "test123",
"audit_file": null,
"final_file": null
},
{
"id": "ad21ebf2-d87a-43bf-8639-1cdabcd77c97",
"file_name": "高泰出口货订单表.xlsx",
"booking_number": "UO815266",
"status": "已完成",
"usd": "3.0000",
"instance_id": 685,
"create_by": "test123",
"audit_file": [
{
"fileName": "订舱明细UO81526620250920153747.xlsx",
"fileId": "2ccff892-ec61-4a8e-9901-f01c3daf72b7",
"fileType": "xlsx"
}
],
"final_file": [
{
"fileName": "订舱合同_UO815266_20250920153753_92191.xlsx",
"fileId": "d55d7929-5ea8-4129-a8c1-5d6f28ce89d9",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_UO815266_20250920153755_44609.xlsx",
"fileId": "19ef6a3b-d9d8-4987-b6bd-0bb48b0532e2",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_UO815266_20250920153757_77201.xlsx",
"fileId": "a1ebe5c5-bd9f-40f7-b919-8011e60915c3",
"fileType": "xlsx"
}
]
},
{
"id": "bae2609b-7608-41e3-93a2-02a81f7e738f",
"file_name": "高泰出口货订单.xlsx",
"booking_number": null,
"status": "待审核",
"usd": null,
"instance_id": 683,
"create_by": "test123",
"audit_file": null,
"final_file": null
},
{
"id": "e58886df-727b-4803-b13d-df0682d9e226",
"file_name": "高泰出口货订单表.xlsx",
"booking_number": "DR677738",
"status": "已完成",
"usd": "3.0000",
"instance_id": 682,
"create_by": "test123",
"audit_file": [
{
"fileName": "订舱明细DR67773820250920153514.xlsx",
"fileId": "85125ce5-708b-4eb0-81cf-94a29d014fec",
"fileType": "xlsx"
}
],
"final_file": [
{
"fileName": "订舱合同_DR677738_20250920153522_95548.xlsx",
"fileId": "8275efd6-b7ea-4f23-b7eb-116c5d6a89ec",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_DR677738_20250920153524_32804.xlsx",
"fileId": "60581a74-11b0-4ef0-b7da-6f8190479880",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_DR677738_20250920153526_19680.xlsx",
"fileId": "97cee51d-e172-4edc-8021-76940c43c91e",
"fileType": "xlsx"
}
]
},
{
"id": "af5f54c2-d207-4c1a-91db-05631c1a6369",
"file_name": "高泰出口货订单表.xlsx",
"booking_number": "MY512871",
"status": "已完成",
"usd": "3.0000",
"instance_id": 679,
"create_by": "test123",
"audit_file": [
{
"fileName": "订舱明细MY51287120250920152648.xlsx",
"fileId": "62bbd275-d902-4b51-b557-6b45ac7d49ad",
"fileType": "xlsx"
}
],
"final_file": [
{
"fileName": "订舱合同_MY512871_20250920152704_47232.xlsx",
"fileId": "ed9a0baa-9962-48d9-bcec-2dfc0d500dd4",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_MY512871_20250920152707_39135.xlsx",
"fileId": "ff01b42c-3fd8-4d02-867b-37a7d16446c3",
"fileType": "xlsx"
},
{
"fileName": "订舱合同_MY512871_20250920152708_52585.xlsx",
"fileId": "681368c2-92c1-4de7-a596-a235214e5e42",
"fileType": "xlsx"
}
]
}
],
"striped": true,
"scrollX": false,
"fullscreen": false,
"operationsButtons": [
{
"label": "修改订舱",
"key": "btn1",
"type": "primary",
"prompt": "prompt:modify_booking()",
"passAllParams": false
},
{
"label": "删除订舱",
"key": "btn2",
"type": "primary",
"prompt": "prompt:delete_booking()",
"passAllParams": false
}
]
}
]
}
}
},
"success": true,
"time": "2025-10-13 11:35:44"
}
}

View File

@ -1,17 +1,17 @@
const { defineConfig } = require('@vue/cli-service')
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
port: 8080,
proxy: {
'/brain': {
target: 'http://47.110.148.47:8090',
"/brain": {
target: "http://47.110.148.47:8090",
changeOrigin: true,
ws: true,
pathRewrite: {
'^/brain': '/brain'
}
}
}
}
})
"^/brain": "/brain",
},
},
},
},
});