Commit 7d82bc09 by yuzhenWang

添加保险产品

parent ea074136
# 页面标题 # 页面标题
VITE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 银盾中台系统
# 开发环境配置 # 开发环境配置
VITE_APP_ENV = 'development' VITE_APP_ENV = 'development'
......
# 页面标题 # 页面标题
VITE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 银盾中台系统
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'production' VITE_APP_ENV = 'production'
...@@ -8,4 +8,4 @@ VITE_APP_ENV = 'production' ...@@ -8,4 +8,4 @@ VITE_APP_ENV = 'production'
VITE_APP_BASE_API = '/prod-api' VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
# 页面标题 # 页面标题
VITE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 银盾中台系统
# 生产环境配置 # 生产环境配置
VITE_APP_ENV = 'staging' VITE_APP_ENV = 'staging'
...@@ -8,4 +8,4 @@ VITE_APP_ENV = 'staging' ...@@ -8,4 +8,4 @@ VITE_APP_ENV = 'staging'
VITE_APP_BASE_API = '/stage-api' VITE_APP_BASE_API = '/stage-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli # 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/companyLogo.png">
<title>若依管理系统</title> <title>银盾中台系统</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style> <style>
html, html,
...@@ -212,4 +212,4 @@ ...@@ -212,4 +212,4 @@
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>
\ No newline at end of file
import request from '@/utils/request'
// 修改上传图片方法,添加正确的请求配置
export function uploadImage(data) {
console.log('data', data)
return request({
url: '/oss/api/oss/upload',
data: data,
method: 'post',
// 添加以下配置
headers: {
'Content-Type': 'multipart/form-data' // 明确指定为multipart类型
},
// 如果使用axios,需要设置这个参数
transformRequest: [
function (data) {
return data
}
]
})
}
// 查询用户是否有访问项目的权限
export function getVisitPermission(projectBizId) {
return request({
url: '/user/api/sysUser/login/permission/project/visit?projectBizId=' + projectBizId,
method: 'get'
})
}
import request from '@/utils/request'
// 查询保险产品列表
export function getInsuranceProductList(data) {
return request({
url: '/insurance/base/api/insuranceProduct/page',
method: 'post',
data: data
})
}
// 编辑产品状态
export function editProductStatus(productBizId, status) {
return request({
url: `/insurance/base/api/insuranceProduct/edit/status?productBizId=${productBizId}&status=${status}`,
method: 'patch'
})
}
// 添加产品
export function addInsuranceProduct(data) {
return request({
url: '/insurance/base/api/insuranceProduct/add',
method: 'post',
data: data
})
}
// 修改产品
export function editInsuranceProduct(data) {
return request({
url: '/insurance/base/api/insuranceProduct/edit',
method: 'put',
data: data
})
}
// 获取产品详情
export function getProductDetail(productBizId) {
return request({
url: `/insurance/base/api/insuranceProduct/detail?productBizId=${productBizId}`,
method: 'get'
})
}
// 查询保险计划产品列表
export function getInsuranceProducPlanList(data) {
return request({
url: '/insurance/base/api/insuranceProductPlan/page',
method: 'post',
data: data
})
}
// 编辑产品计划状态
export function editPlanStatus(planBizId, status) {
return request({
url: `/insurance/base/api/insuranceProductPlan/edit/status?planBizId=${planBizId}&status=${status}`,
method: 'patch'
})
}
// 添加保险计划产品列表
export function addInsuranceProducPlan(data) {
return request({
url: '/insurance/base/api/insuranceProductPlan/add',
method: 'post',
data: data
})
}
// 添加保险计划产品列表
export function getInsuranceProducPlanDetail(planBizId) {
return request({
url: `/insurance/base/api/insuranceProductPlan/detail?planBizId=${planBizId}`,
method: 'get'
})
}
// 修改产品计划
export function editInsuranceProducPlan(data) {
return request({
url: '/insurance/base/api/insuranceProductPlan/edit',
method: 'put',
data: data
})
}
//查询租户保险产品列表
export function getRelTenantInsuranceList(data) {
return request({
url: '/insurance/base/api/relTenantInsuranceProduct/page',
method: 'post',
data: data
})
}
//查询租户保险产品导入列表
export function getImportRelTenantInsuranceList(data) {
return request({
url: '/insurance/base/api/relTenantInsuranceProduct/select/product/page',
method: 'post',
data: data
})
}
//租户导入保险产品
export function addImportInsuranceProductList(data) {
return request({
url: '/insurance/base/api/relTenantInsuranceProduct/add/product/list',
method: 'post',
data: data
})
}
// 删除租户和保险产品关系
export function delInsuranceProduct(id) {
return request({
url: '/insurance/base/api/relTenantInsuranceProduct/del?id=' + id,
method: 'delete'
})
}
// 查询保险附加产品列表
export function getAdditionalProductList(data) {
return request({
url: '/insurance/base/api/insuranceAdditionalProduct/page',
method: 'post',
data: data
})
}
// 添加保险附加产品
export function addInsuranceAdditionalProduct(data) {
return request({
url: '/insurance/base/api/insuranceAdditionalProduct/add',
method: 'post',
data: data
})
}
// 获取附加产品详情
export function getAdditionalProductDetail(additionalProductBizId) {
return request({
url: `/insurance/base/api/insuranceAdditionalProduct/detail?additionalProductBizId=${additionalProductBizId}`,
method: 'get'
})
}
// 编辑附加产品信息
export function editAdditionalProduct(data) {
return request({
url: '/insurance/base/api/insuranceAdditionalProduct/edit',
method: 'put',
data: data
})
}
// 编辑附加产品状态
export function editAdditionalProductStatus(additionalProductBizId, status) {
return request({
url: `/insurance/base/api/insuranceAdditionalProduct/edit/status?additionalProductBizId=${additionalProductBizId}&status=${status}`,
method: 'patch'
})
}
...@@ -18,9 +18,9 @@ export function listDeptExcludeChild(deptId) { ...@@ -18,9 +18,9 @@ export function listDeptExcludeChild(deptId) {
} }
// 查询部门详细 // 查询部门详细
export function getDept(deptId) { export function getDept(deptBizId) {
return request({ return request({
url: '/system/dept/' + deptId, url: '/user/api/sysDept/detail?deptBizId=' + deptBizId,
method: 'get' method: 'get'
}) })
} }
...@@ -28,7 +28,7 @@ export function getDept(deptId) { ...@@ -28,7 +28,7 @@ export function getDept(deptId) {
// 新增部门 // 新增部门
export function addDept(data) { export function addDept(data) {
return request({ return request({
url: '/system/dept', url: '/user/api/sysDept/add',
method: 'post', method: 'post',
data: data data: data
}) })
...@@ -46,7 +46,39 @@ export function updateDept(data) { ...@@ -46,7 +46,39 @@ export function updateDept(data) {
// 删除部门 // 删除部门
export function delDept(deptId) { export function delDept(deptId) {
return request({ return request({
url: '/system/dept/' + deptId, url: '/user/api/sysDept/del?deptBizId=' + deptId,
method: 'delete' method: 'delete'
}) })
} }
\ No newline at end of file
// 编辑部门状态
export function deptStatusChange(deptBizId, status) {
return request({
url: `/user/api/sysDept/edit/status?deptBizId=${deptBizId}&status=${status}`,
method: 'patch'
})
}
// 编辑部门
export function editDept(data) {
return request({
url: '/user/api/sysDept/edit',
method: 'put',
data: data
})
}
//查询部门列表
export function deptList(data) {
return request({
url: '/user/api/sysDept/page',
method: 'post',
data: data
})
}
//获取公司列表
export function getAllCompanys(data) {
return request({
url: '/user/api/sysDept/company/page',
method: 'post',
data: data
})
}
...@@ -2,208 +2,266 @@ import request from '@/utils/request' ...@@ -2,208 +2,266 @@ import request from '@/utils/request'
// 查询租户项目关系列表 // 查询租户项目关系列表
export function listTenantProject(data) { export function listTenantProject(data) {
return request({ return request({
url: '/user/api/relTenantProject/page', url: '/user/api/relTenantProject/page',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 查询导入项目关系列表 // 查询导入项目关系列表
export function listImportTenantProject(data) { export function listImportTenantProject(data) {
return request({ return request({
url: '/user/api/relTenantProject/select/project/page', url: '/user/api/relTenantProject/select/project/page',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 导入项目列表数据提交 // 导入项目列表数据提交
export function addImportTenantProjectList(importTenantProjectListIds, tenantBizId) { export function addImportTenantProjectList(importTenantProjectListIds, tenantBizId) {
const data = { const data = {
projectBizIdList: importTenantProjectListIds, projectBizIdList: importTenantProjectListIds,
tenantBizId: tenantBizId tenantBizId: tenantBizId
} }
return request({ return request({
url: '/user/api/relTenantProject/add/project/list', url: '/user/api/relTenantProject/add/project/list',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 删除租户和项目关系 // 删除租户和项目关系
export function delRelTenantProject(id) { export function delRelTenantProject(id) {
return request({ return request({
url: '/user/api/relTenantProject/del?id=' + id, url: '/user/api/relTenantProject/del?id=' + id,
method: 'delete' method: 'delete'
}) })
} }
// 查询租户用户关系列表 // 查询租户用户关系列表
export function listTenantUser(data) { export function listTenantUser(data) {
return request({ return request({
url: '/user/api/relTenantUser/page', url: '/user/api/relTenantUser/page',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 查询导入用户关系列表 // 查询导入用户关系列表
export function listImportTenantUser(data) { export function listImportTenantUser(data) {
return request({ return request({
url: '/user/api/relTenantUser/select/user/page', url: '/user/api/relTenantUser/select/user/page',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 导入用户列表数据提交 // 导入用户列表数据提交
export function addImportTenantUserList(importTenantUserListIds, tenantBizId) { export function addImportTenantUserList(importTenantUserListIds, tenantBizId) {
const data = { const data = {
userBizIdList: importTenantUserListIds, userBizIdList: importTenantUserListIds,
tenantBizId: tenantBizId tenantBizId: tenantBizId
} }
return request({ return request({
url: '/user/api/relTenantUser/add/user/list', url: '/user/api/relTenantUser/add/user/list',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 删除租户和用户关系 // 删除租户和用户关系
export function delRelTenantUser(id) { export function delRelTenantUser(id) {
return request({ return request({
url: '/user/api/relTenantUser/del?id=' + id, url: '/user/api/relTenantUser/del?id=' + id,
method: 'delete' method: 'delete'
}) })
} }
// 查询租户角色关系列表 // 查询租户角色关系列表
export function listTenantRole(data) { export function listTenantRole(data) {
return request({ return request({
url: '/user/api/relTenantRole/page', url: '/user/api/relTenantRole/page',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 删除租户和角色关系 // 删除租户和角色关系
export function delRelTenantRole(id) { export function delRelTenantRole(id) {
return request({ return request({
url: '/user/api/relTenantRole/del?id=' + id, url: '/user/api/relTenantRole/del?id=' + id,
method: 'delete' method: 'delete'
}) })
} }
// 查询导入角色关系列表 // 查询导入角色关系列表
export function listImportTenantRole(data) { export function listImportTenantRole(data) {
return request({ return request({
url: '/user/api/relTenantRole/select/role/page', url: '/user/api/relTenantRole/select/role/page',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 导入角色列表数据提交 // 导入角色列表数据提交
export function addImportTenantRoleList(importTenantRoleListIds, tenantBizId) { export function addImportTenantRoleList(importTenantRoleListIds, tenantBizId) {
const data = { const data = {
roleBizIdList: importTenantRoleListIds, roleBizIdList: importTenantRoleListIds,
tenantBizId: tenantBizId tenantBizId: tenantBizId
} }
return request({ return request({
url: '/user/api/relTenantRole/add/role/list', url: '/user/api/relTenantRole/add/role/list',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 查询租户和菜单关系列表 // 查询租户和菜单关系列表
export function listMenu(data) { export function listMenu(data) {
return request({ return request({
url: '/user/api/relTenantMenu/page', url: '/user/api/relTenantMenu/page',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 获取租户菜单导入的菜单树 // 获取租户菜单导入的菜单树
export function getMenuTree(tenantBizId) { export function getMenuTree(tenantBizId) {
const data = { const data = {
tenantBizId: tenantBizId tenantBizId: tenantBizId
} }
return request({ return request({
url: '/user/api/relTenantMenu/import/query/menu/tree', url: '/user/api/relTenantMenu/import/query/menu/tree',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 获取租户菜单导入选中的菜单列表 // 获取租户菜单导入选中的菜单列表
export function getImportSelectedMenuList(tenantBizId) { export function getImportSelectedMenuList(tenantBizId) {
return request({ return request({
url: '/user/api/relTenantMenu/import/query/selected/menu/list?tenantBizId=' + tenantBizId, url: '/user/api/relTenantMenu/import/query/selected/menu/list?tenantBizId=' + tenantBizId,
method: 'get' method: 'get'
}) })
} }
// 导入菜单列表数据提交 // 导入菜单列表数据提交
export function addImportTenantMenuList(data) { export function addImportTenantMenuList(data) {
return request({ return request({
url: '/user/api/relTenantMenu/add/menu/list', url: '/user/api/relTenantMenu/add/menu/list',
method: 'post', method: 'post',
data: data data: data
}) })
} }
//分配角色-左侧待选列表 //分配角色-左侧待选列表
export function listLeftRole(data) { export function listLeftRole(data) {
return request({ return request({
url: '/user/api/relUserRole/candidate/tenant/userRolePage', url: '/user/api/relUserRole/candidate/tenant/userRolePage',
method: 'post', method: 'post',
data: data data: data
}) })
} }
//分配角色-右侧已选列表 //分配角色-右侧已选列表
export function listRightRole(data) { export function listRightRole(data) {
return request({ return request({
url: '/user/api/relUserRole/selected/tenant/userRolePage', url: '/user/api/relUserRole/selected/tenant/userRolePage',
method: 'post', method: 'post',
data: data data: data
}) })
} }
//分配角色-左侧待选列表-添加用户角色列表关系 //分配角色-左侧待选列表-添加用户角色列表关系
export function addRightRoleList(data) { export function addRightRoleList(data) {
return request({ return request({
url: '/user/api/relUserRole/add/tenant/userRoleList', url: '/user/api/relUserRole/add/tenant/userRoleList',
method: 'post', method: 'post',
data: data data: data
}) })
} }
//分配角色-右侧已选列表-移除用户角色列表关系 //分配角色-右侧已选列表-移除用户角色列表关系
export function delRightRoleList(data) { export function delRightRoleList(data) {
return request({ return request({
url: '/user/api/relUserRole/del/tenant/userRoleList', url: '/user/api/relUserRole/del/tenant/userRoleList',
method: 'post', method: 'post',
data: data data: data
}) })
} }
//租户和菜单关系树形列表查询 //租户和菜单关系树形列表查询
export function getFpMenuTree(data) { export function getFpMenuTree(data) {
return request({ return request({
url: '/user/api/relTenantMenu/tree', url: '/user/api/relTenantMenu/tree',
method: 'post', method: 'post',
data: data data: data
}) })
} }
//租户和菜单关系树形列表查询 //租户和菜单关系树形列表查询
export function getSelectedFpMenuList(data) { export function getSelectedFpMenuList(data) {
return request({ return request({
url: '/user/api/relRoleMenu/selected/tenant/roleMenuList', url: '/user/api/relRoleMenu/selected/tenant/roleMenuList',
method: 'post', method: 'post',
data: data data: data
}) })
} }
//添加菜单角色列表关系 //添加菜单角色列表关系
export function addFpMenuList(data) { export function addFpMenuList(data) {
return request({ return request({
url: '/user/api/relRoleMenu/add/tenant/roleMenuList', url: '/user/api/relRoleMenu/add/tenant/roleMenuList',
method: 'post', method: 'post',
data: data data: data
}) })
}
//租户-部门树形列表查询
export function deptTreeList(data) {
return request({
url: '/user/api/relTenantDept/tree',
method: 'post',
data: data
})
} }
//租户-部门树形列表查询
export function importGetDeptTreeList(data) {
return request({
url: '/user/api/relTenantDept/import/query/dept/tree',
method: 'post',
data: data
})
}
// 根据部门Id查询已经被勾选的部门
export function getDeptTreeIds(tenantBizId) {
return request({
url: '/user/api/relTenantDept/import/query/selected/dept/list?tenantBizId=' + tenantBizId,
method: 'get'
})
}
//租户-部门树形列表查询
export function addDeptPermissionTreeList(data) {
return request({
url: '/user/api/relTenantDept/add/dept/list',
method: 'post',
data: data
})
}
//租户-部门用户列表查询
export function getDeptUserList(data) {
return request({
url: '/user/api/relDeptUser/tenant/page',
method: 'post',
data: data
})
}
//租户-部门用户导入列表查询
export function getImportDeptUserList(data) {
return request({
url: '/user/api/relDeptUser/tenant/import/page',
method: 'post',
data: data
})
}
//租户-部门用户导入列表添加
export function addImportDeptUserList(data) {
return request({
url: '/user/api/relDeptUser/tenant/import/add',
method: 'post',
data: data
})
}
//租户-部门用户导入列表添加
export function deleteImportDeptUser(data) {
return request({
url: '/user/api/relDeptUser/tenant/del',
method: 'post',
data: data
})
}
...@@ -3,11 +3,15 @@ ...@@ -3,11 +3,15 @@
<template v-for="(item, index) in options"> <template v-for="(item, index) in options">
<template v-if="values.includes(item.value)"> <template v-if="values.includes(item.value)">
<span <span
v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)" v-if="
(item.elTagType == 'default' || item.elTagType == '') &&
(item.elTagClass == '' || item.elTagClass == null)
"
:key="item.value" :key="item.value"
:index="index" :index="index"
:class="item.elTagClass" :class="item.elTagClass"
>{{ item.label + " " }}</span> >{{ item.label + ' ' }}</span
>
<el-tag <el-tag
v-else v-else
:disable-transitions="true" :disable-transitions="true"
...@@ -15,7 +19,8 @@ ...@@ -15,7 +19,8 @@
:index="index" :index="index"
:type="item.elTagType" :type="item.elTagType"
:class="item.elTagClass" :class="item.elTagClass"
>{{ item.label + " " }}</el-tag> >{{ item.label + ' ' }}</el-tag
>
</template> </template>
</template> </template>
<template v-if="unmatch && showValue"> <template v-if="unmatch && showValue">
...@@ -32,30 +37,39 @@ const props = defineProps({ ...@@ -32,30 +37,39 @@ const props = defineProps({
// 数据 // 数据
options: { options: {
type: Array, type: Array,
default: null, default: null
}, },
// 当前的值 // 当前的值
value: [Number, String, Array], value: [Number, String, Array],
// 当未找到匹配的数据时,显示value // 当未找到匹配的数据时,显示value
showValue: { showValue: {
type: Boolean, type: Boolean,
default: true, default: true
}, },
separator: { separator: {
type: String, type: String,
default: ",", default: ','
} }
}) })
const values = computed(() => { const values = computed(() => {
if (props.value === null || typeof props.value === 'undefined' || props.value === '') return [] if (props.value === null || typeof props.value === 'undefined' || props.value === '') return []
return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator) return Array.isArray(props.value)
? props.value.map(item => '' + item)
: String(props.value).split(props.separator)
}) })
const unmatch = computed(() => { const unmatch = computed(() => {
unmatchArray.value = [] unmatchArray.value = []
// 没有value不显示 // 没有value不显示
if (props.value === null || typeof props.value === 'undefined' || props.value === '' || !Array.isArray(props.options) || props.options.length === 0) return false if (
props.value === null ||
typeof props.value === 'undefined' ||
props.value === '' ||
!Array.isArray(props.options) ||
props.options.length === 0
)
return false
// 传入值为数组 // 传入值为数组
let unmatch = false // 添加一个标志来判断是否有未匹配项 let unmatch = false // 添加一个标志来判断是否有未匹配项
values.value.forEach(item => { values.value.forEach(item => {
...@@ -68,9 +82,9 @@ const unmatch = computed(() => { ...@@ -68,9 +82,9 @@ const unmatch = computed(() => {
}) })
function handleArray(array) { function handleArray(array) {
if (array.length === 0) return "" if (array.length === 0) return ''
return array.reduce((pre, cur) => { return array.reduce((pre, cur) => {
return pre + " " + cur return pre + ' ' + cur
}) })
} }
</script> </script>
......
...@@ -28,28 +28,20 @@ ...@@ -28,28 +28,20 @@
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template> </template>
<template v-if="fileType"> <template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> 格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</template> </template>
的文件 的文件
</div> </div>
<el-dialog <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
v-model="dialogVisible" <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
title="预览"
width="800px"
append-to-body
>
<img
:src="dialogImageUrl"
style="display: block; max-width: 100%; margin: 0 auto"
/>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { getToken } from "@/utils/auth" import { getToken } from '@/utils/auth'
import { isExternal } from "@/utils/validate" import { isExternal } from '@/utils/validate'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
const props = defineProps({ const props = defineProps({
...@@ -57,7 +49,7 @@ const props = defineProps({ ...@@ -57,7 +49,7 @@ const props = defineProps({
// 上传接口地址 // 上传接口地址
action: { action: {
type: String, type: String,
default: "/common/upload" default: '/common/upload'
}, },
// 上传携带的参数 // 上传携带的参数
data: { data: {
...@@ -76,7 +68,7 @@ const props = defineProps({ ...@@ -76,7 +68,7 @@ const props = defineProps({
// 文件类型, 例如['png', 'jpg', 'jpeg'] // 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: { fileType: {
type: Array, type: Array,
default: () => ["png", "jpg", "jpeg"] default: () => ['png', 'jpg', 'jpeg']
}, },
// 是否显示提示 // 是否显示提示
isShowTip: { isShowTip: {
...@@ -99,44 +91,46 @@ const { proxy } = getCurrentInstance() ...@@ -99,44 +91,46 @@ const { proxy } = getCurrentInstance()
const emit = defineEmits() const emit = defineEmits()
const number = ref(0) const number = ref(0)
const uploadList = ref([]) const uploadList = ref([])
const dialogImageUrl = ref("") const dialogImageUrl = ref('')
const dialogVisible = ref(false) const dialogVisible = ref(false)
const baseUrl = import.meta.env.VITE_APP_BASE_API const baseUrl = import.meta.env.VITE_APP_BASE_API
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // 上传的图片服务器地址 const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() }) const headers = ref({ Authorization: 'Bearer ' + getToken() })
const fileList = ref([]) const fileList = ref([])
const showTip = computed( const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
() => props.isShowTip && (props.fileType || props.fileSize)
) watch(
() => props.modelValue,
watch(() => props.modelValue, val => { val => {
if (val) { if (val) {
// 首先将值转为数组 // 首先将值转为数组
const list = Array.isArray(val) ? val : props.modelValue.split(",") const list = Array.isArray(val) ? val : props.modelValue.split(',')
// 然后将数组转为对象数组 // 然后将数组转为对象数组
fileList.value = list.map(item => { fileList.value = list.map(item => {
if (typeof item === "string") { if (typeof item === 'string') {
if (item.indexOf(baseUrl) === -1 && !isExternal(item)) { if (item.indexOf(baseUrl) === -1 && !isExternal(item)) {
item = { name: baseUrl + item, url: baseUrl + item } item = { name: baseUrl + item, url: baseUrl + item }
} else { } else {
item = { name: item, url: item } item = { name: item, url: item }
}
} }
} return item
return item })
}) } else {
} else { fileList.value = []
fileList.value = [] return []
return [] }
} },
},{ deep: true, immediate: true }) { deep: true, immediate: true }
)
// 上传前loading加载 // 上传前loading加载
function handleBeforeUpload(file) { function handleBeforeUpload(file) {
let isImg = false let isImg = false
if (props.fileType.length) { if (props.fileType.length) {
let fileExtension = "" let fileExtension = ''
if (file.name.lastIndexOf(".") > -1) { if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1) fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
} }
isImg = props.fileType.some(type => { isImg = props.fileType.some(type => {
if (file.type.indexOf(type) > -1) return true if (file.type.indexOf(type) > -1) return true
...@@ -144,10 +138,10 @@ function handleBeforeUpload(file) { ...@@ -144,10 +138,10 @@ function handleBeforeUpload(file) {
return false return false
}) })
} else { } else {
isImg = file.type.indexOf("image") > -1 isImg = file.type.indexOf('image') > -1
} }
if (!isImg) { if (!isImg) {
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}图片格式文件!`) proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join('/')}图片格式文件!`)
return false return false
} }
if (file.name.includes(',')) { if (file.name.includes(',')) {
...@@ -161,7 +155,7 @@ function handleBeforeUpload(file) { ...@@ -161,7 +155,7 @@ function handleBeforeUpload(file) {
return false return false
} }
} }
proxy.$modal.loading("正在上传图片,请稍候...") proxy.$modal.loading('正在上传图片,请稍候...')
number.value++ number.value++
} }
...@@ -173,7 +167,9 @@ function handleExceed() { ...@@ -173,7 +167,9 @@ function handleExceed() {
// 上传成功回调 // 上传成功回调
function handleUploadSuccess(res, file) { function handleUploadSuccess(res, file) {
if (res.code === 200) { if (res.code === 200) {
uploadList.value.push({ name: res.fileName, url: res.fileName }) console.log('图片上传', res)
uploadList.value.push({ name: res.data.originalName, url: res.data.url })
uploadedSuccessfully() uploadedSuccessfully()
} else { } else {
number.value-- number.value--
...@@ -189,7 +185,7 @@ function handleDelete(file) { ...@@ -189,7 +185,7 @@ function handleDelete(file) {
const findex = fileList.value.map(f => f.name).indexOf(file.name) const findex = fileList.value.map(f => f.name).indexOf(file.name)
if (findex > -1 && uploadList.value.length === number.value) { if (findex > -1 && uploadList.value.length === number.value) {
fileList.value.splice(findex, 1) fileList.value.splice(findex, 1)
emit("update:modelValue", listToString(fileList.value)) emit('update:modelValue', listToString(fileList.value))
return false return false
} }
} }
...@@ -200,14 +196,14 @@ function uploadedSuccessfully() { ...@@ -200,14 +196,14 @@ function uploadedSuccessfully() {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value) fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
uploadList.value = [] uploadList.value = []
number.value = 0 number.value = 0
emit("update:modelValue", listToString(fileList.value)) emit('update:modelValue', listToString(fileList.value))
proxy.$modal.closeLoading() proxy.$modal.closeLoading()
} }
} }
// 上传失败 // 上传失败
function handleUploadError() { function handleUploadError() {
proxy.$modal.msgError("上传图片失败") proxy.$modal.msgError('上传图片失败')
proxy.$modal.closeLoading() proxy.$modal.closeLoading()
} }
...@@ -219,14 +215,14 @@ function handlePictureCardPreview(file) { ...@@ -219,14 +215,14 @@ function handlePictureCardPreview(file) {
// 对象转成指定字符串分隔 // 对象转成指定字符串分隔
function listToString(list, separator) { function listToString(list, separator) {
let strs = "" let strs = ''
separator = separator || "," separator = separator || ','
for (let i in list) { for (let i in list) {
if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) { if (undefined !== list[i].url && list[i].url.indexOf('blob:') !== 0) {
strs += list[i].url.replace(baseUrl, "") + separator strs += list[i].url.replace(baseUrl, '') + separator
} }
} }
return strs != "" ? strs.substr(0, strs.length - 1) : "" return strs != '' ? strs.substr(0, strs.length - 1) : ''
} }
// 初始化拖拽排序 // 初始化拖拽排序
...@@ -235,7 +231,7 @@ onMounted(() => { ...@@ -235,7 +231,7 @@ onMounted(() => {
nextTick(() => { nextTick(() => {
const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list') const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list')
Sortable.create(element, { Sortable.create(element, {
onEnd: (evt) => { onEnd: evt => {
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0] const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
fileList.value.splice(evt.newIndex, 0, movedItem) fileList.value.splice(evt.newIndex, 0, movedItem)
emit('update:modelValue', listToString(fileList.value)) emit('update:modelValue', listToString(fileList.value))
...@@ -249,10 +245,10 @@ onMounted(() => { ...@@ -249,10 +245,10 @@ onMounted(() => {
<style scoped lang="scss"> <style scoped lang="scss">
// .el-upload--picture-card 控制加号部分 // .el-upload--picture-card 控制加号部分
:deep(.hide .el-upload--picture-card) { :deep(.hide .el-upload--picture-card) {
display: none; display: none;
} }
:deep(.el-upload.el-upload--picture-card.is-disabled) { :deep(.el-upload.el-upload--picture-card.is-disabled) {
display: none !important; display: none !important;
} }
</style> </style>
\ No newline at end of file
<template>
<el-select
v-model="selectedValues"
multiple
collapse-tags
collapse-tags-tooltip
:placeholder="placeholder"
@remove-tag="handleRemoveTag"
@clear="handleClear"
>
<el-option :value="optionValue" style="height: auto; padding: 0">
<el-tree
ref="treeRef"
:data="treeData"
:props="treeProps"
:node-key="nodeKey"
:default-expand-all="defaultExpandAll"
:expand-on-click-node="false"
:check-strictly="checkStrictly"
show-checkbox
@check="handleTreeCheck"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span v-if="showIcon && data.icon" :class="data.icon" style="margin-right: 6px"></span>
<span>{{ node.label }}</span>
</span>
</template>
</el-tree>
</el-option>
</el-select>
</template>
<script setup>
import { ref, watch, computed, onMounted } from 'vue'
const props = defineProps({
// 树形数据
treeData: {
type: Array,
default: () => []
},
// 树形配置
treeProps: {
type: Object,
default: () => ({
children: 'children',
label: 'label',
disabled: 'disabled'
})
},
// 节点唯一标识字段
nodeKey: {
type: String,
default: 'id'
},
// 是否默认展开所有节点
defaultExpandAll: {
type: Boolean,
default: false
},
// 是否严格遵守父子不互相关联
checkStrictly: {
type: Boolean,
default: false
},
// 是否显示图标
showIcon: {
type: Boolean,
default: false
},
// 选中的值
modelValue: {
type: Array,
default: () => []
},
// 占位符
placeholder: {
type: String,
default: '请选择'
}
})
const emit = defineEmits(['update:modelValue', 'change'])
const treeRef = ref(null)
const selectedValues = ref([])
const optionValue = ref([]) // 虚拟option值,用于el-select
// 初始化选中值
onMounted(() => {
selectedValues.value = [...props.modelValue]
if (treeRef.value) {
treeRef.value.setCheckedKeys(selectedValues.value)
}
})
// 监听外部modelValue变化
watch(
() => props.modelValue,
newVal => {
if (JSON.stringify(newVal) !== JSON.stringify(selectedValues.value)) {
selectedValues.value = [...newVal]
if (treeRef.value) {
treeRef.value.setCheckedKeys(selectedValues.value)
}
}
}
)
// 处理树节点选中事件
const handleTreeCheck = (node, treeCheckedStatus) => {
const checkedKeys = treeCheckedStatus.checkedKeys
selectedValues.value = checkedKeys
emit('update:modelValue', checkedKeys)
emit('change', checkedKeys)
}
// 处理移除标签事件
const handleRemoveTag = tag => {
if (treeRef.value) {
treeRef.value.setChecked(tag, false)
}
}
// 处理清空事件
const handleClear = () => {
if (treeRef.value) {
treeRef.value.setCheckedKeys([])
}
}
</script>
<style scoped>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
font-size: 14px;
padding-right: 8px;
}
:deep(.el-select .el-select__tags) {
flex-wrap: nowrap;
overflow: hidden;
}
:deep(.el-tree) {
padding: 8px 12px;
max-height: 300px;
overflow-y: auto;
}
:deep(.el-tree-node__content) {
height: 34px;
}
</style>
<template> <template>
<div class="navbar"> <div class="navbar">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" /> id="hamburger-container"
:is-active="appStore.sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb
v-if="!settingsStore.topNav"
id="breadcrumb-container"
class="breadcrumb-container"
/>
<top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" /> <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
<div class="right-menu"> <div class="right-menu">
<!-- 添加租户选择器 --> <!-- 添加租户选择器 -->
<!-- <tenant-select class="right-menu-item hover-effect" v-if="userStore.tenants.length >= 1" />--> <!-- <tenant-select class="right-menu-item hover-effect" v-if="userStore.tenants.length >= 1" />-->
<el-dropdown class="tenant-selector" @command="handleTenantChange"> <el-dropdown class="tenant-selector" @command="handleTenantChange">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
{{ currentTenantName }}<el-icon class="el-icon--right"><arrow-down /></el-icon> {{ currentTenantName }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
...@@ -14,9 +23,9 @@ ...@@ -14,9 +23,9 @@
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item
v-for="tenant in availableTenants" v-for="tenant in availableTenants"
:key="tenant.apiLoginTenantInfoResponse.tenantBizId" :key="tenant.apiLoginTenantInfoResponse.tenantBizId"
:command="tenant" :command="tenant"
> >
{{ tenant.apiLoginTenantInfoResponse.tenantName }} {{ tenant.apiLoginTenantInfoResponse.tenantName }}
</el-dropdown-item> </el-dropdown-item>
...@@ -26,13 +35,13 @@ ...@@ -26,13 +35,13 @@
<template v-if="appStore.device !== 'mobile'"> <template v-if="appStore.device !== 'mobile'">
<header-search id="header-search" class="right-menu-item" /> <header-search id="header-search" class="right-menu-item" />
<el-tooltip content="源码地址" effect="dark" placement="bottom"> <!-- <el-tooltip content="源码地址" effect="dark" placement="bottom">
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" /> <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
</el-tooltip> </el-tooltip> -->
<el-tooltip content="文档地址" effect="dark" placement="bottom"> <!-- <el-tooltip content="文档地址" effect="dark" placement="bottom">
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" /> <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
</el-tooltip> </el-tooltip> -->
<screenfull id="screenfull" class="right-menu-item hover-effect" /> <screenfull id="screenfull" class="right-menu-item hover-effect" />
...@@ -48,23 +57,31 @@ ...@@ -48,23 +57,31 @@
</el-tooltip> </el-tooltip>
</template> </template>
<el-dropdown @command="handleCommand" class="avatar-container right-menu-item hover-effect" trigger="hover"> <el-dropdown
@command="handleCommand"
class="avatar-container right-menu-item hover-effect"
trigger="hover"
>
<div class="avatar-wrapper"> <div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" /> <img :src="userStore.avatar" class="user-avatar" />
<span class="user-nickname"> {{ userStore.nickName }} </span> <span class="user-nickname"> {{ userStore.nickName }} </span>
</div> </div>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<router-link to="/user/profile"> <!-- <router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item> <el-dropdown-item>个人中心</el-dropdown-item>
</router-link> </router-link> -->
<el-dropdown-item divided command="logout"> <el-dropdown-item divided command="logout">
<span>退出登录</span> <span>退出登录</span>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<div class="right-menu-item hover-effect setting" @click="setLayout" v-if="settingsStore.showSettings"> <div
class="right-menu-item hover-effect setting"
@click="setLayout"
v-if="settingsStore.showSettings"
>
<svg-icon icon-class="more-up" /> <svg-icon icon-class="more-up" />
</div> </div>
</div> </div>
...@@ -98,9 +115,10 @@ const availableTenants = computed(() => { ...@@ -98,9 +115,10 @@ const availableTenants = computed(() => {
// 添加空值检查 // 添加空值检查
if (!userStore.tenants) return [] if (!userStore.tenants) return []
return userStore.tenants.filter(tenant => return userStore.tenants.filter(
tenant =>
// (tenant.apiLoginProjectInfoResponseList && tenant.apiLoginProjectInfoResponseList.length > 0) && // (tenant.apiLoginProjectInfoResponseList && tenant.apiLoginProjectInfoResponseList.length > 0) &&
(tenant.apiLoginTenantInfoResponse && tenant.apiLoginTenantInfoResponse.status === 1) tenant.apiLoginTenantInfoResponse && tenant.apiLoginTenantInfoResponse.status === 1
) )
}) })
...@@ -115,7 +133,8 @@ const currentTenantName = computed(() => { ...@@ -115,7 +133,8 @@ const currentTenantName = computed(() => {
}) })
// 切换租户处理 // 切换租户处理
const handleTenantChange = (tenant) => { const handleTenantChange = tenant => {
// 之前的逻辑
userStore.switchTenant(tenant) userStore.switchTenant(tenant)
} }
...@@ -125,10 +144,10 @@ function toggleSideBar() { ...@@ -125,10 +144,10 @@ function toggleSideBar() {
function handleCommand(command) { function handleCommand(command) {
switch (command) { switch (command) {
case "setLayout": case 'setLayout':
setLayout() setLayout()
break break
case "logout": case 'logout':
logout() logout()
break break
default: default:
...@@ -141,11 +160,13 @@ function logout() { ...@@ -141,11 +160,13 @@ function logout() {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(() => { })
userStore.logOut().then(() => { .then(() => {
location.href = '/index' userStore.logOut().then(() => {
location.href = '/workbench'
})
}) })
}).catch(() => { }) .catch(() => {})
} }
const emits = defineEmits(['setLayout']) const emits = defineEmits(['setLayout'])
...@@ -158,7 +179,7 @@ function toggleTheme() { ...@@ -158,7 +179,7 @@ function toggleTheme() {
} }
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
.tenant-selector { .tenant-selector {
margin-left: 20px; margin-left: 20px;
cursor: pointer; cursor: pointer;
...@@ -236,7 +257,7 @@ function toggleTheme() { ...@@ -236,7 +257,7 @@ function toggleTheme() {
svg { svg {
transition: transform 0.3s; transition: transform 0.3s;
&:hover { &:hover {
transform: scale(1.15); transform: scale(1.15);
} }
...@@ -260,7 +281,7 @@ function toggleTheme() { ...@@ -260,7 +281,7 @@ function toggleTheme() {
border-radius: 50%; border-radius: 50%;
} }
.user-nickname{ .user-nickname {
position: relative; position: relative;
left: 5px; left: 5px;
bottom: 10px; bottom: 10px;
...@@ -278,7 +299,7 @@ function toggleTheme() { ...@@ -278,7 +299,7 @@ function toggleTheme() {
} }
} }
//租户选择器样式 //租户选择器样式
//.tenant-select { //.tenant-select {
// margin-right: 10px; // margin-right: 10px;
// padding: 0 10px; // padding: 0 10px;
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
</template> </template>
<script setup> <script setup>
import logo from '@/assets/logo/logo.png' import logo from '@/assets/logo/companyLogo.png'
import useSettingsStore from '@/store/modules/settings' import useSettingsStore from '@/store/modules/settings'
import variables from '@/assets/styles/variables.module.scss' import variables from '@/assets/styles/variables.module.scss'
...@@ -94,4 +94,4 @@ const getLogoTextColor = computed(() => { ...@@ -94,4 +94,4 @@ const getLogoTextColor = computed(() => {
} }
} }
} }
</style> </style>
\ No newline at end of file
...@@ -5,38 +5,39 @@ ...@@ -5,38 +5,39 @@
v-for="tag in visitedViews" v-for="tag in visitedViews"
:key="tag.path" :key="tag.path"
:data-path="tag.path" :data-path="tag.path"
:class="{ 'active': isActive(tag), 'has-icon': tagsIcon }" :class="{ active: isActive(tag), 'has-icon': tagsIcon }"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }" :to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
class="tags-view-item" class="tags-view-item"
:style="activeStyle(tag)" :style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''" @click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@contextmenu.prevent="openMenu(tag, $event)" @contextmenu.prevent="openMenu(tag, $event)"
> >
<svg-icon v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'" :icon-class="tag.meta.icon" /> <svg-icon
v-if="tagsIcon && tag.meta && tag.meta.icon && tag.meta.icon !== '#'"
:icon-class="tag.meta.icon"
/>
{{ tag.title }} {{ tag.title }}
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)"> <span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" /> <close class="el-icon-close" style="width: 1em; height: 1em; vertical-align: middle" />
</span> </span>
</router-link> </router-link>
</scroll-pane> </scroll-pane>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu"> <ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)"> <li @click="refreshSelectedTag(selectedTag)">
<refresh-right style="width: 1em; height: 1em;" /> 刷新页面 <refresh-right style="width: 1em; height: 1em" /> 刷新页面
</li> </li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)"> <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<close style="width: 1em; height: 1em;" /> 关闭当前 <close style="width: 1em; height: 1em" /> 关闭当前
</li>
<li @click="closeOthersTags">
<circle-close style="width: 1em; height: 1em;" /> 关闭其他
</li> </li>
<li @click="closeOthersTags"><circle-close style="width: 1em; height: 1em" /> 关闭其他</li>
<li v-if="!isFirstView()" @click="closeLeftTags"> <li v-if="!isFirstView()" @click="closeLeftTags">
<back style="width: 1em; height: 1em;" /> 关闭左侧 <back style="width: 1em; height: 1em" /> 关闭左侧
</li> </li>
<li v-if="!isLastView()" @click="closeRightTags"> <li v-if="!isLastView()" @click="closeRightTags">
<right style="width: 1em; height: 1em;" /> 关闭右侧 <right style="width: 1em; height: 1em" /> 关闭右侧
</li> </li>
<li @click="closeAllTags(selectedTag)"> <li @click="closeAllTags(selectedTag)">
<circle-close style="width: 1em; height: 1em;" /> 全部关闭 <circle-close style="width: 1em; height: 1em" /> 全部关闭
</li> </li>
</ul> </ul>
</div> </div>
...@@ -64,13 +65,14 @@ const visitedViews = computed(() => useTagsViewStore().visitedViews) ...@@ -64,13 +65,14 @@ const visitedViews = computed(() => useTagsViewStore().visitedViews)
const routes = computed(() => usePermissionStore().routes) const routes = computed(() => usePermissionStore().routes)
const theme = computed(() => useSettingsStore().theme) const theme = computed(() => useSettingsStore().theme)
const tagsIcon = computed(() => useSettingsStore().tagsIcon) const tagsIcon = computed(() => useSettingsStore().tagsIcon)
console.log('visitedViews', visitedViews.value)
watch(route, () => { watch(route, () => {
addTags() addTags()
moveToCurrentTag() moveToCurrentTag()
}) })
watch(visible, (value) => { watch(visible, value => {
if (value) { if (value) {
document.body.addEventListener('click', closeMenu) document.body.addEventListener('click', closeMenu)
} else { } else {
...@@ -90,8 +92,8 @@ function isActive(r) { ...@@ -90,8 +92,8 @@ function isActive(r) {
function activeStyle(tag) { function activeStyle(tag) {
if (!isActive(tag)) return {} if (!isActive(tag)) return {}
return { return {
"background-color": theme.value, 'background-color': theme.value,
"border-color": theme.value 'border-color': theme.value
} }
} }
...@@ -101,7 +103,10 @@ function isAffix(tag) { ...@@ -101,7 +103,10 @@ function isAffix(tag) {
function isFirstView() { function isFirstView() {
try { try {
return selectedTag.value.fullPath === '/index' || selectedTag.value.fullPath === visitedViews.value[1].fullPath return (
selectedTag.value.fullPath === '/index' ||
selectedTag.value.fullPath === visitedViews.value[1].fullPath
)
} catch (err) { } catch (err) {
return false return false
} }
...@@ -143,7 +148,7 @@ function initTags() { ...@@ -143,7 +148,7 @@ function initTags() {
for (const tag of res) { for (const tag of res) {
// Must have tag name // Must have tag name
if (tag.name) { if (tag.name) {
useTagsViewStore().addVisitedView(tag) useTagsViewStore().addVisitedView(tag)
} }
} }
} }
...@@ -201,7 +206,7 @@ function closeLeftTags() { ...@@ -201,7 +206,7 @@ function closeLeftTags() {
} }
function closeOthersTags() { function closeOthersTags() {
router.push(selectedTag.value).catch(() => { }) router.push(selectedTag.value).catch(() => {})
proxy.$tab.closeOtherPage(selectedTag.value).then(() => { proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
moveToCurrentTag() moveToCurrentTag()
}) })
...@@ -265,7 +270,7 @@ function handleScroll() { ...@@ -265,7 +270,7 @@ function handleScroll() {
width: 100%; width: 100%;
background: var(--tags-bg, #fff); background: var(--tags-bg, #fff);
border-bottom: 1px solid var(--tags-item-border, #d8dce5); border-bottom: 1px solid var(--tags-item-border, #d8dce5);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04); box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
.tags-view-wrapper { .tags-view-wrapper {
.tags-view-item { .tags-view-item {
...@@ -324,7 +329,7 @@ function handleScroll() { ...@@ -324,7 +329,7 @@ function handleScroll() {
font-size: 12px; font-size: 12px;
font-weight: 400; font-weight: 400;
color: var(--tags-item-text, #333); color: var(--tags-item-text, #333);
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3); box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
border: 1px solid var(--el-border-color-light, #e4e7ed); border: 1px solid var(--el-border-color-light, #e4e7ed);
li { li {
...@@ -350,11 +355,11 @@ function handleScroll() { ...@@ -350,11 +355,11 @@ function handleScroll() {
vertical-align: 2px; vertical-align: 2px;
border-radius: 50%; border-radius: 50%;
text-align: center; text-align: center;
transition: all .3s cubic-bezier(.645, .045, .355, 1); transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
transform-origin: 100% 50%; transform-origin: 100% 50%;
&:before { &:before {
transform: scale(.6); transform: scale(0.6);
display: inline-block; display: inline-block;
vertical-align: -3px; vertical-align: -3px;
} }
......
...@@ -48,7 +48,7 @@ export const constantRoutes = [ ...@@ -48,7 +48,7 @@ export const constantRoutes = [
hidden: true hidden: true
}, },
{ {
path: "/:pathMatch(.*)*", path: '/:pathMatch(.*)*',
component: () => import('@/views/error/404'), component: () => import('@/views/error/404'),
hidden: true hidden: true
}, },
...@@ -70,7 +70,7 @@ export const constantRoutes = [ ...@@ -70,7 +70,7 @@ export const constantRoutes = [
meta: { title: '工作台', icon: 'dashboard', affix: true } meta: { title: '工作台', icon: 'dashboard', affix: true }
} }
] ]
}, }
// { // {
// path: '', // path: '',
// component: Layout, // component: Layout,
...@@ -198,7 +198,7 @@ const router = createRouter({ ...@@ -198,7 +198,7 @@ const router = createRouter({
return savedPosition return savedPosition
} }
return { top: 0 } return { top: 0 }
}, }
}) })
// 重置路由函数 // 重置路由函数
......
...@@ -9,55 +9,54 @@ import useUserStore from '@/store/modules/user' ...@@ -9,55 +9,54 @@ import useUserStore from '@/store/modules/user'
// 匹配views里面所有的.vue文件 // 匹配views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue') const modules = import.meta.glob('./../../views/**/*.vue')
const usePermissionStore = defineStore( const usePermissionStore = defineStore('permission', {
'permission', state: () => ({
{ routes: [],
state: () => ({ addRoutes: [],
routes: [], defaultRoutes: [],
addRoutes: [], topbarRouters: [],
defaultRoutes: [], sidebarRouters: []
topbarRouters: [], }),
sidebarRouters: [] actions: {
}), setRoutes(routes) {
actions: { this.addRoutes = routes
setRoutes(routes) { this.routes = constantRoutes.concat(routes)
this.addRoutes = routes },
this.routes = constantRoutes.concat(routes) setDefaultRoutes(routes) {
}, this.defaultRoutes = constantRoutes.concat(routes)
setDefaultRoutes(routes) { },
this.defaultRoutes = constantRoutes.concat(routes) setTopbarRoutes(routes) {
}, this.topbarRouters = routes
setTopbarRoutes(routes) { },
this.topbarRouters = routes setSidebarRouters(routes) {
}, this.sidebarRouters = routes
setSidebarRouters(routes) { },
this.sidebarRouters = routes generateRoutes() {
}, return new Promise(resolve => {
generateRoutes() { const userStore = useUserStore()
return new Promise(resolve => { const tenantBizId =
const userStore = useUserStore() userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId || 'tenant_1001'
const tenantBizId = userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId || "tenant_1001" // debugger
getRouters(tenantBizId).then(res => {
getRouters(tenantBizId).then(res => { const sdata = JSON.parse(JSON.stringify(res.data))
const sdata = JSON.parse(JSON.stringify(res.data)) const rdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data)) const defaultData = JSON.parse(JSON.stringify(res.data))
const defaultData = JSON.parse(JSON.stringify(res.data))
const sidebarRoutes = filterAsyncRouter(sdata)
const sidebarRoutes = filterAsyncRouter(sdata) const rewriteRoutes = filterAsyncRouter(rdata, false, true)
const rewriteRoutes = filterAsyncRouter(rdata, false, true) const defaultRoutes = filterAsyncRouter(defaultData)
const defaultRoutes = filterAsyncRouter(defaultData)
this.setRoutes(rewriteRoutes)
this.setRoutes(rewriteRoutes) this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)) this.setDefaultRoutes(sidebarRoutes)
this.setDefaultRoutes(sidebarRoutes) this.setTopbarRoutes(defaultRoutes)
this.setTopbarRoutes(defaultRoutes)
resolve(rewriteRoutes)
resolve(rewriteRoutes)
})
}) })
} })
} }
}) }
})
// export default usePermissionStore // export default usePermissionStore
...@@ -66,28 +65,30 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { ...@@ -66,28 +65,30 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => { return asyncRouterMap.filter(route => {
// 确保路由有有效路径 // 确保路由有有效路径
if (!route.path || route.path.trim() === '') { if (!route.path || route.path.trim() === '') {
console.warn('忽略无效路由项(缺少路径):', route); console.warn('忽略无效路由项(缺少路径):', route)
return false; return false
} }
// 只处理目录(1)和菜单(2)类型 // 只处理目录(1)和菜单(2)类型
if (route.menuType !== 1 && route.menuType !== 2) { if (route.menuType !== 1 && route.menuType !== 2) {
return false; return false
} }
// 创建必要的元信息 // 创建必要的元信息
if (!route.meta) { if (!route.meta) {
route.meta = {}; route.meta = {}
} }
// 设置菜单标题 // 设置菜单标题
route.meta.title = route.menuName; route.meta.title = route.menuName
route.meta.icon = route.icon; route.meta.icon = route.icon
route.meta.isCache = route.isCache === 1; route.meta.isCache = route.isCache === 1
route.name = route.routeName
//确保单子菜单的父菜单也显示 //确保单子菜单的父菜单也显示
if (route.menuType === 1) { if (route.menuType === 1) {
route.alwaysShow = true; // 强制显示父菜单 route.alwaysShow = true // 强制显示父菜单
} }
// 新增:添加标签页显示属性(关键修改) // 新增:添加标签页显示属性(关键修改)
...@@ -96,36 +97,36 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { ...@@ -96,36 +97,36 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
// 确保顶级菜单有布局组件 // 确保顶级菜单有布局组件
if (route.parentBizId === '0' && !route.component) { if (route.parentBizId === '0' && !route.component) {
route.component = 'Layout'; route.component = 'Layout'
} }
// 组件映射处理 // 组件映射处理
if (route.component) { if (route.component) {
if (route.component === 'Layout') { if (route.component === 'Layout') {
route.component = Layout; route.component = Layout
} else if (route.component === 'ParentView') { } else if (route.component === 'ParentView') {
route.component = ParentView; route.component = ParentView
} else if (route.component === 'InnerLink') { } else if (route.component === 'InnerLink') {
route.component = InnerLink; route.component = InnerLink
} else { } else {
route.component = loadView(route.component); route.component = loadView(route.component)
} }
} }
// 递归处理子路由 // 递归处理子路由
if (route.children && route.children.length) { if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, route, type); route.children = filterAsyncRouter(route.children, route, type)
// 设置重定向到第一个子路由(对于目录类型) // 设置重定向到第一个子路由(对于目录类型)
if (route.menuType === 1 && route.children.length > 0) { if (route.menuType === 1 && route.children.length > 0) {
route.redirect = route.children[0].path; route.redirect = route.children[0].path
} }
} else { } else {
delete route.children; delete route.children
} }
return true; return true
}); })
} }
// // 遍历后台传来的路由字符串,转换为组件对象 // // 遍历后台传来的路由字符串,转换为组件对象
// function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { // function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
...@@ -185,7 +186,7 @@ export function filterDynamicRoutes(routes) { ...@@ -185,7 +186,7 @@ export function filterDynamicRoutes(routes) {
return res return res
} }
export const loadView = (view) => { export const loadView = view => {
let res let res
for (const path in modules) { for (const path in modules) {
const dir = path.split('views/')[1].split('.vue')[0] const dir = path.split('views/')[1].split('.vue')[0]
......
import axios from 'axios' import axios from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading } from 'element-plus' import { ElNotification, ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode' import errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/ruoyi' import { tansParams, blobValidate } from '@/utils/ruoyi'
...@@ -21,86 +21,101 @@ const service = axios.create({ ...@@ -21,86 +21,101 @@ const service = axios.create({
}) })
// request拦截器 // request拦截器
service.interceptors.request.use(config => { service.interceptors.request.use(
config => {
// 添加租户ID到请求头 // 添加租户ID到请求头
const userStore = useUserStore() const userStore = useUserStore()
if (userStore.currentTenant) { if (userStore.currentTenant) {
const tenantBizId = userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId const tenantBizId = userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId
config.headers['X-Tenant-ID'] = tenantBizId config.headers['X-Tenant-ID'] = tenantBizId
} }
// 是否需要设置 token // 是否需要设置 token
const isToken = (config.headers || {}).isToken === false const isToken = (config.headers || {}).isToken === false
// 是否需要防止数据重复提交 // 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) { if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params)
url = url.slice(0, -1)
config.params = {}
config.url = url
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
} }
const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小 // get请求映射params参数
const limitSize = 5 * 1024 * 1024 // 限制存放数据5M if (config.method === 'get' && config.params) {
if (requestSize >= limitSize) { let url = config.url + '?' + tansParams(config.params)
console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') url = url.slice(0, -1)
return config config.params = {}
config.url = url
} }
const sessionObj = cache.session.getJSON('sessionObj') if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
if (sessionObj === undefined || sessionObj === null || sessionObj === '') { const requestObj = {
cache.session.setJSON('sessionObj', requestObj) url: config.url,
} else { data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
const s_url = sessionObj.url // 请求地址 time: new Date().getTime()
const s_data = sessionObj.data // 请求数据 }
const s_time = sessionObj.time // 请求时间 const requestSize = Object.keys(JSON.stringify(requestObj)).length // 请求数据大小
const interval = 1000 // 间隔时间(ms),小于此时间视为重复提交 const limitSize = 5 * 1024 * 1024 // 限制存放数据5M
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { if (requestSize >= limitSize) {
const message = '数据正在处理,请勿重复提交' console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')
console.warn(`[${s_url}]: ` + message) return config
return Promise.reject(new Error(message)) }
} else { const sessionObj = cache.session.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj) cache.session.setJSON('sessionObj', requestObj)
} else {
// const s_url = sessionObj.url // 请求地址
// const s_data = sessionObj.data // 请求数据
// const s_time = sessionObj.time // 请求时间
// const interval = 5000 // 间隔时间(ms),小于此时间视为重复提交
// if (
// s_data === requestObj.data &&
// requestObj.time - s_time < interval &&
// s_url === requestObj.url
// ) {
// const message = '数据正在处理,请勿重复提交'
// console.warn(`[${s_url}]: ` + message)
// return Promise.reject(new Error(message))
// } else {
// cache.session.setJSON('sessionObj', requestObj)
// }
} }
} }
} return config
return config },
}, error => { error => {
console.log(error) console.log(error)
Promise.reject(error) Promise.reject(error)
}) }
)
// 响应拦截器 // 响应拦截器
service.interceptors.response.use(res => { service.interceptors.response.use(
res => {
// 未设置状态码则默认成功状态 // 未设置状态码则默认成功状态
const code = res.data.code || 200 const code = res.data.code || 200
// 获取错误信息 // 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default'] const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回 // 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data return res.data
} }
if (code === 401) { if (code === 401) {
if (!isRelogin.show) { if (!isRelogin.show) {
isRelogin.show = true isRelogin.show = true
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
isRelogin.show = false confirmButtonText: '重新登录',
useUserStore().logOut().then(() => { cancelButtonText: '取消',
location.href = '/index' type: 'warning'
})
.then(() => {
isRelogin.show = false
useUserStore()
.logOut()
.then(() => {
location.href = '/index'
})
}) })
}).catch(() => { .catch(() => {
isRelogin.show = false isRelogin.show = false
}) })
} }
return Promise.reject('无效的会话,或者会话已过期,请重新登录。') return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) { } else if (code === 500) {
ElMessage({ message: msg, type: 'error' }) ElMessage({ message: msg, type: 'error' })
...@@ -112,18 +127,18 @@ service.interceptors.response.use(res => { ...@@ -112,18 +127,18 @@ service.interceptors.response.use(res => {
ElNotification.error({ title: msg }) ElNotification.error({ title: msg })
return Promise.reject('error') return Promise.reject('error')
} else { } else {
return Promise.resolve(res.data) return Promise.resolve(res.data)
} }
}, },
error => { error => {
console.log('err' + error) console.log('err' + error)
let { message } = error let { message } = error
if (message == "Network Error") { if (message == 'Network Error') {
message = "后端接口连接异常" message = '后端接口连接异常'
} else if (message.includes("timeout")) { } else if (message.includes('timeout')) {
message = "系统接口请求超时" message = '系统接口请求超时'
} else if (message.includes("Request failed with status code")) { } else if (message.includes('Request failed with status code')) {
message = "系统接口" + message.substr(message.length - 3) + "异常" message = '系统接口' + message.substr(message.length - 3) + '异常'
} }
ElMessage({ message: message, type: 'error', duration: 5 * 1000 }) ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error) return Promise.reject(error)
...@@ -132,29 +147,39 @@ service.interceptors.response.use(res => { ...@@ -132,29 +147,39 @@ service.interceptors.response.use(res => {
// 通用下载方法 // 通用下载方法
export function download(url, params, filename, config) { export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", }) downloadLoadingInstance = ElLoading.service({
return service.post(url, params, { text: '正在下载数据,请稍候',
transformRequest: [(params) => { return tansParams(params) }], background: 'rgba(0, 0, 0, 0.7)'
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
const isBlob = blobValidate(data)
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text()
const rspObj = JSON.parse(resText)
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg)
}
downloadLoadingInstance.close()
}).catch((r) => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close()
}) })
return service
.post(url, params, {
transformRequest: [
params => {
return tansParams(params)
}
],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
})
.then(async data => {
const isBlob = blobValidate(data)
if (isBlob) {
const blob = new Blob([data])
saveAs(blob, filename)
} else {
const resText = await data.text()
const rspObj = JSON.parse(resText)
const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
ElMessage.error(errMsg)
}
downloadLoadingInstance.close()
})
.catch(r => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close()
})
} }
export default service export default service
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
v-show="showSearch"
:inline="true"
label-width="100px"
>
<el-form-item label="附加产品名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入附加产品名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="附加产品状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择" clearable style="width: 240px">
<el-option
v-for="dict in bx_product_status"
:key="dict.value"
:label="dict.label"
:value="Number(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="loading" :data="insuranceList">
<el-table-column label="产品类型" prop="productType" width="150" align="left" fixed="left">
<template #default="scope">
<dict-tag :options="bx_product_type" :value="scope.row.productType" />
</template>
</el-table-column>
<el-table-column label="产品图片" align="center" prop="picture" width="150">
<template #default="scope">
<!-- :preview-src-list="[scope.row.logoUrl]" -->
<el-image
v-if="scope.row.picture"
:src="scope.row.picture"
class="logo-image"
fit="cover"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<span v-else></span>
</template>
</el-table-column>
<el-table-column
label="产品名称"
prop="productName"
:show-overflow-tooltip="true"
width="150"
align="left"
/>
<el-table-column label="附加产品状态" prop="productStatus" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_product_status" :value="scope.row.productStatus" />
</template>
</el-table-column>
<el-table-column label="货币类型" prop="currency" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_currency_type" :value="scope.row.currency" />
</template>
</el-table-column>
<el-table-column
label="供款年期(年)"
width="150"
align="left"
prop="paymentTerm"
></el-table-column>
<el-table-column
label="受保年龄范围(岁)"
prop="insuredAgeRange"
:show-overflow-tooltip="true"
width="150"
align="left"
/>
<el-table-column
label="基础费率(%)"
prop="premiumRate"
:show-overflow-tooltip="true"
width="150"
/>
<el-table-column label="区分吸烟" prop="smokingAllowed" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_smoking_allowed" :value="scope.row.smokingAllowed" />
</template>
</el-table-column>
<el-table-column
label="保障内容"
prop="coverageContent"
:show-overflow-tooltip="true"
width="200"
align="left"
/>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="250"
fixed="right"
>
<template #default="scope">
<div style="display: flex; align-items: center">
<el-button
link
type="primary"
icon="Edit"
style="margin-right: 10px"
@click="handleUpdate(scope.row)"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>修改</el-button
>
<el-dropdown
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
@command="handleStatusCommand"
>
<el-button link type="primary" icon="Edit"> 附加产品状态 </el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="{ action: '在售', row: scope.row }"
>在售</el-dropdown-item
>
<el-dropdown-item :command="{ action: '下架', row: scope.row }"
>下架</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- <el-button
link
type="primary"
icon="Delete"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>删除</el-button
> -->
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改产品配置对话框 -->
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
<el-form ref="roleRef" :model="form" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="form.productName" placeholder="请输入产品名称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品类型" prop="productType">
<el-select v-model="form.productType" placeholder="请选择">
<el-option
v-for="item in bx_product_type"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="货币" prop="currency">
<el-select v-model="form.currency" placeholder="请选择">
<el-option
v-for="item in bx_currency_type"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="基础费率" prop="premiumRate">
<div class="years">
<el-input
placeholder="请输入"
v-model="form.premiumRate"
type="number"
min="0"
max="100"
@input="handlePremiumRateInput"
/>
<div class="hao">%</div>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否区分吸烟" prop="smokingAllowed">
<el-select v-model="form.smokingAllowed" placeholder="请选择">
<el-option
v-for="item in bx_smoking_allowed"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="附加产品状态" prop="productStatus">
<el-select v-model="form.productStatus" placeholder="请选择">
<el-option
v-for="item in bx_product_status"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<div class="timeRange">
<el-form-item label="受保年龄范围" prop="startAge" style="width: 120%">
<el-input
placeholder="起保年龄"
v-model="form.startAge"
type="number"
min="0"
oninput="value=value.replace(/[^0-9]/g,'')"
/>
</el-form-item>
<div class="line">-</div>
<el-form-item label="" prop="endAge">
<el-input
placeholder="终保年龄"
v-model="form.endAge"
type="number"
min="0"
oninput="value=value.replace(/[^0-9]/g,'')"
/>
</el-form-item>
</div>
</el-col>
<el-col :span="12">
<el-form-item label="所属保险公司" prop="ssDeptBizIdList">
<el-tooltip
:disabled="!showSsDeptTooltip"
placement="top"
:content="ssDeptSelectedLabels"
>
<el-select
v-model="form.ssDeptBizIdList"
multiple
collapse-tags
:collapse-tags-tooltip="false"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索公司"
:remote-method="searchCompanys"
:loading="companyLoading"
@mouseenter.native="handleSelectMouseEnter('ssDept')"
@mouseleave.native="handleSelectMouseLeave('ssDept')"
>
<el-option
v-for="item in companyOptions"
:key="item.deptBizId"
:label="item.deptName"
:value="item.deptBizId"
/>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出单公司" prop="cdDeptBizIdList">
<el-tooltip
:disabled="!showCdDeptTooltip"
placement="top"
:content="cdDeptSelectedLabels"
>
<el-select
v-model="form.cdDeptBizIdList"
multiple
collapse-tags
:collapse-tags-tooltip="false"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索公司"
:remote-method="searchCompanys"
:loading="companyLoading"
@mouseenter.native="handleSelectMouseEnter('cdDept')"
@mouseleave.native="handleSelectMouseLeave('cdDept')"
>
<el-option
v-for="item in companyOptions"
:key="item.deptBizId"
:label="item.deptName"
:value="item.deptBizId"
/>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属公司所在地" prop="location">
<el-input
v-model="form.location"
type="text"
placeholder="请输入公司所在地"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="保障内容" prop="coverageContent">
<el-input
v-model="form.coverageContent"
type="textarea"
placeholder="请输入保障内容"
></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item
label="产品图片"
prop="picture"
:rules="[{ required: true, message: '请上传产品图片', trigger: 'change' }]"
>
<image-upload
v-model="form.picture"
:action="'/oss/api/oss/upload'"
:limit="1"
:file-size="5"
:file-type="['png', 'jpg', 'jpeg']"
:is-show-tip="true"
@change="handleImageChange"
/>
</el-form-item>
</el-col>
<el-col :span="24" v-for="(item, index) in form.tempTermList" :key="item.id">
<el-form-item
:label="`供款年期${index + 1}`"
:prop="`tempTermList.${index}.paymentTerm`"
:rules="[{ required: true, message: '供款年期不能为空', trigger: 'blur' }]"
>
<div class="termBox">
<el-input
v-model="item.paymentTerm"
placeholder="请输入年期"
style="margin-right: 10px"
></el-input>
<el-button
circle
icon="plus"
@click="addPaymentTerm"
v-if="index === form.tempTermList.length - 1"
/>
<el-button circle icon="delete" @click="removePaymentTerm(index)" />
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="AdditionalProduct">
import { delRole, searchScopeList } from '@/api/system/role'
import {
getAdditionalProductList,
editAdditionalProductStatus,
addInsuranceAdditionalProduct,
getInsuranceProducPlanList,
editAdditionalProduct,
getAdditionalProductDetail
} from '@/api/insurance/index'
import { getAllCompanys } from '@/api/system/dept'
import useUserStore from '@/store/modules/user'
import { computed, ref } from 'vue'
import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组件
const userStore = useUserStore()
const router = useRouter()
const { proxy } = getCurrentInstance()
const { bx_product_type, sys_scope, bx_product_status, bx_smoking_allowed, bx_currency_type } =
proxy.useDict(
'bx_product_type',
'sys_scope',
'bx_product_status',
'bx_smoking_allowed',
'bx_currency_type'
)
const insuranceList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const total = ref(0)
const title = ref('')
const dateRange = ref([])
const companyLoading = ref(false)
const companyOptions = ref([])
const planLoading = ref(false)
const planOptions = ref([])
//在data中添加一个用于跟踪图片上传状态的变量
const imageUploaded = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
productName: undefined,
status: undefined
},
rules: {
productName: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
scope: [{ required: true, message: '作用域不能为空', trigger: 'blur' }],
productType: [{ required: true, message: '产品类型不能为空', trigger: 'blur' }],
currency: [{ required: true, message: '货币不能为空', trigger: 'blur' }],
premiumRate: [
{ required: true, message: '基础费率不能为空', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === '' || value === null) {
callback(new Error('基础费率不能为空'))
return
}
const numValue = Number(value)
if (isNaN(numValue)) {
callback(new Error('请输入有效的数字'))
} else if (numValue < 0) {
callback(new Error('基础费率不能小于0'))
} else if (numValue > 100) {
callback(new Error('基础费率不能大于100'))
} else {
callback()
}
},
trigger: 'blur'
}
],
smokingAllowed: [{ required: true, message: '是否区分吸烟不能为空', trigger: 'blur' }],
productStatus: [{ required: true, message: '产品状态不能为空', trigger: 'blur' }],
startAge: [
{ required: true, message: '起保年龄不能为空', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!Number.isInteger(Number(value)) || value <= 0) {
callback(new Error('请输入正整数'))
} else {
callback()
}
},
trigger: 'blur'
}
],
endAge: [
{ required: true, message: '终保年龄不能为空', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!Number.isInteger(Number(value)) || value <= 0) {
callback(new Error('请输入正整数'))
} else if (Number(value) <= Number(form.value.startAge)) {
callback(new Error('终保必须大于起保'))
} else {
callback()
}
},
trigger: 'blur'
}
],
tenantBizId: [{ required: true, message: '所属租户不能为空', trigger: 'blur' }],
ssDeptBizIdList: [
{
required: true,
message: '所属保险公司不能为空',
trigger: 'change',
validator: (rule, value, callback) => {
if (!value || value.length === 0) {
callback(new Error('请至少选择一个所属保险公司'))
} else {
callback()
}
}
}
],
picture: [
{
required: true,
validator: (rule, value, callback) => {
if (!value || value.trim() === '') {
callback(new Error('请上传产品图片'))
} else {
callback()
}
},
trigger: 'change'
}
]
}
})
const { queryParams, form, rules } = toRefs(data)
const filteredScopeOptions = computed(() => {
if (!sys_scope.value || !Array.isArray(sys_scope.value)) return []
return sys_scope.value.filter(option => {
// 添加安全判断
if (!option || typeof option.value === 'undefined') return false
return Number(option.value) !== 3
})
})
const handlePremiumRateInput = value => {
// 限制输入范围为0-100
if (value > 100) {
form.value.premiumRate = 100
} else if (value < 0) {
form.value.premiumRate = 0
}
// 限制小数位数为2位
if (value && value.toString().includes('.')) {
const parts = value.toString().split('.')
if (parts[1].length > 2) {
form.value.premiumRate = Number(value).toFixed(2)
}
}
}
const handleStatusCommand = command => {
const { row, action } = command
const newStatus = row.productStatus === 1 ? 0 : 1 // 获取切换后的新状态
proxy.$modal
.confirm(`确认要${action}"${row.productName}"吗?`)
.then(function () {
return editAdditionalProductStatus(row.additionalProductBizId, newStatus).then(() => {
proxy.$modal.msgSuccess(`${row.productName}${action}成功`)
getList()
})
})
.catch(() => {})
}
/** 查询角色列表 */
function getList() {
loading.value = true
getAdditionalProductList(queryParams.value).then(response => {
if (response.code === 200) {
insuranceList.value = response.data.records
total.value = response.data.total
loading.value = false
} else {
loading.value = false
proxy.$modal.msgError(response.msg)
}
})
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
proxy.resetForm('queryRef')
handleQuery()
}
/** 删除按钮操作 */
function handleDelete(row) {
const roleIds = row.roleId || ids.value
proxy.$modal
.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?')
.then(function () {
return delRole(roleIds)
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 重置新增的表单以及其他数据 */
function reset() {
form.value = {
tempTermList: [],
ssDeptBizIdList: [],
cdDeptBizIdList: [],
planBizIdList: []
}
proxy.resetForm('roleRef')
}
/** 添加角色 */
function handleAdd() {
form.value = {
tempTermList: [],
ssDeptBizIdList: [],
cdDeptBizIdList: [],
planBizIdList: []
}
form.value.tempTermList.push({ id: 1 })
searchCompanys('') //搜索公司
// searchPlanProduct('') //搜索计划产品
open.value = true
title.value = '添加产品'
}
/** 修改产品 */
function handleUpdate(row) {
reset()
searchCompanys('') //搜索公司
// searchPlanProduct('') //搜索计划产品
const additionalProductBizId = row.additionalProductBizId
getAdditionalProductDetail(additionalProductBizId).then(response => {
if (response.code === 200) {
console.log('response.data', response.data)
let result = response.data
form.value.tempTermList = result.paymentTermList.map((item, index) => {
return {
id: index + 1,
paymentTerm: item
}
})
form.value.ssDeptBizIdList = result.ssDeptDtoList.map(item => item.deptBizId)
form.value.cdDeptBizIdList = result.cdDeptDtoList.map(item => item.deptBizId)
// form.value.planBizIdList = result.planDtoList.map(item => item.planBizId)
form.value.startAge = result.insuredAgeRange.split('-')[0]
form.value.endAge = result.insuredAgeRange.split('-')[1]
form.value = Object.assign({}, form.value, result)
open.value = true
title.value = '修改产品'
} else {
proxy.$modal.msgError(response.msg)
}
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs['roleRef'].validate(valid => {
if (valid) {
let newTermList = JSON.parse(JSON.stringify(form.value.tempTermList))
form.value.paymentTermList = newTermList.map(item => item.paymentTerm)
form.value.insuredAgeRange = `${form.value.startAge}-${form.value.endAge}`
if (form.value.additionalProductBizId) {
editAdditionalProduct(form.value).then(response => {
if (response.code === 200) {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
} else {
proxy.$modal.msgError(response.msg)
}
})
} else {
addInsuranceAdditionalProduct(form.value).then(response => {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
})
}
}
})
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
getList()
//========作用域-切换逻辑开始=========
const tenantOptions = ref([])
const tenantLoading = ref(false)
const projectOptions = ref([])
const projectLoading = ref(false)
// 作用域变更处理
function handleScopeChange(val) {
console.log('作用域变更:', val)
// 清空不需要的字段值
if (val === 2) {
form.value.projectBizId = ''
// 自动加载租户列表(可选)
searchTenants('')
} else if (val === 3) {
form.value.tenantBizId = ''
// 自动加载项目列表(可选)
searchProjects('')
} else if (val === 1) {
//系统级
form.value.tenantBizId = ''
form.value.projectBizId = ''
}
}
// 搜索租户方法
function searchTenants(query) {
tenantLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 2,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
tenantOptions.value = response.data.records
})
} catch (error) {
console.error('租户搜索失败', error)
tenantOptions.value = []
} finally {
tenantLoading.value = false
}
}
// 搜索项目方法
function searchProjects(query) {
projectLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 3,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
projectOptions.value = response.data.records
})
} catch (error) {
console.error('项目搜索失败', error)
projectOptions.value = []
} finally {
projectLoading.value = false
}
}
//========作用域-切换逻辑结束=========
// 新增供款年期
const addPaymentTerm = () => {
form.value.tempTermList.push({
id: form.value.tempTermList.length + 1
})
}
// 删除供款年期
const removePaymentTerm = index => {
if (form.value.tempTermList.length == 1) {
proxy.$modal.msgError('供款年期不能为空')
return
}
form.value.tempTermList.splice(index, 1)
}
// 搜索公司方法
const searchCompanys = query => {
companyLoading.value = true
try {
const params = {
deptName: query,
pageNo: 1,
pageSize: 10
}
getAllCompanys(params).then(response => {
companyOptions.value = response.data.records
})
} catch (error) {
console.error('公司搜索失败', error)
companyOptions.value = []
} finally {
companyLoading.value = false
}
}
// 搜索计划产品
const searchPlanProduct = query => {
planLoading.value = true
try {
const params = {
planName: query,
pageNo: 1,
pageSize: 10
}
getInsuranceProducPlanList(params).then(response => {
planOptions.value = response.data.records
})
} catch (error) {
console.error('公司搜索失败', error)
planOptions.value = []
} finally {
planLoading.value = false
}
}
//========多选下拉框悬停效果开始=========
// 新增响应式数据
const showSsDeptTooltip = ref(false)
const showCdDeptTooltip = ref(false)
const showPlanTooltip = ref(false)
// 计算选中项的标签文本
const ssDeptSelectedLabels = computed(() => {
return form.value.ssDeptBizIdList
.map(id => {
const item = companyOptions.value.find(opt => opt.deptBizId === id)
return item ? item.deptName : id
})
.join('、')
})
const cdDeptSelectedLabels = computed(() => {
return form.value.cdDeptBizIdList
.map(id => {
const item = companyOptions.value.find(opt => opt.deptBizId === id)
return item ? item.deptName : id
})
.join('、')
})
const planSelectedLabels = computed(() => {
return form.value.planBizIdList
.map(id => {
const item = planOptions.value.find(opt => opt.planBizId === id)
return item ? item.planName : id
})
.join('、')
})
// 处理鼠标进入select
const handleSelectMouseEnter = type => {
// 判断是否有折叠的标签(选中数量超过显示数量)
let hasCollapsedTags = false
switch (type) {
case 'ssDept':
hasCollapsedTags = form.value.ssDeptBizIdList.length > 0
break
case 'cdDept':
hasCollapsedTags = form.value.cdDeptBizIdList.length > 0
break
case 'plan':
hasCollapsedTags = form.value.planBizIdList.length > 0
break
}
if (hasCollapsedTags) {
switch (type) {
case 'ssDept':
showSsDeptTooltip.value = true
break
case 'cdDept':
showCdDeptTooltip.value = true
break
case 'plan':
showPlanTooltip.value = true
break
}
}
}
// 处理鼠标离开select
const handleSelectMouseLeave = type => {
switch (type) {
case 'ssDept':
showSsDeptTooltip.value = false
break
case 'cdDept':
showCdDeptTooltip.value = false
break
case 'plan':
showPlanTooltip.value = false
break
}
}
//========多选下拉框悬停效果结束=========
// 添加图片变更处理函数
function handleImageChange(value) {
imageUploaded.value = !!value
// 手动触发表单验证
if (proxy.$refs.form) {
proxy.$refs.form.validateField('picture')
}
}
</script>
<style lang="scss" scoped>
.logo-image {
width: 70px;
height: 70px;
border-radius: 4px;
object-fit: cover;
}
.years {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.line {
margin: 0 10px;
}
}
.termBox {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.timeRange {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.line {
margin: 0 10px;
}
}
.timeRange ::v-deep .el-form-item--default .el-form-item__content {
margin-left: 0 !important;
}
</style>
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
v-show="showSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="产品名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入产品名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="产品状态" prop="productStatus">
<el-select
v-model="queryParams.productStatus"
placeholder="请选择"
clearable
style="width: 240px"
>
<el-option
v-for="dict in bx_product_status"
:key="dict.value"
:label="dict.label"
:value="Number(dict.value)"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="loading" :data="insuranceList">
<el-table-column label="产品类型" prop="productType" width="150" align="left" fixed="left">
<template #default="scope">
<dict-tag :options="bx_product_type" :value="scope.row.productType" />
</template>
</el-table-column>
<el-table-column label="产品图片" align="center" prop="picture" width="150">
<template #default="scope">
<!-- :preview-src-list="[scope.row.logoUrl]" -->
<el-image
v-if="scope.row.picture"
:src="scope.row.picture"
class="logo-image"
fit="cover"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<span v-else></span>
</template>
</el-table-column>
<el-table-column
label="所属租户名称"
prop="tenantName"
:show-overflow-tooltip="true"
width="150"
/>
<el-table-column
label="所属项目名称"
prop="projectName"
:show-overflow-tooltip="true"
width="150"
/>
<el-table-column
label="产品名称"
prop="productName"
:show-overflow-tooltip="true"
width="150"
align="left"
/>
<el-table-column label="产品状态" prop="productStatus" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_product_status" :value="scope.row.productStatus" />
</template>
</el-table-column>
<el-table-column label="作用域" prop="scope" width="150" align="left">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column label="货币类型" prop="currency" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_currency_type" :value="scope.row.currency" />
</template>
</el-table-column>
<el-table-column
label="供款年期(年)"
width="150"
align="left"
prop="paymentTerm"
></el-table-column>
<el-table-column
label="受保年龄范围(岁)"
prop="insuredAgeRange"
:show-overflow-tooltip="true"
width="150"
align="left"
/>
<el-table-column
label="基础费率(%)"
prop="premiumRate"
:show-overflow-tooltip="true"
width="150"
/>
<el-table-column label="区分吸烟" prop="smokingAllowed" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_smoking_allowed" :value="scope.row.smokingAllowed" />
</template>
</el-table-column>
<el-table-column
label="保障内容"
prop="coverageContent"
:show-overflow-tooltip="true"
width="200"
align="left"
/>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="200"
fixed="right"
>
<template #default="scope">
<div style="display: flex; align-items: center">
<el-button
link
type="primary"
icon="Edit"
style="margin-right: 10px"
@click="handleUpdate(scope.row)"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>修改</el-button
>
<el-dropdown
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
@command="handleStatusCommand"
>
<el-button link type="primary" icon="Edit"> 产品状态 </el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="{ action: '在售', row: scope.row }"
>在售</el-dropdown-item
>
<el-dropdown-item :command="{ action: '下架', row: scope.row }"
>下架</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- <el-button
link
type="primary"
icon="Delete"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>删除</el-button
> -->
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改产品配置对话框 -->
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
<el-form ref="roleRef" :model="form" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="form.productName" placeholder="请输入产品名称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="12">
<!-- 作用域选择 -->
<el-form-item label="作用域" prop="scope">
<el-select v-model="form.scope" placeholder="请输入" @change="handleScopeChange">
<el-option
v-for="item in filteredScopeOptions"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" v-if="[2].includes(form.scope) || [3].includes(form.scope)">
<el-form-item v-if="[2].includes(form.scope)" label="所属租户" prop="tenantBizId">
<template #label>
<span>
<el-tooltip content="所属租户" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
所属租户
</span>
</template>
<el-select
v-model="form.tenantBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索租户"
:remote-method="searchTenants"
:loading="tenantLoading"
>
<el-option
v-for="item in tenantOptions"
:key="item.tenantBizId"
:label="item.tenantName"
:value="item.tenantBizId"
/>
</el-select>
</el-form-item>
<el-form-item v-if="[3].includes(form.scope)" label="所属项目" prop="projectBizId">
<template #label>
<span>
<el-tooltip content="所属项目" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
所属项目
</span>
</template>
<el-select
v-model="form.projectBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索项目"
:remote-method="searchProjects"
:loading="projectLoading"
>
<el-option
v-for="item in projectOptions"
:key="item.projectBizId"
:label="item.projectName"
:value="item.projectBizId"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品类型" prop="productType">
<el-select v-model="form.productType" placeholder="请选择">
<el-option
v-for="item in bx_product_type"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="货币" prop="currency">
<el-select v-model="form.currency" placeholder="请选择">
<el-option
v-for="item in bx_currency_type"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="基础费率" prop="premiumRate">
<div class="years">
<el-input
placeholder="请输入"
v-model="form.premiumRate"
type="number"
min="0"
max="100"
@input="handlePremiumRateInput"
/>
<div class="hao">%</div>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否区分吸烟" prop="smokingAllowed">
<el-select v-model="form.smokingAllowed" placeholder="请选择">
<el-option
v-for="item in bx_smoking_allowed"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品状态" prop="productStatus">
<el-select v-model="form.productStatus" placeholder="请选择">
<el-option
v-for="item in bx_product_status"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<div class="timeRange">
<el-form-item label="受保年龄范围" prop="startAge" style="width: 120%">
<el-input
placeholder="起保年龄"
v-model="form.startAge"
type="number"
min="0"
oninput="value=value.replace(/[^0-9]/g,'')"
/>
</el-form-item>
<div class="line">-</div>
<el-form-item label="" prop="endAge">
<el-input
placeholder="终保年龄"
v-model="form.endAge"
type="number"
min="0"
oninput="value=value.replace(/[^0-9]/g,'')"
/>
</el-form-item>
</div>
</el-col>
<el-col :span="12">
<el-form-item label="所属保险公司" prop="ssDeptBizIdList">
<el-tooltip
:disabled="!showSsDeptTooltip"
placement="top"
:content="ssDeptSelectedLabels"
>
<el-select
v-model="form.ssDeptBizIdList"
multiple
collapse-tags
:collapse-tags-tooltip="false"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索公司"
:remote-method="searchCompanys"
:loading="companyLoading"
@mouseenter.native="handleSelectMouseEnter('ssDept')"
@mouseleave.native="handleSelectMouseLeave('ssDept')"
>
<el-option
v-for="item in companyOptions"
:key="item.deptBizId"
:label="item.deptName"
:value="item.deptBizId"
/>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出单公司" prop="cdDeptBizIdList">
<el-tooltip
:disabled="!showCdDeptTooltip"
placement="top"
:content="cdDeptSelectedLabels"
>
<el-select
v-model="form.cdDeptBizIdList"
multiple
collapse-tags
:collapse-tags-tooltip="false"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索公司"
:remote-method="searchCompanys"
:loading="companyLoading"
@mouseenter.native="handleSelectMouseEnter('cdDept')"
@mouseleave.native="handleSelectMouseLeave('cdDept')"
>
<el-option
v-for="item in companyOptions"
:key="item.deptBizId"
:label="item.deptName"
:value="item.deptBizId"
/>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品计划" prop="planBizIdList">
<el-tooltip
:disabled="!showPlanTooltip"
placement="top"
:content="planSelectedLabels"
>
<el-select
v-model="form.planBizIdList"
multiple
collapse-tags
:collapse-tags-tooltip="false"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索计划产品"
:remote-method="searchPlanProduct"
:loading="planLoading"
@mouseenter.native="handleSelectMouseEnter('plan')"
@mouseleave.native="handleSelectMouseLeave('plan')"
>
<el-option
v-for="item in planOptions"
:key="item.planBizId"
:label="item.planName"
:value="item.planBizId"
/>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="附加产品" prop="additionalProductBizIdList">
<el-tooltip
:disabled="!showAdditionalTooltip"
placement="top"
:content="additionalSelectedLabels"
>
<el-select
v-model="form.additionalProductBizIdList"
multiple
collapse-tags
:collapse-tags-tooltip="false"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索附加产品"
:remote-method="searchAdditionalProduct"
:loading="additionalLoading"
@mouseenter.native="handleSelectMouseEnter('additional')"
@mouseleave.native="handleSelectMouseLeave('additional')"
>
<el-option
v-for="item in additionalOptions"
:key="item.additionalProductBizId"
:label="item.productName"
:value="item.additionalProductBizId"
/>
</el-select>
</el-tooltip>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属公司所在地" prop="location">
<el-input
v-model="form.location"
type="text"
placeholder="请输入公司所在地"
></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="保障内容" prop="coverageContent">
<el-input
v-model="form.coverageContent"
type="textarea"
placeholder="请输入保障内容"
></el-input>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item
label="产品图片"
prop="picture"
:rules="[{ required: true, message: '请上传产品图片', trigger: 'change' }]"
>
<image-upload
v-model="form.picture"
:action="'/oss/api/oss/upload'"
:limit="1"
:file-size="5"
:file-type="['png', 'jpg', 'jpeg']"
:is-show-tip="true"
@change="handleImageChange"
/>
</el-form-item>
</el-col>
<el-col :span="24" v-for="(item, index) in form.tempTermList" :key="item.id">
<el-form-item
:label="`供款年期${index + 1}`"
:prop="`tempTermList.${index}.paymentTerm`"
:rules="[{ required: true, message: '供款年期不能为空', trigger: 'blur' }]"
>
<div class="termBox">
<el-input
v-model="item.paymentTerm"
placeholder="请输入年期"
style="margin-right: 10px"
></el-input>
<el-button
circle
icon="plus"
@click="addPaymentTerm"
v-if="index === form.tempTermList.length - 1"
/>
<el-button circle icon="delete" @click="removePaymentTerm(index)" />
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="InsuranceProduct">
import { delRole, searchScopeList } from '@/api/system/role'
import {
getInsuranceProductList,
editProductStatus,
addInsuranceProduct,
getInsuranceProducPlanList,
editInsuranceProduct,
getProductDetail,
getAdditionalProductList
} from '@/api/insurance/index'
import { getAllCompanys } from '@/api/system/dept'
import useUserStore from '@/store/modules/user'
import { computed, ref } from 'vue'
import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组件
const userStore = useUserStore()
const router = useRouter()
const { proxy } = getCurrentInstance()
const { bx_product_type, sys_scope, bx_product_status, bx_smoking_allowed, bx_currency_type } =
proxy.useDict(
'bx_product_type',
'sys_scope',
'bx_product_status',
'bx_smoking_allowed',
'bx_currency_type'
)
const insuranceList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const total = ref(0)
const title = ref('')
const dateRange = ref([])
const companyLoading = ref(false)
const companyOptions = ref([])
const planLoading = ref(false)
const additionalLoading = ref(false)
const planOptions = ref([])
const additionalOptions = ref([])
//在data中添加一个用于跟踪图片上传状态的变量
const imageUploaded = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
productName: undefined,
productStatus: undefined
},
rules: {
productName: [{ required: true, message: '产品名称不能为空', trigger: 'blur' }],
scope: [{ required: true, message: '作用域不能为空', trigger: 'blur' }],
productType: [{ required: true, message: '产品类型不能为空', trigger: 'blur' }],
currency: [{ required: true, message: '货币不能为空', trigger: 'blur' }],
premiumRate: [
{ required: true, message: '基础费率不能为空', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === '' || value === null) {
callback(new Error('基础费率不能为空'))
return
}
const numValue = Number(value)
if (isNaN(numValue)) {
callback(new Error('请输入有效的数字'))
} else if (numValue < 0) {
callback(new Error('基础费率不能小于0'))
} else if (numValue > 100) {
callback(new Error('基础费率不能大于100'))
} else {
callback()
}
},
trigger: 'blur'
}
],
smokingAllowed: [{ required: true, message: '是否区分吸烟不能为空', trigger: 'blur' }],
productStatus: [{ required: true, message: '产品状态不能为空', trigger: 'blur' }],
startAge: [
{ required: true, message: '起保年龄不能为空', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!Number.isInteger(Number(value)) || value <= 0) {
callback(new Error('请输入正整数'))
} else {
callback()
}
},
trigger: 'blur'
}
],
endAge: [
{ required: true, message: '终保年龄不能为空', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (!Number.isInteger(Number(value)) || value <= 0) {
callback(new Error('请输入正整数'))
} else if (Number(value) <= Number(form.value.startAge)) {
callback(new Error('终保必须大于起保'))
} else {
callback()
}
},
trigger: 'blur'
}
],
tenantBizId: [{ required: true, message: '所属租户不能为空', trigger: 'blur' }],
ssDeptBizIdList: [
{
required: true,
message: '所属保险公司不能为空',
trigger: 'change',
validator: (rule, value, callback) => {
if (!value || value.length === 0) {
callback(new Error('请至少选择一个所属保险公司'))
} else {
callback()
}
}
}
],
picture: [
{
required: true,
validator: (rule, value, callback) => {
if (!value || value.trim() === '') {
callback(new Error('请上传产品图片'))
} else {
callback()
}
},
trigger: 'change'
}
]
}
})
const { queryParams, form, rules } = toRefs(data)
const filteredScopeOptions = computed(() => {
if (!sys_scope.value || !Array.isArray(sys_scope.value)) return []
return sys_scope.value.filter(option => {
// 添加安全判断
if (!option || typeof option.value === 'undefined') return false
return Number(option.value) !== 3
})
})
// 添加图片变更处理函数
function handleImageChange(value) {
imageUploaded.value = !!value
// 手动触发表单验证
if (proxy.$refs.form) {
proxy.$refs.form.validateField('picture')
}
}
const handlePremiumRateInput = value => {
// 限制输入范围为0-100
if (value > 100) {
form.value.premiumRate = 100
} else if (value < 0) {
form.value.premiumRate = 0
}
// 限制小数位数为2位
if (value && value.toString().includes('.')) {
const parts = value.toString().split('.')
if (parts[1].length > 2) {
form.value.premiumRate = Number(value).toFixed(2)
}
}
}
const handleStatusCommand = command => {
const { row, action } = command
const newStatus = row.productStatus === 1 ? 0 : 1 // 获取切换后的新状态
proxy.$modal
.confirm(`确认要${action}"${row.productName}"吗?`)
.then(function () {
return editProductStatus(row.productBizId, newStatus).then(() => {
proxy.$modal.msgSuccess(`${row.productName}${action}成功`)
getList()
})
})
.catch(() => {})
}
/** 查询角色列表 */
function getList() {
loading.value = true
getInsuranceProductList(queryParams.value).then(response => {
if (response.code === 200) {
insuranceList.value = response.data.records
total.value = response.data.total
loading.value = false
} else {
proxy.$modal.msgError(response.msg)
}
})
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
proxy.resetForm('queryRef')
handleQuery()
}
/** 删除按钮操作 */
function handleDelete(row) {
const roleIds = row.roleId || ids.value
proxy.$modal
.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?')
.then(function () {
return delRole(roleIds)
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 重置新增的表单以及其他数据 */
function reset() {
form.value = {
tempTermList: [],
ssDeptBizIdList: [],
cdDeptBizIdList: [],
planBizIdList: [],
additionalProductBizIdList: []
}
proxy.resetForm('roleRef')
}
/** 添加角色 */
function handleAdd() {
form.value = {
tempTermList: [],
ssDeptBizIdList: [],
cdDeptBizIdList: [],
planBizIdList: [],
additionalProductBizIdList: []
}
form.value.tempTermList.push({ id: 1 })
searchCompanys('') //搜索公司
searchPlanProduct('') //搜索计划产品
searchAdditionalProduct('') //搜索附加产品
open.value = true
title.value = '添加产品'
}
/** 修改产品 */
function handleUpdate(row) {
reset()
searchCompanys('') //搜索公司
searchPlanProduct('') //搜索计划产品
searchAdditionalProduct('') //搜索附加产品
const productBizId = row.productBizId
getProductDetail(productBizId).then(response => {
if (response.code === 200) {
console.log('response.data', response.data)
let result = response.data
form.value.tempTermList = result.paymentTermList.map((item, index) => {
return {
id: index + 1,
paymentTerm: item
}
})
// 回显保险公司数据
form.value.ssDeptBizIdList = result.ssDeptDtoList.map(item => item.deptBizId)
// 回显出单公司数据
form.value.cdDeptBizIdList = result.cdDeptDtoList.map(item => item.deptBizId)
// 回显产品计划数据
form.value.planBizIdList = result.planDtoList.map(item => item.planBizId)
// 回显附加产品数据
form.value.additionalProductBizIdList = result.insuranceAdditionalProductDtoList.map(
item => item.additionalProductBizId
)
form.value.startAge = result.insuredAgeRange.split('-')[0]
form.value.endAge = result.insuredAgeRange.split('-')[1]
form.value = Object.assign({}, form.value, result)
// 根据作用域类型预加载关联数据
if (response.data.scope === 2) {
tenantOptions.value.push({
tenantBizId: response.data.tenantBizId,
tenantName: response.data.tenantName
})
} else if (response.data.scope === 3) {
projectOptions.value.push({
projectBizId: response.data.projectBizId,
projectName: response.data.projectName
})
}
open.value = true
title.value = '修改产品'
} else {
proxy.$modal.msgError(response.msg)
}
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs['roleRef'].validate(valid => {
if (valid) {
let newTermList = JSON.parse(JSON.stringify(form.value.tempTermList))
form.value.paymentTermList = newTermList.map(item => item.paymentTerm)
form.value.insuredAgeRange = `${form.value.startAge}-${form.value.endAge}`
if (form.value.productBizId) {
editInsuranceProduct(form.value).then(response => {
if (response.code === 200) {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
} else {
proxy.$modal.msgError(response.msg)
}
})
} else {
addInsuranceProduct(form.value).then(response => {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
})
}
}
})
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
getList()
//========作用域-切换逻辑开始=========
const tenantOptions = ref([])
const tenantLoading = ref(false)
const projectOptions = ref([])
const projectLoading = ref(false)
// 作用域变更处理
function handleScopeChange(val) {
console.log('作用域变更:', val)
// 清空不需要的字段值
if (val === 2) {
form.value.projectBizId = ''
// 自动加载租户列表(可选)
searchTenants('')
} else if (val === 3) {
form.value.tenantBizId = ''
// 自动加载项目列表(可选)
searchProjects('')
} else if (val === 1) {
//系统级
form.value.tenantBizId = ''
form.value.projectBizId = ''
}
}
// 搜索租户方法
function searchTenants(query) {
tenantLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 2,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
tenantOptions.value = response.data.records
})
} catch (error) {
console.error('租户搜索失败', error)
tenantOptions.value = []
} finally {
tenantLoading.value = false
}
}
// 搜索项目方法
function searchProjects(query) {
projectLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 3,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
projectOptions.value = response.data.records
})
} catch (error) {
console.error('项目搜索失败', error)
projectOptions.value = []
} finally {
projectLoading.value = false
}
}
//========作用域-切换逻辑结束=========
// 新增供款年期
const addPaymentTerm = () => {
form.value.tempTermList.push({
id: form.value.tempTermList.length + 1
})
}
// 删除供款年期
const removePaymentTerm = index => {
if (form.value.tempTermList.length == 1) {
proxy.$modal.msgError('供款年期不能为空')
return
}
form.value.tempTermList.splice(index, 1)
}
// 搜索公司方法
const searchCompanys = query => {
companyLoading.value = true
try {
const params = {
deptName: query,
pageNo: 1,
pageSize: 10
}
getAllCompanys(params).then(response => {
companyOptions.value = response.data.records
})
} catch (error) {
console.error('公司搜索失败', error)
companyOptions.value = []
} finally {
companyLoading.value = false
}
}
// 搜索计划产品
const searchPlanProduct = query => {
planLoading.value = true
try {
const params = {
planName: query,
pageNo: 1,
pageSize: 10
}
getInsuranceProducPlanList(params).then(response => {
planOptions.value = response.data.records
})
} catch (error) {
console.error('公司搜索失败', error)
planOptions.value = []
} finally {
planLoading.value = false
}
}
// 搜索附加产品
const searchAdditionalProduct = query => {
additionalLoading.value = true
try {
const params = {
productName: query,
pageNo: 1,
pageSize: 10
}
getAdditionalProductList(params).then(response => {
additionalOptions.value = response.data.records
})
} catch (error) {
console.error('附加产品搜索失败', error)
additionalOptions.value = []
} finally {
additionalLoading.value = false
}
}
//========多选下拉框悬停效果开始=========
// 新增响应式数据
const showSsDeptTooltip = ref(false)
const showCdDeptTooltip = ref(false)
const showPlanTooltip = ref(false)
const showAdditionalTooltip = ref(false)
// 计算选中项的标签文本
const ssDeptSelectedLabels = computed(() => {
return form.value.ssDeptBizIdList
.map(id => {
const item = companyOptions.value.find(opt => opt.deptBizId === id)
return item ? item.deptName : id
})
.join('、')
})
const cdDeptSelectedLabels = computed(() => {
return form.value.cdDeptBizIdList
.map(id => {
const item = companyOptions.value.find(opt => opt.deptBizId === id)
return item ? item.deptName : id
})
.join('、')
})
const planSelectedLabels = computed(() => {
return form.value.planBizIdList
.map(id => {
const item = planOptions.value.find(opt => opt.planBizId === id)
return item ? item.planName : id
})
.join('、')
})
const additionalSelectedLabels = computed(() => {
return form.value.additionalProductBizIdList
.map(id => {
const item = additionalOptions.value.find(opt => opt.additionalProductBizId === id)
return item ? item.productName : id
})
.join('、')
})
// 处理鼠标进入select
const handleSelectMouseEnter = type => {
// 判断是否有折叠的标签(选中数量超过显示数量)
let hasCollapsedTags = false
switch (type) {
case 'ssDept':
hasCollapsedTags = form.value.ssDeptBizIdList.length > 0
break
case 'cdDept':
hasCollapsedTags = form.value.cdDeptBizIdList.length > 0
break
case 'plan':
hasCollapsedTags = form.value.planBizIdList.length > 0
break
case 'additional':
hasCollapsedTags = form.value.additionalProductBizIdList.length > 0
break
}
if (hasCollapsedTags) {
switch (type) {
case 'ssDept':
showSsDeptTooltip.value = true
break
case 'cdDept':
showCdDeptTooltip.value = true
break
case 'plan':
showPlanTooltip.value = true
break
case 'additional':
showAdditionalTooltip.value = true
break
}
}
}
// 处理鼠标离开select
const handleSelectMouseLeave = type => {
switch (type) {
case 'ssDept':
showSsDeptTooltip.value = false
break
case 'cdDept':
showCdDeptTooltip.value = false
break
case 'plan':
showPlanTooltip.value = false
break
case 'additional':
showAdditionalTooltip.value = false
break
}
}
//========多选下拉框悬停效果结束=========
</script>
<style lang="scss" scoped>
.logo-image {
width: 70px;
height: 70px;
border-radius: 4px;
object-fit: cover;
}
.years {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.line {
margin: 0 10px;
}
}
.termBox {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.timeRange {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.line {
margin: 0 10px;
}
}
.timeRange ::v-deep .el-form-item--default .el-form-item__content {
margin-left: 0 !important;
}
</style>
<template>
<div class="app-container">
<el-form
:model="queryParams"
ref="queryRef"
v-show="showSearch"
:inline="true"
label-width="100px"
>
<el-form-item label="产品计划名称" prop="productName">
<el-input
v-model="queryParams.planName"
placeholder="请输入产品计划名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="loading" :data="insurancePlanList">
<el-table-column label="产品计划名称" prop="planName" :show-overflow-tooltip="true" />
<el-table-column label="产品图片" align="center" prop="picture" width="150">
<template #default="scope">
<!-- :preview-src-list="[scope.row.logoUrl]" -->
<el-image
v-if="scope.row.picture"
:src="scope.row.picture"
class="logo-image"
fit="cover"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@click="
evt => {
evt.preventDefault()
handleStatusChange(scope.row)
}
"
></el-switch>
<dict-tag
:options="sys_status"
:value="scope.row.status"
v-if="userStore.isSuperAdmin === 0 && scope.row.scope === 1"
/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="left" prop="createTime">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="200"
fixed="right"
>
<template #default="scope">
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>修改</el-button
>
<!-- <el-button
link
type="primary"
icon="Delete"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>删除</el-button
> -->
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改角色配置对话框 -->
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
<el-form ref="roleRef" :model="form" :rules="rules" label-width="150px">
<el-row>
<el-col :span="24">
<el-form-item label="产品计划名称" prop="planName">
<el-input v-model="form.planName" placeholder="请输入产品计划名称" maxlength="30" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_status"
:key="dict.value"
:value="Number(dict.value)"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item
label="产品图片"
prop="picture"
:rules="[{ required: true, message: '请上传产品图片', trigger: 'change' }]"
>
<image-upload
v-model="form.picture"
:action="'/oss/api/oss/upload'"
:limit="1"
:file-size="5"
:file-type="['png', 'jpg', 'jpeg']"
:is-show-tip="true"
@change="handleImageChange"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="InsuranceProductPlan">
import {
addRole,
roleStatusChange,
delRole,
listRole,
getRoleDetail,
roleUpdate,
searchScopeList
} from '@/api/system/role'
import {
getInsuranceProducPlanList,
editPlanStatus,
addInsuranceProducPlan,
getInsuranceProducPlanDetail,
editInsuranceProducPlan
} from '@/api/insurance/index'
import useUserStore from '@/store/modules/user'
import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组件
import { computed } from 'vue'
const userStore = useUserStore()
const router = useRouter()
const { proxy } = getCurrentInstance()
const { bx_product_type, sys_scope, sys_status, bx_product_status, bx_smoking_allowed } =
proxy.useDict(
'bx_product_type',
'sys_scope',
'sys_status',
'bx_product_status',
'bx_smoking_allowed'
)
const insurancePlanList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const total = ref(0)
const title = ref('')
const dateRange = ref([])
const queryRef = ref(null)
//在data中添加一个用于跟踪图片上传状态的变量
const imageUploaded = ref(false)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
planName: undefined
},
rules: {
planName: [{ required: true, message: '产品计划名称不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
picture: [
{
required: true,
validator: (rule, value, callback) => {
if (!value || value.trim() === '') {
callback(new Error('请上传产品图片'))
} else {
callback()
}
},
trigger: 'change'
}
]
}
})
const { queryParams, form, rules } = toRefs(data)
/** 计算属性-角色类型 */
const filteredRoleTypeOptions = computed(() => {
//角色类型或者角色类型数组是否存在
if (!sys_role_type.value || !Array.isArray(sys_role_type.value)) return []
return sys_role_type.value.filter(option => {
// 过滤掉超级管理员选项
if (!option || typeof option.value === 'undefined') return false
return Number(option.value) !== 1
})
})
/** 查询角色列表 */
function getList() {
loading.value = true
getInsuranceProducPlanList(queryParams.value).then(response => {
if (response.code === 200) {
insurancePlanList.value = response.data.records
total.value = response.data.total
loading.value = false
} else {
proxy.$modal.msgError(response.msg)
}
})
}
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
queryParams.value = {
pageNum: 1,
pageSize: 10,
planName: undefined
}
proxy.resetForm('queryRef')
handleQuery()
}
/** 删除按钮操作 */
function handleDelete(row) {
const roleIds = row.roleId || ids.value
proxy.$modal
.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?')
.then(function () {
return delRole(roleIds)
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 角色状态修改 */
function handleStatusChange(row) {
const newStatus = row.status === 0 ? 0 : 1 // 获取切换后的新状态
const text = newStatus === 0 ? '停用' : '启用' // 根据新状态显示文本
proxy.$modal
.confirm(`确认要${text}"${row.planName}"产品吗?`)
.then(function () {
return editPlanStatus(row.planBizId, newStatus).then(() => {
proxy.$modal.msgSuccess(`${text}成功`)
// 请求成功后,更新状态
row.status = newStatus
getList()
})
})
.catch(() => {
// 操作取消时恢复原状态
row.status = row.status === 1 ? 0 : 1
// 用户取消操作,保持原状态不变
// 这里不需要做任何操作,因为switch会自动回滚
})
}
/** 重置新增的表单以及其他数据 */
function reset() {
form.value = {}
proxy.resetForm('roleRef')
}
/** 添加产品计划 */
function handleAdd() {
form.value = {}
open.value = true
title.value = '添加产品计划'
}
/** 修改产品计划 */
function handleUpdate(row) {
reset()
const planBizId = row.planBizId
getInsuranceProducPlanDetail(planBizId).then(response => {
if (response.code === 200) {
form.value = response.data
open.value = true
title.value = '修改产品计划'
} else {
proxy.$modal.msgError(response.msg)
}
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs['roleRef'].validate(valid => {
if (valid) {
if (form.value.planBizId) {
editInsuranceProducPlan(form.value).then(response => {
if (response.code === 200) {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
} else {
proxy.$modal.msgError(response.msg)
}
})
} else {
addInsuranceProducPlan(form.value).then(response => {
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
})
}
}
})
}
/** 取消按钮 */
function cancel() {
open.value = false
reset()
}
getList()
//========作用域-切换逻辑开始=========
const tenantOptions = ref([])
const tenantLoading = ref(false)
const projectOptions = ref([])
const projectLoading = ref(false)
// 作用域变更处理
function handleScopeChange(val) {
console.log('作用域变更:', val)
// 清空不需要的字段值
if (val === 2) {
form.value.projectBizId = ''
// 自动加载租户列表(可选)
searchTenants('')
} else if (val === 3) {
form.value.tenantBizId = ''
// 自动加载项目列表(可选)
searchProjects('')
} else if (val === 1) {
//系统级
form.value.tenantBizId = ''
form.value.projectBizId = ''
}
}
// 搜索租户方法
function searchTenants(query) {
tenantLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 2,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
tenantOptions.value = response.data.records
})
} catch (error) {
console.error('租户搜索失败', error)
tenantOptions.value = []
} finally {
tenantLoading.value = false
}
}
// 搜索项目方法
function searchProjects(query) {
projectLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 3,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
projectOptions.value = response.data.records
})
} catch (error) {
console.error('项目搜索失败', error)
projectOptions.value = []
} finally {
projectLoading.value = false
}
}
//========作用域-切换逻辑结束=========
// 添加图片变更处理函数
function handleImageChange(value) {
imageUploaded.value = !!value
// 手动触发表单验证
if (proxy.$refs.form) {
proxy.$refs.form.validateField('picture')
}
}
</script>
<style lang="scss" scoped>
.logo-image {
width: 70px;
height: 70px;
border-radius: 4px;
object-fit: cover;
}
</style>
...@@ -20,10 +20,7 @@ ...@@ -20,10 +20,7 @@
@click="goTarget('https://gitee.com/y_project/RuoYi-Vue')" @click="goTarget('https://gitee.com/y_project/RuoYi-Vue')"
>访问码云</el-button >访问码云</el-button
> >
<el-button <el-button icon="HomeFilled" plain @click="goTarget('http://ruoyi.vip')"
icon="HomeFilled"
plain
@click="goTarget('http://ruoyi.vip')"
>访问主页</el-button >访问主页</el-button
> >
</p> </p>
...@@ -83,22 +80,20 @@ ...@@ -83,22 +80,20 @@
<p> <p>
<i class="el-icon-user-solid"></i> QQ群:<s> 满937441 </s> <s> 满887144332 </s> <i class="el-icon-user-solid"></i> QQ群:<s> 满937441 </s> <s> 满887144332 </s>
<s> 满180251782 </s> <s> 满104180207 </s> <s> 满186866453 </s> <s> 满201396349 </s> <s> 满180251782 </s> <s> 满104180207 </s> <s> 满186866453 </s> <s> 满201396349 </s>
<s> 满101456076 </s> <s> 满101539465 </s> <s> 满264312783 </s> <s> 满167385320 </s> <s> 满101456076 </s> <s> 满101539465 </s> <s> 满264312783 </s> <s> 满167385320 </s>
<s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s> <s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s>
<s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s> <s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s>
<s> 满161281055 </s> <s> 满138988063 </s> <s> 满151450850 </s> <s> 满224622315 </s> <s> 满161281055 </s> <s> 满138988063 </s> <s> 满151450850 </s> <s> 满224622315 </s>
<s> 满287842588 </s> <s> 满187944233 </s> <s> 满228578329 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766" target="_blank">191164766</a> <s> 满287842588 </s> <s> 满187944233 </s> <s> 满228578329 </s>
</p> <a
<p> href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766"
<i class="el-icon-chat-dot-round"></i> 微信:<a target="_blank"
href="javascript:;" >191164766</a
>/ *若依</a
> >
</p> </p>
<p><i class="el-icon-chat-dot-round"></i> 微信:<a href="javascript:;">/ *若依</a></p>
<p> <p>
<i class="el-icon-money"></i> 支付宝:<a <i class="el-icon-money"></i> 支付宝:<a href="javascript:;" class="支付宝信息"
href="javascript:;"
class="支付宝信息"
>/ *若依</a >/ *若依</a
> >
</p> </p>
...@@ -878,9 +873,7 @@ ...@@ -878,9 +873,7 @@
<li>调整表头固定列默认样式</li> <li>调整表头固定列默认样式</li>
<li>代码生成模板调整,字段为String并且必填则加空串条件</li> <li>代码生成模板调整,字段为String并且必填则加空串条件</li>
<li>代码生成字典Integer/Long使用parseInt</li> <li>代码生成字典Integer/Long使用parseInt</li>
<li> <li>修复dict_sort不可update为0的问题&查询返回增加dict_sort升序排序</li>
修复dict_sort不可update为0的问题&查询返回增加dict_sort升序排序
</li>
<li>修正岗位导出权限注解</li> <li>修正岗位导出权限注解</li>
<li>禁止加密密文返回前端</li> <li>禁止加密密文返回前端</li>
<li>修复代码生成页面中的查询条件创建时间未生效的问题</li> <li>修复代码生成页面中的查询条件创建时间未生效的问题</li>
...@@ -1042,11 +1035,7 @@ ...@@ -1042,11 +1035,7 @@
</div> </div>
</template> </template>
<div class="body"> <div class="body">
<img <img src="@/assets/images/pay.png" alt="donate" style="width: 100%" />
src="@/assets/images/pay.png"
alt="donate"
style="width:100%"
/>
<span style="display: inline-block; height: 30px; line-height: 30px" <span style="display: inline-block; height: 30px; line-height: 30px"
>你可以请作者喝杯咖啡表示鼓励</span >你可以请作者喝杯咖啡表示鼓励</span
> >
...@@ -1088,7 +1077,7 @@ function goTarget(url) { ...@@ -1088,7 +1077,7 @@ function goTarget(url) {
margin: 0; margin: 0;
} }
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 13px; font-size: 13px;
color: #676a6c; color: #676a6c;
overflow-x: hidden; overflow-x: hidden;
...@@ -1128,4 +1117,3 @@ function goTarget(url) { ...@@ -1128,4 +1117,3 @@ function goTarget(url) {
} }
} }
</style> </style>
...@@ -10,7 +10,9 @@ ...@@ -10,7 +10,9 @@
auto-complete="off" auto-complete="off"
placeholder="账号" placeholder="账号"
> >
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template> <template #prefix
><svg-icon icon-class="user" class="el-input__icon input-icon"
/></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
...@@ -22,10 +24,12 @@ ...@@ -22,10 +24,12 @@
placeholder="密码" placeholder="密码"
@keyup.enter="handleLogin" @keyup.enter="handleLogin"
> >
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template> <template #prefix
><svg-icon icon-class="password" class="el-input__icon input-icon"
/></template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" v-if="captchaEnabled"> <!-- <el-form-item prop="code" v-if="captchaEnabled">
<el-input <el-input
v-model="loginForm.code" v-model="loginForm.code"
size="large" size="large"
...@@ -39,20 +43,22 @@ ...@@ -39,20 +43,22 @@
<div class="login-code"> <div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/> <img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div> </div>
</el-form-item> </el-form-item> -->
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> <el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px"
<el-form-item style="width:100%;"> >记住密码</el-checkbox
>
<el-form-item style="width: 100%">
<el-button <el-button
:loading="loading" :loading="loading"
size="large" size="large"
type="primary" type="primary"
style="width:100%;" style="width: 100%"
@click.prevent="handleLogin" @click.prevent="handleLogin"
> >
<span v-if="!loading">登 录</span> <span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span> <span v-else>登 录 中...</span>
</el-button> </el-button>
<div style="float: right;" v-if="register"> <div style="float: right" v-if="register">
<router-link class="link-type" :to="'/register'">立即注册</router-link> <router-link class="link-type" :to="'/register'">立即注册</router-link>
</div> </div>
</el-form-item> </el-form-item>
...@@ -65,9 +71,9 @@ ...@@ -65,9 +71,9 @@
</template> </template>
<script setup> <script setup>
import { getCodeImg } from "@/api/login" import { getCodeImg } from '@/api/login'
import Cookies from "js-cookie" import Cookies from 'js-cookie'
import { encrypt, decrypt } from "@/utils/jsencrypt" import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
const title = import.meta.env.VITE_APP_TITLE const title = import.meta.env.VITE_APP_TITLE
...@@ -77,20 +83,20 @@ const router = useRouter() ...@@ -77,20 +83,20 @@ const router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const loginForm = ref({ const loginForm = ref({
username: "zs", username: 'zs',
password: "123", password: '123',
rememberMe: false, rememberMe: false,
code: "", code: '',
uuid: "" uuid: ''
}) })
const loginRules = { const loginRules = {
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }], username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }], password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }]
code: [{ required: true, trigger: "change", message: "请输入验证码" }] // code: [{ required: true, trigger: "change", message: "请输入验证码" }]
} }
const codeUrl = ref("") const codeUrl = ref('')
const loading = ref(false) const loading = ref(false)
// 验证码开关 // 验证码开关
const captchaEnabled = ref(true) const captchaEnabled = ref(true)
...@@ -98,9 +104,13 @@ const captchaEnabled = ref(true) ...@@ -98,9 +104,13 @@ const captchaEnabled = ref(true)
const register = ref(false) const register = ref(false)
const redirect = ref(undefined) const redirect = ref(undefined)
watch(route, (newRoute) => { watch(
route,
newRoute => {
redirect.value = newRoute.query && newRoute.query.redirect redirect.value = newRoute.query && newRoute.query.redirect
}, { immediate: true }) },
{ immediate: true }
)
function handleLogin() { function handleLogin() {
proxy.$refs.loginRef.validate(valid => { proxy.$refs.loginRef.validate(valid => {
...@@ -108,50 +118,53 @@ function handleLogin() { ...@@ -108,50 +118,53 @@ function handleLogin() {
loading.value = true loading.value = true
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码 // 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) { if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 }) Cookies.set('username', loginForm.value.username, { expires: 30 })
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 }) Cookies.set('password', encrypt(loginForm.value.password), { expires: 30 })
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 }) Cookies.set('rememberMe', loginForm.value.rememberMe, { expires: 30 })
} else { } else {
// 否则移除 // 否则移除
Cookies.remove("username") Cookies.remove('username')
Cookies.remove("password") Cookies.remove('password')
Cookies.remove("rememberMe") Cookies.remove('rememberMe')
} }
// 调用action的登录方法 // 调用action的登录方法
userStore.login(loginForm.value).then(() => { userStore
const query = route.query .login(loginForm.value)
const otherQueryParams = Object.keys(query).reduce((acc, cur) => { .then(() => {
if (cur !== "redirect") { const query = route.query
acc[cur] = query[cur] const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
} if (cur !== 'redirect') {
return acc acc[cur] = query[cur]
}, {}) }
router.push({ path: redirect.value || "/", query: otherQueryParams }) return acc
}).catch(() => { }, {})
loading.value = false router.push({ path: redirect.value || '/', query: otherQueryParams })
// 重新获取验证码 })
if (captchaEnabled.value) { .catch(() => {
getCode() loading.value = false
} // 重新获取验证码
}) // if (captchaEnabled.value) {
// getCode()
// }
})
} }
}) })
} }
function getCode() { const getCode = () => {
getCodeImg().then(res => { getCodeImg().then(res => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (captchaEnabled.value) { if (captchaEnabled.value) {
codeUrl.value = "data:image/gif;base64," + res.img codeUrl.value = 'data:image/gif;base64,' + res.img
loginForm.value.uuid = res.uuid loginForm.value.uuid = res.uuid
} }
}) })
} }
function getCookie() { function getCookie() {
const username = Cookies.get("username") const username = Cookies.get('username')
const password = Cookies.get("password") const password = Cookies.get('password')
const rememberMe = Cookies.get("rememberMe") const rememberMe = Cookies.get('rememberMe')
loginForm.value = { loginForm.value = {
username: username === undefined ? loginForm.value.username : username, username: username === undefined ? loginForm.value.username : username,
password: password === undefined ? loginForm.value.password : decrypt(password), password: password === undefined ? loginForm.value.password : decrypt(password),
...@@ -159,17 +172,17 @@ function getCookie() { ...@@ -159,17 +172,17 @@ function getCookie() {
} }
} }
getCode() // getCode()
getCookie() getCookie()
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
.login { .login {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
background-image: url("../assets/images/login-background.jpg"); background-image: url('../assets/images/login-background.jpg');
background-size: cover; background-size: cover;
} }
.title { .title {
......
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch"> <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
<el-form-item label="部门名称" prop="deptName"> <el-form-item label="部门名称" prop="deptName">
<el-input <el-input
v-model="queryParams.deptName" v-model="queryParams.deptName"
placeholder="请输入部门名称" placeholder="请输入部门名称"
clearable clearable
style="width: 200px" style="width: 200px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item>
<el-select v-model="queryParams.status" placeholder="部门状态" clearable style="width: 200px"> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-option <el-button icon="Refresh" @click="resetQuery">重置</el-button>
v-for="dict in sys_normal_disable" <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-if="refreshTable"
v-loading="loading"
:data="menuList"
row-key="deptBizId"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="deptName" label="部门名称" width="140px"></el-table-column>
<el-table-column prop="type" label="类型">
<template #default="scope">
<dict-tag :options="sys_dept_type" :value="scope.row.menuType" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="150">
<template #default="scope">
<el-switch
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@click="
evt => {
evt.preventDefault()
handleStatusChange(scope.row)
}
"
></el-switch>
<dict-tag
:options="sys_status"
:value="scope.row.status"
v-if="userStore.isSuperAdmin === 0 && scope.row.scope === 1"
/>
</template>
</el-table-column>
<el-table-column prop="attribute" label="组织属性">
<template #default="scope">
<dict-tag :options="sys_dept_attribute" :value="scope.row.attribute" />
</template>
</el-table-column>
<el-table-column prop="phone" label="联系电话"></el-table-column>
<el-table-column prop="email" label="邮箱"></el-table-column>
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150px">
<template #default="scope">
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>修改</el-button
>
<!-- <el-button
v-if="
scope.row.parentId != 0 &&
((userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1)
"
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
>删除</el-button
> -->
</template>
</el-table-column>
</el-table>
<!-- 添加或修改菜单对话框 -->
<el-dialog :title="title" v-model="open" width="680px" append-to-body>
<el-form ref="deptRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item label="上级部门" prop="parentBizId">
<el-tree-select
v-model="form.parentBizId"
:data="menuOptions"
:props="{ value: 'deptBizId', label: 'deptName', children: 'children' }"
value-key="deptBizId"
placeholder="选择上级部门"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="类型" prop="type">
<el-radio-group v-model="form.type" @change="handleTypeChange">
<el-radio
v-for="dict in sys_dept_type"
:key="Number(dict.value)"
:value="Number(dict.value)"
>{{ dict.label }}</el-radio
>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_status"
:key="dict.value" :key="dict.value"
:label="dict.label" :value="Number(dict.value)"
:value="dict.value" >{{ dict.label }}</el-radio
/> >
</el-select> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item> </el-col>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-col :span="12">
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-form-item :label="`${nameDept}名称`" prop="deptName">
</el-form-item> <el-input v-model="form.deptName" :placeholder="`请输入${nameDept}名称`" />
</el-form> </el-form-item>
</el-col>
<el-row :gutter="10" class="mb8"> <el-col :span="12">
<el-col :span="1.5"> <el-form-item label="组织属性" prop="attribute">
<el-button <el-select v-model="form.attribute" placeholder="请输入">
type="primary" <el-option
plain v-for="item in sys_dept_attribute"
icon="Plus" :key="item.value"
@click="handleAdd" :label="item.label"
v-hasPermi="['system:dept:add']" :value="Number(item.value)"
>新增</el-button> />
</el-col> </el-select>
<el-col :span="1.5"> </el-form-item>
<el-button </el-col>
type="info"
plain <el-col :span="12">
icon="Sort" <el-form-item prop="scope" label="作用域">
@click="toggleExpandAll" <el-select v-model="form.scope" placeholder="请输入" @change="handleScopeChange">
>展开/折叠</el-button> <el-option
</el-col> v-for="item in filteredScopeOptions"
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> :key="item.value"
</el-row> :label="item.label"
:value="Number(item.value)"
<el-table />
v-if="refreshTable" </el-select>
v-loading="loading" </el-form-item>
:data="deptList" </el-col>
row-key="deptId"
:default-expand-all="isExpandAll" <el-col :span="12">
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" <el-form-item label="显示排序" prop="orderNum">
> <el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column> </el-form-item>
<el-table-column prop="orderNum" label="排序" width="200"></el-table-column> </el-col>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope"> <el-col :span="12">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" /> <el-form-item label="联系电话" prop="phone">
</template> <el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
</el-table-column> </el-form-item>
<el-table-column label="创建时间" align="center" prop="createTime" width="200"> </el-col>
<template #default="scope"> <el-col :span="12">
<span>{{ parseTime(scope.row.createTime) }}</span> <el-form-item label="邮箱" prop="email">
</template> <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-table-column> </el-form-item>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> </el-col>
<template #default="scope"> <el-col :span="12">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:dept:edit']">修改</el-button> <!-- 所属租户 - 可搜索下拉框 -->
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['system:dept:add']">新增</el-button> <el-form-item v-if="form.scope == '2'" label="所属租户" prop="tenantBizId">
<el-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">删除</el-button> <el-select
</template> v-model="form.tenantBizId"
</el-table-column> filterable
</el-table> remote
reserve-keyword
<!-- 添加或修改部门对话框 --> placeholder="请输入关键词搜索租户"
<el-dialog :title="title" v-model="open" width="600px" append-to-body> :remote-method="searchTenants"
<el-form ref="deptRef" :model="form" :rules="rules" label-width="80px"> :loading="tenantLoading"
<el-row> >
<el-col :span="24" v-if="form.parentId !== 0"> <el-option
<el-form-item label="上级部门" prop="parentId"> v-for="item in tenantOptions"
<el-tree-select :key="item.tenantBizId"
v-model="form.parentId" :label="item.tenantName"
:data="deptOptions" :value="item.tenantBizId"
:props="{ value: 'deptId', label: 'deptName', children: 'children' }" />
value-key="deptId" </el-select>
placeholder="选择上级部门" </el-form-item>
check-strictly
/> <!-- 所属项目 - 可搜索下拉框 -->
</el-form-item> <el-form-item v-if="form.scope == '3'" label="所属项目" prop="projectBizId">
</el-col> <el-select
<el-col :span="12"> v-model="form.projectBizId"
<el-form-item label="部门名称" prop="deptName"> filterable
<el-input v-model="form.deptName" placeholder="请输入部门名称" /> remote
</el-form-item> reserve-keyword
</el-col> placeholder="请输入关键词搜索项目"
<el-col :span="12"> :remote-method="searchProjects"
<el-form-item label="显示排序" prop="orderNum"> :loading="projectLoading"
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" /> >
</el-form-item> <el-option
</el-col> v-for="item in projectOptions"
<el-col :span="12"> :key="item.projectBizId"
<el-form-item label="负责人" prop="leader"> :label="item.projectName"
<el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" /> :value="item.projectBizId"
</el-form-item> />
</el-col> </el-select>
<el-col :span="12"> </el-form-item>
<el-form-item label="联系电话" prop="phone"> </el-col>
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" /> </el-row>
</el-form-item> </el-form>
</el-col> <template #footer>
<el-col :span="12"> <div class="dialog-footer">
<el-form-item label="邮箱" prop="email"> <el-button type="primary" @click="submitForm">确定</el-button>
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> <el-button @click="cancel">取 消</el-button>
</el-form-item> </div>
</el-col> </template>
<el-col :span="12"> </el-dialog>
<el-form-item label="部门状态"> </div>
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_normal_disable"
:key="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template> </template>
<script setup name="Dept"> <script setup name="Menu">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept" import { addMenu, delMenu, getMenu, updateMenu, searchScopeList } from '@/api/system/menu'
import { deptStatusChange, editDept, addDept, deptList, getDept } from '@/api/system/dept'
import useUserStore from '@/store/modules/user'
import SvgIcon from '@/components/SvgIcon'
import IconSelect from '@/components/IconSelect'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_normal_disable } = proxy.useDict("sys_normal_disable") const { sys_scope, sys_status, sys_dept_type, sys_dept_attribute } = proxy.useDict(
'sys_scope',
'sys_status',
'sys_dept_attribute',
'sys_dept_type'
)
const userStore = useUserStore()
const deptList = ref([]) const menuList = ref([])
const open = ref(false) const open = ref(false)
const loading = ref(true) const loading = ref(true)
const showSearch = ref(true) const showSearch = ref(true)
const title = ref("") const title = ref('')
const deptOptions = ref([]) const menuOptions = ref([])
const isExpandAll = ref(true) const isExpandAll = ref(false)
const refreshTable = ref(true) const refreshTable = ref(true)
const iconSelectRef = ref(null)
const nameDept = ref('部门')
const data = reactive({ const data = reactive({
form: {}, form: {},
queryParams: { queryParams: {
deptName: undefined, pageNo: 1,
status: undefined pageSize: 999999,
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
menuName: undefined
}, },
rules: { rules: {
parentId: [{ required: true, message: "上级部门不能为空", trigger: "blur" }], deptName: [{ required: true, message: `名称不能为空`, trigger: 'blur' }],
deptName: [{ required: true, message: "部门名称不能为空", trigger: "blur" }], orderNum: [{ required: true, message: '顺序不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: "显示排序不能为空", trigger: "blur" }], type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }], scope: [{ required: true, message: '作用域不能为空', trigger: 'blur' }],
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }] attribute: [{ required: true, message: '组织属性不能为空', trigger: 'blur' }],
}, status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
projectBizId: [{ required: true, message: '项目不能为空', trigger: 'blur' }],
tenantBizId: [{ required: true, message: '租户不能为空', trigger: 'blur' }],
parentBizId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }]
}
}) })
const { queryParams, form, rules } = toRefs(data) const { queryParams, form, rules } = toRefs(data)
const filteredScopeOptions = computed(() => {
if (!sys_scope.value || !Array.isArray(sys_scope.value)) return []
return sys_scope.value.filter(option => {
// 添加安全判断
if (!option || typeof option.value === 'undefined') return false
return Number(option.value) !== 3
})
})
// 类型修改
const handleTypeChange = value => {
sys_dept_type.value.forEach(item => {
if (item.value == value) {
nameDept.value = item.label
}
})
console.log('item', nameDept.value)
}
/** 查询部门列表 */ /** 部门状态修改 还未连调 */
const handleStatusChange = row => {
const newStatus = row.status === 0 ? 0 : 1 // 获取切换后的新状态
const text = newStatus === 0 ? '停用' : '启用' // 根据新状态显示文本
proxy.$modal
.confirm(`确认要${text}"${row.deptName}"吗?`)
.then(function () {
return deptStatusChange(row.deptBizId, newStatus).then(() => {
proxy.$modal.msgSuccess(`${text}成功`)
// 请求成功后,更新状态
row.status = newStatus
getList()
})
})
.catch(() => {
// 操作取消时恢复原状态
row.status = row.status === 1 ? 0 : 1
// 用户取消操作,保持原状态不变
// 这里不需要做任何操作,因为switch会自动回滚
})
}
/** 查询菜单列表 */
function getList() { function getList() {
loading.value = true loading.value = true
listDept(queryParams.value).then(response => { deptList(queryParams.value).then(response => {
deptList.value = proxy.handleTree(response.data, "deptId") menuList.value = proxy.handleTree(response.data.records, 'deptBizId')
console.log('menuList.value', menuList.value)
loading.value = false loading.value = false
}) })
} }
/** 查询菜单下拉树结构 */
function getTreeselect() {
menuOptions.value = []
deptList(queryParams.value).then(response => {
const menu = { deptBizId: '0', deptName: '主类目', children: [] }
menu.children = proxy.handleTree(response.data.records, 'deptBizId', 'parentBizId')
console.log('deptMenu', menu)
menuOptions.value.push(menu)
})
}
/** 取消按钮 */ /** 取消按钮 */
function cancel() { function cancel() {
open.value = false open.value = false
...@@ -189,17 +365,18 @@ function cancel() { ...@@ -189,17 +365,18 @@ function cancel() {
/** 表单重置 */ /** 表单重置 */
function reset() { function reset() {
form.value = { form.value = {}
deptId: undefined, proxy.resetForm('deptRef')
parentId: undefined, }
deptName: undefined,
orderNum: 0, /** 展示下拉图标 */
leader: undefined, function showSelectIcon() {
phone: undefined, iconSelectRef.value.reset()
email: undefined, }
status: "0"
} /** 选择图标 */
proxy.resetForm("deptRef") function selected(name) {
form.value.icon = name
} }
/** 搜索按钮操作 */ /** 搜索按钮操作 */
...@@ -209,21 +386,16 @@ function handleQuery() { ...@@ -209,21 +386,16 @@ function handleQuery() {
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
proxy.resetForm("queryRef") proxy.resetForm('queryRef')
handleQuery() handleQuery()
} }
/** 新增按钮操作 */ /** 新增按钮操作 */
function handleAdd(row) { function handleAdd() {
reset() reset()
listDept().then(response => { getTreeselect()
deptOptions.value = proxy.handleTree(response.data, "deptId")
})
if (row != undefined) {
form.value.parentId = row.deptId
}
open.value = true open.value = true
title.value = "添加部门" title.value = '添加'
} }
/** 展开/折叠操作 */ /** 展开/折叠操作 */
...@@ -236,31 +408,43 @@ function toggleExpandAll() { ...@@ -236,31 +408,43 @@ function toggleExpandAll() {
} }
/** 修改按钮操作 */ /** 修改按钮操作 */
function handleUpdate(row) { async function handleUpdate(row) {
reset() reset()
listDeptExcludeChild(row.deptId).then(response => { await getTreeselect()
deptOptions.value = proxy.handleTree(response.data, "deptId") getDept(row.deptBizId).then(response => {
})
getDept(row.deptId).then(response => {
form.value = response.data form.value = response.data
// 根据作用域类型预加载关联数据
if (response.data.scope === 2) {
tenantOptions.value.push({
tenantBizId: response.data.tenantBizId,
tenantName: response.data.tenantName
})
} else if (response.data.scope === 3) {
projectOptions.value.push({
projectBizId: response.data.projectBizId,
projectName: response.data.projectName
})
}
handleTypeChange(response.data.type)
open.value = true open.value = true
title.value = "修改部门" title.value = `编辑`
}) })
} }
/** 提交按钮 */ /** 提交按钮 */
function submitForm() { function submitForm() {
proxy.$refs["deptRef"].validate(valid => { proxy.$refs['deptRef'].validate(valid => {
if (valid) { if (valid) {
if (form.value.deptId != undefined) { if (form.value.deptBizId) {
updateDept(form.value).then(response => { editDept(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功") proxy.$modal.msgSuccess('修改成功')
open.value = false open.value = false
getList() getList()
}) })
} else { } else {
addDept(form.value).then(response => { addDept(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功") proxy.$modal.msgSuccess('新增成功')
open.value = false open.value = false
getList() getList()
}) })
...@@ -271,13 +455,86 @@ function submitForm() { ...@@ -271,13 +455,86 @@ function submitForm() {
/** 删除按钮操作 */ /** 删除按钮操作 */
function handleDelete(row) { function handleDelete(row) {
proxy.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() { proxy.$modal
return delDept(row.deptId) .confirm('是否确认删除名称为"' + row.deptName + '"的数据项?')
}).then(() => { .then(function () {
getList() return delDept(row.deptBizId)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
getList() getList()
//========作用域-切换逻辑开始=========
const tenantOptions = ref([])
const tenantLoading = ref(false)
const projectOptions = ref([])
const projectLoading = ref(false)
// 作用域变更处理
function handleScopeChange(val) {
console.log('作用域变更:', val)
// 清空不需要的字段值
if (val === 2) {
form.value.projectBizId = ''
// 自动加载租户列表(可选)
searchTenants('')
} else if (val === 3) {
form.value.tenantBizId = ''
// 自动加载项目列表(可选)
searchProjects('')
} else if (val === 1) {
//系统级
form.value.tenantBizId = ''
form.value.projectBizId = ''
}
}
// 搜索租户方法
function searchTenants(query) {
tenantLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 2,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
tenantOptions.value = response.data.records
})
} catch (error) {
console.error('租户搜索失败', error)
tenantOptions.value = []
} finally {
tenantLoading.value = false
}
}
// 搜索项目方法
function searchProjects(query) {
projectLoading.value = true
try {
const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 3,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => {
projectOptions.value = response.data.records
})
} catch (error) {
console.error('项目搜索失败', error)
projectOptions.value = []
} finally {
projectLoading.value = false
}
}
//========作用域-切换逻辑结束=========
</script> </script>
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px"> <el-form
<el-form-item label="项目名称" prop="projectName"> :model="queryParams"
<el-input ref="queryRef"
v-model="queryParams.projectName" v-show="showSearch"
placeholder="请输入项目名称" :inline="true"
clearable label-width="68px"
style="width: 240px" >
@keyup.enter="handleQuery" <el-form-item label="项目名称" prop="projectName">
<el-input
v-model="queryParams.projectName"
placeholder="请输入项目名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="项目状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in sys_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="loading" :data="projectList">
<el-table-column label="项目LOGO" align="center">
<template #default="scope">
<el-image
v-if="scope.row.logoUrl"
class="logo-image"
:src="scope.row.logoUrl"
:preview-src-list="[scope.row.logoUrl]"
fit="cover"
preview-teleported
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="项目名称" prop="projectName" />
<el-table-column prop="scope" label="作用域">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column label="所属租户名称" prop="tenantName" />
<el-table-column prop="isIn" label="是否内置项目">
<template #default="scope">
<dict-tag :options="sys_no_yes" :value="scope.row.isIn" />
</template>
</el-table-column>
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="val => handleStatusChange(scope.row, $event)"
></el-switch>
<dict-tag
:options="sys_status"
:value="scope.row.status"
v-if="userStore.isSuperAdmin === 0 && scope.row.scope === 1"
/>
</template>
</el-table-column>
<el-table-column label="项目开始时间" align="center" prop="startTime">
<template #default="scope">
<span>{{ parseTime(scope.row.startTime) }}</span>
</template>
</el-table-column>
<el-table-column label="项目结束时间" align="center" prop="endTime">
<template #default="scope">
<span>{{ parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="200px"
>
<template #default="scope">
<el-button
link
type="primary"
icon="Edit"
@click="handlePermission(scope.row)"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>分配权限</el-button
>
<el-button
link
type="primary"
icon="Edit"
@click="handleUpdate(scope.row)"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>修改</el-button
>
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-if="
(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) ||
userStore.isSuperAdmin === 1
"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改项目对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="projectRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item label="项目开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="选择项目开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="项目结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="选择项目结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="是否内置项目">
<el-radio-group v-model="form.isIn">
<el-radio
v-for="dict in sys_no_yes"
:key="Number(dict.value)"
:value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="项目访问地址" prop="projectUrl">
<el-input v-model="form.projectUrl" placeholder="请输入项目访问地址" />
</el-form-item>
<!-- 作用域选择 -->
<el-form-item label="作用域">
<el-radio-group v-model="form.scope" @change="handleScopeChange">
<el-radio
v-for="dict in filteredScopeOptions"
:key="Number(dict.value)"
:value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 所属租户 - 可搜索下拉框 -->
<el-form-item v-if="[2].includes(form.scope)" label="所属租户" prop="tenantBizId">
<el-select
v-model="form.tenantBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索租户"
:remote-method="searchTenants"
:loading="tenantLoading"
>
<el-option
v-for="item in tenantOptions"
:key="item.tenantBizId"
:label="item.tenantName"
:value="item.tenantBizId"
/> />
</el-form-item> </el-select>
<el-form-item label="状态" prop="status"> </el-form-item>
<el-select
v-model="queryParams.status" <!-- 所属项目 - 可搜索下拉框 -->
placeholder="项目状态" <el-form-item v-if="[3].includes(form.scope)" label="所属项目" prop="projectBizId">
clearable <el-select
style="width: 240px" v-model="form.projectBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索项目"
:remote-method="searchProjects"
:loading="projectLoading"
>
<el-option
v-for="item in projectOptions"
:key="item.projectBizId"
:label="item.projectName"
:value="item.projectBizId"
/>
</el-select>
</el-form-item>
<el-form-item label="项目描述">
<el-input
v-model="form.description"
type="textarea"
placeholder="请输入项目描述"
></el-input>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_status"
:key="Number(dict.value)"
:value="Number(dict.value)"
> >
<el-option {{ dict.label }}
v-for="dict in sys_status" </el-radio>
:key="dict.value" </el-radio-group>
:label="dict.label" </el-form-item>
:value="dict.value" <el-form-item
/> label="项目图标"
</el-select> prop="logoUrl"
</el-form-item> :rules="[{ required: true, message: '请上传项目图标', trigger: 'change' }]"
<el-form-item> >
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <image-upload
<el-button icon="Refresh" @click="resetQuery">重置</el-button> v-model="form.logoUrl"
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button> :action="'/oss/api/oss/upload'"
</el-form-item> :limit="1"
:file-size="3"
:file-type="['png', 'jpg', 'jpeg']"
:is-show-tip="true"
@change="handleImageChange"
/>
</el-form-item>
</el-form> </el-form>
<template #footer>
<!-- 表格数据 --> <div class="dialog-footer">
<el-table v-loading="loading" :data="projectList"> <el-button type="primary" @click="submitForm">确 定</el-button>
<el-table-column label="项目LOGO" align="center"> <el-button @click="cancel">取 消</el-button>
<template #default="scope"> </div>
<el-image </template>
v-if="scope.row.logoUrl" </el-dialog>
style="width: 40px; height: 40px; border-radius: 4px"
:src="scope.row.logoUrl" <!-- 分配角色数据权限对话框 -->
:preview-src-list="[scope.row.logoUrl]" <el-dialog :title="title" v-model="openDataScope" width="500px" append-to-body>
fit="cover" <el-form :model="form" label-width="80px">
preview-teleported <el-form-item label="角色名称">
> <el-input v-model="form.roleName" :disabled="true" />
<template #error> </el-form-item>
<div class="image-slot"> <el-form-item label="权限字符">
<el-icon><Picture /></el-icon> <el-input v-model="form.roleKey" :disabled="true" />
</div> </el-form-item>
</template> <el-form-item label="权限范围">
</el-image> <el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<span v-else>无LOGO</span> <el-option
</template> v-for="item in dataScopeOptions"
</el-table-column> :key="item.value"
<el-table-column label="项目名称" prop="projectName" /> :label="item.label"
<el-table-column prop="scope" label="作用域"> :value="item.value"
<template #default="scope"> ></el-option>
<dict-tag :options="sys_scope" :value="scope.row.scope" /> </el-select>
</template> </el-form-item>
</el-table-column> <el-form-item label="数据权限" v-show="form.dataScope == 2">
<el-table-column label="所属租户名称" prop="tenantName" /> <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')"
<el-table-column prop="isIn" label="是否内置项目"> >展开/折叠</el-checkbox
<template #default="scope"> >
<dict-tag :options="sys_no_yes" :value="scope.row.isIn" /> <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')"
</template> >全选/全不选</el-checkbox
</el-table-column> >
<el-table-column label="状态" align="center"> <el-checkbox
<template #default="scope"> v-model="form.deptCheckStrictly"
<el-switch @change="handleCheckedTreeConnect($event, 'dept')"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1" >父子联动</el-checkbox
v-model="scope.row.status" >
:active-value="1" <el-tree
:inactive-value="0" class="tree-border"
@change="(val) => handleStatusChange(scope.row, $event)" :data="deptOptions"
></el-switch> show-checkbox
<dict-tag :options="sys_status" :value="scope.row.status" default-expand-all
v-if="userStore.isSuperAdmin === 0 && scope.row.scope === 1"/> ref="deptRef"
</template> node-key="id"
</el-table-column> :check-strictly="!form.deptCheckStrictly"
<el-table-column label="项目开始时间" align="center" prop="startTime"> empty-text="加载中,请稍候"
<template #default="scope"> :props="{ label: 'label', children: 'children' }"
<span>{{ parseTime(scope.row.startTime) }}</span> ></el-tree>
</template> </el-form-item>
</el-table-column> </el-form>
<el-table-column label="项目结束时间" align="center" prop="endTime"> <template #footer>
<template #default="scope"> <div class="dialog-footer">
<span>{{ parseTime(scope.row.endTime) }}</span> <el-button type="primary" @click="submitDataScope">确 定</el-button>
</template> <el-button @click="cancelDataScope">取 消</el-button>
</el-table-column> </div>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200px"> </template>
<template #default="scope"> </el-dialog>
<el-button link type="primary" icon="Edit" @click="handlePermission(scope.row)" </div>
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">分配权限</el-button>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">修改</el-button>
<!-- <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)" >详情</el-button>-->
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改项目对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="projectRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="项目名称" prop="projectName">
<el-input v-model="form.projectName" placeholder="请输入项目名称" />
</el-form-item>
<el-form-item label="项目开始时间" prop="startTime">
<el-date-picker
v-model="form.startTime"
type="datetime"
placeholder="选择项目开始时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="项目结束时间" prop="endTime">
<el-date-picker
v-model="form.endTime"
type="datetime"
placeholder="选择项目结束时间"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="是否内置项目">
<el-radio-group v-model="form.isIn">
<el-radio
v-for="dict in sys_no_yes"
:key="Number(dict.value)"
:value="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="项目访问地址" prop="projectUrl">
<el-input v-model="form.projectUrl" placeholder="请输入项目访问地址" />
</el-form-item>
<!-- 作用域选择 -->
<el-form-item label="作用域">
<el-radio-group v-model="form.scope" @change="handleScopeChange">
<el-radio
v-for="dict in filteredScopeOptions"
:key="Number(dict.value)"
:value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<!-- 所属租户 - 可搜索下拉框 -->
<el-form-item
v-if="[2].includes(form.scope)"
label="所属租户"
prop="tenantBizId"
>
<el-select
v-model="form.tenantBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索租户"
:remote-method="searchTenants"
:loading="tenantLoading"
>
<el-option
v-for="item in tenantOptions"
:key="item.tenantBizId"
:label="item.tenantName"
:value="item.tenantBizId"
/>
</el-select>
</el-form-item>
<!-- 所属项目 - 可搜索下拉框 -->
<el-form-item
v-if="[3].includes(form.scope)"
label="所属项目"
prop="projectBizId"
>
<el-select
v-model="form.projectBizId"
filterable
remote
reserve-keyword
placeholder="请输入关键词搜索项目"
:remote-method="searchProjects"
:loading="projectLoading"
>
<el-option
v-for="item in projectOptions"
:key="item.projectBizId"
:label="item.projectName"
:value="item.projectBizId"
/>
</el-select>
</el-form-item>
<el-form-item label="项目描述">
<el-input v-model="form.description" type="textarea" placeholder="请输入项目描述"></el-input>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_status"
:key="Number(dict.value)"
:value="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="项目图标" prop="logoUrl">
<el-input v-model="form.logoUrl" placeholder="请输入项目图标" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</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
v-for="item in dataScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="数据权限" v-show="form.dataScope == 2">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">展开/折叠</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, '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="{ label: 'label', children: 'children' }"
></el-tree>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitDataScope">确 定</el-button>
<el-button @click="cancelDataScope">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template> </template>
<script setup name="Project"> <script setup name="Project">
import { computed } from 'vue' import { computed } from 'vue'
import { addProject, changeRoleStatus, dataScope, delRole, getProject, listProject, updateProject, deptTreeSelect,searchScopeList,changeProjectStatus } from "@/api/system/project" import {
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu" addProject,
changeRoleStatus,
dataScope,
delRole,
getProject,
listProject,
updateProject,
deptTreeSelect,
searchScopeList,
changeProjectStatus
} from '@/api/system/project'
import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu'
import {
uploadImage //上传图片的接口
} from '@/api/common'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import {formatIsoToDateTime} from '@/utils/date' import { formatIsoToDateTime } from '@/utils/date'
import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组件
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_status,sys_scope,sys_no_yes } = proxy.useDict("sys_status","sys_scope","sys_no_yes") const { sys_status, sys_scope, sys_no_yes } = proxy.useDict('sys_status', 'sys_scope', 'sys_no_yes')
const projectList = ref([]) const projectList = ref([])
const open = ref(false) const open = ref(false)
...@@ -299,7 +373,7 @@ const ids = ref([]) ...@@ -299,7 +373,7 @@ const ids = ref([])
const single = ref(true) const single = ref(true)
const multiple = ref(true) const multiple = ref(true)
const total = ref(0) const total = ref(0)
const title = ref("") const title = ref('')
const dateRange = ref([]) const dateRange = ref([])
const menuOptions = ref([]) const menuOptions = ref([])
const menuExpand = ref(false) const menuExpand = ref(false)
...@@ -310,14 +384,15 @@ const deptOptions = ref([]) ...@@ -310,14 +384,15 @@ const deptOptions = ref([])
const openDataScope = ref(false) const openDataScope = ref(false)
const menuRef = ref(null) const menuRef = ref(null)
const deptRef = ref(null) const deptRef = ref(null)
//在data中添加一个用于跟踪图片上传状态的变量
const imageUploaded = ref(false)
/** 数据范围选项*/ /** 数据范围选项*/
const dataScopeOptions = ref([ const dataScopeOptions = ref([
{ value: "1", label: "全部数据权限" }, { value: '1', label: '全部数据权限' },
{ value: "2", label: "自定数据权限" }, { value: '2', label: '自定数据权限' },
{ value: "3", label: "本部门数据权限" }, { value: '3', label: '本部门数据权限' },
{ value: "4", label: "本部门及以下数据权限" }, { value: '4', label: '本部门及以下数据权限' },
{ value: "5", label: "仅本人数据权限" } { value: '5', label: '仅本人数据权限' }
]) ])
const data = reactive({ const data = reactive({
...@@ -332,13 +407,26 @@ const data = reactive({ ...@@ -332,13 +407,26 @@ const data = reactive({
status: undefined status: undefined
}, },
rules: { rules: {
projectName: [{ required: true, message: "项目名称不能为空", trigger: "blur" }], projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
status: [{ required: true, message: "状态不能为空", trigger: "blur" }], status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
startTime: [{ required: true, message: "项目开始时间不能为空", trigger: "blur" }], startTime: [{ required: true, message: '项目开始时间不能为空', trigger: 'blur' }],
endTime: [{ required: true, message: "项目结束时间不能为空", trigger: "blur" }], endTime: [{ required: true, message: '项目结束时间不能为空', trigger: 'blur' }],
scope: [{ required: true, message: "作用域不能为空", trigger: "blur" }], scope: [{ required: true, message: '作用域不能为空', trigger: 'blur' }],
logoUrl: [{ required: true, message: "项目图标地址不能为空", trigger: "blur" }], logoUrl: [{ required: true, message: '项目图标地址不能为空', trigger: 'blur' }],
isIn: [{ required: true, message: "是否内置项目不能为空", trigger: "blur" }], isIn: [{ required: true, message: '是否内置项目不能为空', trigger: 'blur' }],
logoUrl: [
{
required: true,
validator: (rule, value, callback) => {
if (!value || value.trim() === '') {
callback(new Error('请上传项目图标'))
} else {
callback()
}
},
trigger: 'change'
}
]
} }
}) })
...@@ -372,26 +460,34 @@ function handleQuery() { ...@@ -372,26 +460,34 @@ function handleQuery() {
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
proxy.resetForm("queryRef") proxy.resetForm('queryRef')
handleQuery() handleQuery()
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
function handleDelete(row) { function handleDelete(row) {
const roleIds = row.roleId || ids.value const roleIds = row.roleId || ids.value
proxy.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () { proxy.$modal
return delRole(roleIds) .confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?')
}).then(() => { .then(function () {
getList() return delRole(roleIds)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
function handleExport() { function handleExport() {
proxy.download("system/role/export", { proxy.download(
...queryParams.value, 'system/role/export',
}, `role_${new Date().getTime()}.xlsx`) {
...queryParams.value
},
`role_${new Date().getTime()}.xlsx`
)
} }
/** 多选框选中数据 */ /** 多选框选中数据 */
...@@ -403,24 +499,25 @@ function handleSelectionChange(selection) { ...@@ -403,24 +499,25 @@ function handleSelectionChange(selection) {
/** 项目状态修改 */ /** 项目状态修改 */
function handleStatusChange(row, event) { function handleStatusChange(row, event) {
let text = row.status === 0 ? "停用" : "启用" let text = row.status === 0 ? '停用' : '启用'
const projectName = row.projectName const projectName = row.projectName
proxy.$modal.confirm(`确认要${text}"${projectName}"项目吗?`) proxy.$modal
.then(() => changeProjectStatus({projectBizId: row.projectBizId, status: row.status})) .confirm(`确认要${text}"${projectName}"项目吗?`)
.then(() => proxy.$modal.msgSuccess(`${text}成功`)) .then(() => changeProjectStatus({ projectBizId: row.projectBizId, status: row.status }))
.catch(() => { .then(() => proxy.$modal.msgSuccess(`${text}成功`))
// 操作取消时恢复原状态 .catch(() => {
row.status = row.status === 0 ? 1 : 0 // 操作取消时恢复原状态
}) row.status = row.status === 0 ? 1 : 0
})
} }
/** 更多操作 */ /** 更多操作 */
function handleCommand(command, row) { function handleCommand(command, row) {
switch (command) { switch (command) {
case "handleDataScope": case 'handleDataScope':
handleDataScope(row) handleDataScope(row)
break break
case "handleAuthUser": case 'handleAuthUser':
handleAuthUser(row) handleAuthUser(row)
break break
default: default:
...@@ -458,14 +555,14 @@ function reset() { ...@@ -458,14 +555,14 @@ function reset() {
isIn: 0, isIn: 0,
projectUrl: undefined projectUrl: undefined
} }
proxy.resetForm("projectRef") proxy.resetForm('projectRef')
} }
/** 添加项目 */ /** 添加项目 */
function handleAdd() { function handleAdd() {
reset() reset()
open.value = true open.value = true
title.value = "添加项目" title.value = '添加项目'
} }
/** 修改项目 */ /** 修改项目 */
...@@ -473,30 +570,22 @@ function handleUpdate(row) { ...@@ -473,30 +570,22 @@ function handleUpdate(row) {
reset() reset()
const projectBizId = row.projectBizId const projectBizId = row.projectBizId
getProject(projectBizId).then(response => { getProject(projectBizId).then(response => {
const resData = response.data; const resData = response.data
form.value = { form.value = {
...resData, ...resData,
startTime: formatIsoToDateTime(resData.startTime), startTime: formatIsoToDateTime(resData.startTime),
endTime: formatIsoToDateTime(resData.endTime) endTime: formatIsoToDateTime(resData.endTime)
}; }
// 根据作用域类型预加载关联数据 // 根据作用域类型预加载关联数据
if (resData.scope === 2) { if (resData.scope === 2) {
tenantOptions.value.push({ tenantOptions.value.push({
tenantBizId: resData.tenantBizId, tenantBizId: resData.tenantBizId,
tenantName: resData.tenantName tenantName: resData.tenantName
}); })
} }
open.value = true open.value = true
}) })
title.value = "修改项目" title.value = '修改项目'
}
/** 根据角色ID查询菜单树结构 */
function getProjectMenuTreeselect(roleId) {
return roleMenuTreeselect(roleId).then(response => {
menuOptions.value = response.menus
return response
})
} }
/** 根据角色ID查询部门树结构 */ /** 根据角色ID查询部门树结构 */
...@@ -509,12 +598,12 @@ function getDeptTree(roleId) { ...@@ -509,12 +598,12 @@ function getDeptTree(roleId) {
/** 树权限(展开/折叠)*/ /** 树权限(展开/折叠)*/
function handleCheckedTreeExpand(value, type) { function handleCheckedTreeExpand(value, type) {
if (type == "menu") { if (type == 'menu') {
let treeList = menuOptions.value let treeList = menuOptions.value
for (let i = 0; i < treeList.length; i++) { for (let i = 0; i < treeList.length; i++) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value menuRef.value.store.nodesMap[treeList[i].id].expanded = value
} }
} else if (type == "dept") { } else if (type == 'dept') {
let treeList = deptOptions.value let treeList = deptOptions.value
for (let i = 0; i < treeList.length; i++) { for (let i = 0; i < treeList.length; i++) {
deptRef.value.store.nodesMap[treeList[i].id].expanded = value deptRef.value.store.nodesMap[treeList[i].id].expanded = value
...@@ -524,18 +613,18 @@ function handleCheckedTreeExpand(value, type) { ...@@ -524,18 +613,18 @@ function handleCheckedTreeExpand(value, type) {
/** 树权限(全选/全不选) */ /** 树权限(全选/全不选) */
function handleCheckedTreeNodeAll(value, type) { function handleCheckedTreeNodeAll(value, type) {
if (type == "menu") { if (type == 'menu') {
menuRef.value.setCheckedNodes(value ? menuOptions.value : []) menuRef.value.setCheckedNodes(value ? menuOptions.value : [])
} else if (type == "dept") { } else if (type == 'dept') {
deptRef.value.setCheckedNodes(value ? deptOptions.value : []) deptRef.value.setCheckedNodes(value ? deptOptions.value : [])
} }
} }
/** 树权限(父子联动) */ /** 树权限(父子联动) */
function handleCheckedTreeConnect(value, type) { function handleCheckedTreeConnect(value, type) {
if (type == "menu") { if (type == 'menu') {
form.value.menuCheckStrictly = value ? true : false form.value.menuCheckStrictly = value ? true : false
} else if (type == "dept") { } else if (type == 'dept') {
form.value.deptCheckStrictly = value ? true : false form.value.deptCheckStrictly = value ? true : false
} }
} }
...@@ -552,18 +641,20 @@ function getMenuAllCheckedKeys() { ...@@ -552,18 +641,20 @@ function getMenuAllCheckedKeys() {
/** 添加和修改项目提交按钮 */ /** 添加和修改项目提交按钮 */
function submitForm() { function submitForm() {
proxy.$refs["projectRef"].validate(valid => { proxy.$refs['projectRef'].validate(valid => {
if (valid) { if (valid) {
if (form.value.projectBizId) { if (form.value.projectBizId) {
updateProject(form.value).then(response => { updateProject(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功") proxy.$modal.msgSuccess('修改成功')
open.value = false open.value = false
reset()
getList() getList()
}) })
} else { } else {
addProject(form.value).then(response => { addProject(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功") proxy.$modal.msgSuccess('新增成功')
open.value = false open.value = false
reset()
getList() getList()
}) })
} }
...@@ -579,7 +670,7 @@ function cancel() { ...@@ -579,7 +670,7 @@ function cancel() {
/** 选择角色权限范围触发 */ /** 选择角色权限范围触发 */
function dataScopeSelectChange(value) { function dataScopeSelectChange(value) {
if (value !== "2") { if (value !== '2') {
deptRef.value.setCheckedKeys([]) deptRef.value.setCheckedKeys([])
} }
} }
...@@ -601,7 +692,7 @@ function handleDataScope(row) { ...@@ -601,7 +692,7 @@ function handleDataScope(row) {
}) })
}) })
}) })
title.value = "分配数据权限" title.value = '分配数据权限'
} }
/** 提交按钮(数据权限) */ /** 提交按钮(数据权限) */
...@@ -609,7 +700,7 @@ function submitDataScope() { ...@@ -609,7 +700,7 @@ function submitDataScope() {
if (form.value.roleId != undefined) { if (form.value.roleId != undefined) {
form.value.deptIds = getDeptAllCheckedKeys() form.value.deptIds = getDeptAllCheckedKeys()
dataScope(form.value).then(response => { dataScope(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功") proxy.$modal.msgSuccess('修改成功')
openDataScope.value = false openDataScope.value = false
getList() getList()
}) })
...@@ -622,7 +713,6 @@ function cancelDataScope() { ...@@ -622,7 +713,6 @@ function cancelDataScope() {
reset() reset()
} }
//========作用域-切换逻辑开始========= //========作用域-切换逻辑开始=========
const tenantOptions = ref([]) const tenantOptions = ref([])
const tenantLoading = ref(false) const tenantLoading = ref(false)
...@@ -630,61 +720,164 @@ const projectOptions = ref([]) ...@@ -630,61 +720,164 @@ const projectOptions = ref([])
const projectLoading = ref(false) const projectLoading = ref(false)
// 作用域变更处理 // 作用域变更处理
function handleScopeChange(val) { function handleScopeChange(val) {
console.log('作用域变更:', val); console.log('作用域变更:', val)
// 清空不需要的字段值 // 清空不需要的字段值
if (val === 2) { if (val === 2) {
form.value.projectBizId = ''; form.value.projectBizId = ''
// 自动加载租户列表(可选) // 自动加载租户列表(可选)
searchTenants(''); searchTenants('')
} else if (val === 3) { } else if (val === 3) {
form.value.tenantBizId = ''; form.value.tenantBizId = ''
// 自动加载项目列表(可选) // 自动加载项目列表(可选)
searchProjects(''); searchProjects('')
} else if (val === 1) { } else if (val === 1) {
//系统级 //系统级
form.value.tenantBizId = ''; form.value.tenantBizId = ''
form.value.projectBizId = ''; form.value.projectBizId = ''
} }
} }
// 搜索租户方法 // 搜索租户方法
function searchTenants(query) { function searchTenants(query) {
tenantLoading.value = true; tenantLoading.value = true
try { try {
const params = { const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId, loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 2,name: query, pageNo:1, pageSize: 10 }; scope: 2,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => { searchScopeList(params).then(response => {
tenantOptions.value = response.data.records tenantOptions.value = response.data.records
}) })
} catch (error) { } catch (error) {
console.error('租户搜索失败', error); console.error('租户搜索失败', error)
tenantOptions.value = []; tenantOptions.value = []
} finally { } finally {
tenantLoading.value = false; tenantLoading.value = false
} }
} }
// 搜索项目方法 // 搜索项目方法
function searchProjects(query) { function searchProjects(query) {
projectLoading.value = true; projectLoading.value = true
try { try {
const params = { const params = {
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId, loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
scope: 3,name: query, pageNo:1, pageSize: 10 }; scope: 3,
name: query,
pageNo: 1,
pageSize: 10
}
searchScopeList(params).then(response => { searchScopeList(params).then(response => {
projectOptions.value = response.data.records projectOptions.value = response.data.records
}) })
} catch (error) { } catch (error) {
console.error('项目搜索失败', error); console.error('项目搜索失败', error)
projectOptions.value = []; projectOptions.value = []
} finally { } finally {
projectLoading.value = false; projectLoading.value = false
} }
} }
//========作用域-切换逻辑结束========= //========作用域-切换逻辑结束=========
// 添加图片变更处理函数
function handleImageChange(value) {
imageUploaded.value = !!value
// 手动触发表单验证
if (proxy.$refs.form) {
proxy.$refs.form.validateField('picture')
}
}
getList() getList()
</script> </script>
<style scoped lang="scss">
.logo-image {
width: 70px;
height: 70px;
border-radius: 4px;
object-fit: cover;
}
.avatarBox {
display: flex;
flex-direction: column;
width: 100%;
.avatar-uploader {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-uploader:hover {
border-color: var(--el-color-primary);
}
.avatar-uploader-icon {
font-size: 40px;
color: #8c939d;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 50px;
}
.avatar {
width: 80%;
height: 80%;
object-fit: contain;
}
.upload-progress {
margin-top: 10px;
width: 50%;
}
.progress-text {
display: block;
text-align: left;
margin-top: 5px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.el-upload__tip {
margin-top: 10px;
color: var(--el-text-color-secondary);
font-size: 12px;
}
.deleteIcon {
color: #fff;
font-size: 18px;
cursor: pointer;
}
.avatar-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
border-radius: 6px;
}
.avatar-overlay:hover {
opacity: 1;
}
}
</style>
<template> <template>
<div class="app-container"> <div class="app-container">
<!-- 选项卡组件:增加 custom-tabs 类名 --> <!-- 选项卡组件:增加 custom-tabs 类名 -->
<el-tabs <el-tabs v-model="activeTab" type="card" @tab-change="handleTabChange" class="custom-tabs">
v-model="activeTab"
type="card"
@tab-change="handleTabChange"
class="custom-tabs"
>
<!-- 用户权限选项卡 --> <!-- 用户权限选项卡 -->
<el-tab-pane label="用户" name="user" class="tab-pane-content"> <el-tab-pane label="用户" name="user" class="tab-pane-content">
<el-form :model="userQueryParams" ref="userQueryRef" v-show="userShowSearch" :inline="true" label-width="68px"> <el-form
:model="userQueryParams"
ref="userQueryRef"
v-show="userShowSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="账号" prop="userName"> <el-form-item label="账号" prop="userName">
<el-input <el-input
v-model="userQueryParams.userName" v-model="userQueryParams.userName"
placeholder="请输入账号" placeholder="请输入账号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="userHandleQuery" @keyup.enter="userHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="姓名" prop="realName"> <el-form-item label="姓名" prop="realName">
<el-input <el-input
v-model="userQueryParams.realName" v-model="userQueryParams.realName"
placeholder="请输入姓名" placeholder="请输入姓名"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="userHandleQuery" @keyup.enter="userHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="手机号" prop="mobile"> <el-form-item label="手机号" prop="mobile">
<el-input <el-input
v-model="userQueryParams.mobile" v-model="userQueryParams.mobile"
placeholder="请输入手机号" placeholder="请输入手机号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="userHandleQuery" @keyup.enter="userHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="userHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="userHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="userResetQuery">重置</el-button> <el-button icon="Refresh" @click="userResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportProjectUserList">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImportProjectUserList"
>导入</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 表格数据 --> <!-- 表格数据 -->
...@@ -55,59 +58,85 @@ ...@@ -55,59 +58,85 @@
<dict-tag :options="sys_gender" :value="scope.row.gender" /> <dict-tag :options="sys_gender" :value="scope.row.gender" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px"> <el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="160px"
>
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)" >分配角色</el-button> <el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)"
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)" >删除</el-button> >分配角色</el-button
>
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="userTotal > 0" v-show="userTotal > 0"
:total="userTotal" :total="userTotal"
v-model:page="userQueryParams.pageNum" v-model:page="userQueryParams.pageNum"
v-model:limit="userQueryParams.pageSize" v-model:limit="userQueryParams.pageSize"
@pagination="getUserList" @pagination="getUserList"
/> />
<!-- 用户导入(根据权限从用户池导入进来) --> <!-- 用户导入(根据权限从用户池导入进来) -->
<el-dialog :title="importUserTitle" v-model="importProjectUserListOpen" width="800px" append-to-body> <el-dialog
<el-form :model="importProjectUserListQueryParams" ref="importProjectUserListQueryRef" v-show="importProjectUserListShowSearch" :inline="true" label-width="55px"> :title="importUserTitle"
v-model="importProjectUserListOpen"
width="800px"
append-to-body
>
<el-form
:model="importProjectUserListQueryParams"
ref="importProjectUserListQueryRef"
v-show="importProjectUserListShowSearch"
:inline="true"
label-width="55px"
>
<el-form-item label="账号" prop="userName"> <el-form-item label="账号" prop="userName">
<el-input <el-input
v-model="importProjectUserListQueryParams.userName" v-model="importProjectUserListQueryParams.userName"
placeholder="请输入账号" placeholder="请输入账号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importProjectUserListHandleQuery" @keyup.enter="importProjectUserListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="姓名" prop="realName"> <el-form-item label="姓名" prop="realName">
<el-input <el-input
v-model="importProjectUserListQueryParams.realName" v-model="importProjectUserListQueryParams.realName"
placeholder="请输入姓名" placeholder="请输入姓名"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importProjectUserListHandleQuery" @keyup.enter="importProjectUserListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="手机号" prop="mobile"> <el-form-item label="手机号" prop="mobile">
<el-input <el-input
v-model="importProjectUserListQueryParams.mobile" v-model="importProjectUserListQueryParams.mobile"
placeholder="请输入手机号" placeholder="请输入手机号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importProjectUserListHandleQuery" @keyup.enter="importProjectUserListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="importProjectUserListHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="importProjectUserListHandleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="importProjectUserListResetQuery">重置</el-button> <el-button icon="Refresh" @click="importProjectUserListResetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 表格数据 --> <!-- 表格数据 -->
<el-table v-loading="importProjectUserListLoading" :data="importProjectUserList" @selection-change="importProjectUserListHandleSelectionChange"> <el-table
v-loading="importProjectUserListLoading"
:data="importProjectUserList"
@selection-change="importProjectUserListHandleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="账号" prop="userName" /> <el-table-column label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" /> <el-table-column label="姓名" prop="realName" />
...@@ -121,11 +150,11 @@ ...@@ -121,11 +150,11 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="importProjectUserListTotal > 0" v-show="importProjectUserListTotal > 0"
:total="importProjectUserListTotal" :total="importProjectUserListTotal"
v-model:page="importProjectUserListQueryParams.pageNum" v-model:page="importProjectUserListQueryParams.pageNum"
v-model:limit="importProjectUserListQueryParams.pageSize" v-model:limit="importProjectUserListQueryParams.pageSize"
@pagination="getImportProjectUserList" @pagination="getImportProjectUserList"
/> />
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
...@@ -137,35 +166,43 @@ ...@@ -137,35 +166,43 @@
<!-- 用户分配角色弹窗 --> <!-- 用户分配角色弹窗 -->
<el-dialog <el-dialog
:title="assignRoleTitle" :title="assignRoleTitle"
v-model="assignRoleDialogVisible" v-model="assignRoleDialogVisible"
width="1100px" width="1100px"
append-to-body append-to-body
> >
<!-- 弹窗内容:左侧可选角色 + 中间箭头 + 右侧已选角色 --> <!-- 弹窗内容:左侧可选角色 + 中间箭头 + 右侧已选角色 -->
<div class="assign-role-container"> <div class="assign-role-container">
<!-- 左侧:可选角色列表(带复选框) --> <!-- 左侧:可选角色列表(带复选框) -->
<div class="left-panel"> <div class="left-panel">
<el-form :model="leftQuery" ref="leftQueryRef" :inline="true" label-width="68px" class="search-form"> <el-form
:model="leftQuery"
ref="leftQueryRef"
:inline="true"
label-width="68px"
class="search-form"
>
<el-form-item label="角色名称"> <el-form-item label="角色名称">
<el-input <el-input
v-model="leftQuery.roleName" v-model="leftQuery.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
@keyup.enter="getLeftRoleList" @keyup.enter="getLeftRoleList"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="queryLeftRoleList">搜索</el-button> <el-button type="primary" icon="Search" @click="queryLeftRoleList"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetLeftRoleQuery">重置</el-button> <el-button icon="Refresh" @click="resetLeftRoleQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table <el-table
v-loading="leftLoading" v-loading="leftLoading"
:data="leftRoleList" :data="leftRoleList"
@selection-change="handleLeftSelectionChange" @selection-change="handleLeftSelectionChange"
border border
> >
<el-table-column type="selection" align="center" /> <el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" /> <el-table-column label="角色名称" prop="roleName" />
...@@ -182,11 +219,11 @@ ...@@ -182,11 +219,11 @@
</el-table> </el-table>
<pagination <pagination
v-show="leftTotal > 0" v-show="leftTotal > 0"
:total="leftTotal" :total="leftTotal"
v-model:page="leftQuery.pageNo" v-model:page="leftQuery.pageNo"
v-model:limit="leftQuery.pageSize" v-model:limit="leftQuery.pageSize"
@pagination="getLeftRoleList" @pagination="getLeftRoleList"
/> />
</div> </div>
...@@ -196,20 +233,20 @@ ...@@ -196,20 +233,20 @@
<!-- 向右箭头:添加到右侧 --> <!-- 向右箭头:添加到右侧 -->
<el-tooltip content="添加选中角色到已选列表" placement="top"> <el-tooltip content="添加选中角色到已选列表" placement="top">
<el-button <el-button
icon="ArrowRight" icon="ArrowRight"
@click="moveToRight" @click="moveToRight"
:disabled="leftSelectedRoles.length === 0" :disabled="leftSelectedRoles.length === 0"
class="arrow-btn" class="arrow-btn"
/> />
</el-tooltip> </el-tooltip>
<!-- 向左箭头:移除到左侧 --> <!-- 向左箭头:移除到左侧 -->
<el-tooltip content="移除选中角色到可选列表" placement="top"> <el-tooltip content="移除选中角色到可选列表" placement="top">
<el-button <el-button
icon="ArrowLeft" icon="ArrowLeft"
@click="moveToLeft" @click="moveToLeft"
:disabled="rightSelectedRoles.length === 0" :disabled="rightSelectedRoles.length === 0"
class="arrow-btn" class="arrow-btn"
/> />
</el-tooltip> </el-tooltip>
</div> </div>
...@@ -219,26 +256,34 @@ ...@@ -219,26 +256,34 @@
<div class="right-panel"> <div class="right-panel">
<div class="selected-title">已选角色({{ rightRoleList.length }}个)</div> <div class="selected-title">已选角色({{ rightRoleList.length }}个)</div>
<el-form :model="rightQuery" ref="rightQueryRef" :inline="true" label-width="68px" class="search-form"> <el-form
:model="rightQuery"
ref="rightQueryRef"
:inline="true"
label-width="68px"
class="search-form"
>
<el-form-item label="角色名称"> <el-form-item label="角色名称">
<el-input <el-input
v-model="rightQuery.roleName" v-model="rightQuery.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
@keyup.enter="getRightRoleList" @keyup.enter="getRightRoleList"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="queryRightRoleList">搜索</el-button> <el-button type="primary" icon="Search" @click="queryRightRoleList"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetRightRoleQuery">重置</el-button> <el-button icon="Refresh" @click="resetRightRoleQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table <el-table
v-loading="rightLoading" v-loading="rightLoading"
:data="rightRoleList" :data="rightRoleList"
@selection-change="handleRightSelectionChange" @selection-change="handleRightSelectionChange"
border border
> >
<el-table-column type="selection" align="center" /> <el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" /> <el-table-column label="角色名称" prop="roleName" />
...@@ -255,11 +300,11 @@ ...@@ -255,11 +300,11 @@
</el-table> </el-table>
<pagination <pagination
v-show="rightTotal > 0" v-show="rightTotal > 0"
:total="rightTotal" :total="rightTotal"
v-model:page="rightQuery.pageNo" v-model:page="rightQuery.pageNo"
v-model:limit="rightQuery.pageSize" v-model:limit="rightQuery.pageSize"
@pagination="getRightRoleList" @pagination="getRightRoleList"
/> />
</div> </div>
</div> </div>
...@@ -275,20 +320,28 @@ ...@@ -275,20 +320,28 @@
</el-tab-pane> </el-tab-pane>
<!-- 角色权限选项卡 --> <!-- 角色权限选项卡 -->
<el-tab-pane label="角色" name="role" class="tab-pane-content"> <el-tab-pane label="角色" name="role" class="tab-pane-content">
<el-form :model="roleQueryParams" ref="roleQueryRef" v-show="roleShowSearch" :inline="true" label-width="68px"> <el-form
:model="roleQueryParams"
ref="roleQueryRef"
v-show="roleShowSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="角色名称" prop="roleName"> <el-form-item label="角色名称" prop="roleName">
<el-input <el-input
v-model="roleQueryParams.roleName" v-model="roleQueryParams.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="roleHandleQuery" @keyup.enter="roleHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="roleResetQuery">重置</el-button> <el-button icon="Refresh" @click="roleResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportProjectRoleList">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImportProjectRoleList"
>导入</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -305,41 +358,67 @@ ...@@ -305,41 +358,67 @@
<dict-tag :options="sys_role_type" :value="scope.row.roleType" /> <dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px"> <el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="160px"
>
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)" >分配菜单</el-button> <el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)"
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)" >删除</el-button> >分配菜单</el-button
>
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="roleTotal > 0" v-show="roleTotal > 0"
:total="roleTotal" :total="roleTotal"
v-model:page="roleQueryParams.pageNum" v-model:page="roleQueryParams.pageNum"
v-model:limit="roleQueryParams.pageSize" v-model:limit="roleQueryParams.pageSize"
@pagination="getRoleList" @pagination="getRoleList"
/> />
<!-- 角色导入(根据权限从角色池导入进来) --> <!-- 角色导入(根据权限从角色池导入进来) -->
<el-dialog :title="importRoleTitle" v-model="importProjectRoleListOpen" width="800px" append-to-body> <el-dialog
<el-form :model="importProjectRoleListQueryParams" ref="importProjectRoleListQueryRef" v-show="importProjectRoleListShowSearch" :inline="true" label-width="70px"> :title="importRoleTitle"
v-model="importProjectRoleListOpen"
width="800px"
append-to-body
>
<el-form
:model="importProjectRoleListQueryParams"
ref="importProjectRoleListQueryRef"
v-show="importProjectRoleListShowSearch"
:inline="true"
label-width="70px"
>
<el-form-item label="角色名称" prop="roleName"> <el-form-item label="角色名称" prop="roleName">
<el-input <el-input
v-model="importProjectRoleListQueryParams.roleName" v-model="importProjectRoleListQueryParams.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importProjectRoleListHandleQuery" @keyup.enter="importProjectRoleListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="importProjectRoleListHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="importProjectRoleListHandleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="importProjectRoleListResetQuery">重置</el-button> <el-button icon="Refresh" @click="importProjectRoleListResetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 表格数据 --> <!-- 表格数据 -->
<el-table v-loading="importProjectRoleListLoading" :data="importProjectRoleList" @selection-change="importProjectRoleListHandleSelectionChange"> <el-table
v-loading="importProjectRoleListLoading"
:data="importProjectRoleList"
@selection-change="importProjectRoleListHandleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="roleName" /> <el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="scope" label="作用域"> <el-table-column prop="scope" label="作用域">
...@@ -354,11 +433,11 @@ ...@@ -354,11 +433,11 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="importProjectRoleListTotal > 0" v-show="importProjectRoleListTotal > 0"
:total="importProjectRoleListTotal" :total="importProjectRoleListTotal"
v-model:page="importProjectRoleListQueryParams.pageNum" v-model:page="importProjectRoleListQueryParams.pageNum"
v-model:limit="importProjectRoleListQueryParams.pageSize" v-model:limit="importProjectRoleListQueryParams.pageSize"
@pagination="getImportProjectRoleList" @pagination="getImportProjectRoleList"
/> />
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
...@@ -374,15 +453,15 @@ ...@@ -374,15 +453,15 @@
<div class="permission-section"> <div class="permission-section">
<el-card header="分配菜单" class="mt-4"> <el-card header="分配菜单" class="mt-4">
<el-tree <el-tree
ref="fpMenuRef" ref="fpMenuRef"
:data="fpMenuTree" :data="fpMenuTree"
:props="fpMenuProps" :props="fpMenuProps"
node-key="menuBizId" node-key="menuBizId"
show-checkbox show-checkbox
highlight-current highlight-current
:check-strictly="fpMenuIsCheckStrictly" :check-strictly="fpMenuIsCheckStrictly"
:expand-on-click-node="false" :expand-on-click-node="false"
class="permission-tree" class="permission-tree"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
...@@ -400,44 +479,54 @@ ...@@ -400,44 +479,54 @@
</el-tab-pane> </el-tab-pane>
<!-- 菜单权限选项卡 --> <!-- 菜单权限选项卡 -->
<el-tab-pane label="菜单" name="menu" class="tab-pane-content"> <el-tab-pane label="菜单" name="menu" class="tab-pane-content">
<el-form :model="menuQueryParams" ref="menuQueryRef" v-show="menuShowSearch" :inline="true" label-width="68px"> <el-form
:model="menuQueryParams"
ref="menuQueryRef"
v-show="menuShowSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="菜单名称" prop="menuName"> <el-form-item label="菜单名称" prop="menuName">
<el-input <el-input
v-model="menuQueryParams.menuName" v-model="menuQueryParams.menuName"
placeholder="请输入菜单名称" placeholder="请输入菜单名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="menuHandleQuery" @keyup.enter="menuHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="menuHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="menuHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="menuResetQuery">重置</el-button> <el-button icon="Refresh" @click="menuResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportProjectMenuList">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImportProjectMenuList"
>导入</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col> </el-col>
<right-toolbar v-model:showSearch="menuShowSearch" @queryTable="getMenuList"></right-toolbar> <right-toolbar
v-model:showSearch="menuShowSearch"
@queryTable="getMenuList"
></right-toolbar>
</el-row> </el-row>
<el-table <el-table
v-if="refreshTable" v-if="refreshTable"
v-loading="menuLoading" v-loading="menuLoading"
:data="menuList" :data="menuList"
row-key="menuBizId" row-key="menuBizId"
:default-expand-all="isExpandAll" :default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
> >
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true"></el-table-column> <el-table-column
prop="menuName"
label="菜单名称"
:show-overflow-tooltip="true"
></el-table-column>
<el-table-column prop="icon" label="图标" align="center"> <el-table-column prop="icon" label="图标" align="center">
<template #default="scope"> <template #default="scope">
<svg-icon :icon-class="scope.row.icon" /> <svg-icon :icon-class="scope.row.icon" />
...@@ -455,27 +544,38 @@ ...@@ -455,27 +544,38 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-if="scope.row.parentBizId === '0'">删除</el-button> <el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-if="scope.row.parentBizId === '0'"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 菜单导入(根据权限从菜单池导入进来) --> <!-- 菜单导入(根据权限从菜单池导入进来) -->
<el-dialog :title="importMenuTitle" v-model="importProjectMenuListOpen" width="500px" append-to-body> <el-dialog
:title="importMenuTitle"
v-model="importProjectMenuListOpen"
width="500px"
append-to-body
>
<!-- 权限分配区域 --> <!-- 权限分配区域 -->
<div class="permission-section"> <div class="permission-section">
<el-card header="导入菜单" class="mt-4"> <el-card header="导入菜单" class="mt-4">
<el-tree <el-tree
ref="treeRef" ref="treeRef"
:data="menuTree" :data="menuTree"
:props="defaultProps" :props="defaultProps"
node-key="menuBizId" node-key="menuBizId"
show-checkbox show-checkbox
highlight-current highlight-current
:check-strictly="isCheckStrictly" :check-strictly="isCheckStrictly"
:expand-on-click-node="false" :expand-on-click-node="false"
class="permission-tree" class="permission-tree"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
...@@ -496,18 +596,48 @@ ...@@ -496,18 +596,48 @@
</template> </template>
<script setup name="ProjectPermission"> <script setup name="ProjectPermission">
import {
import {listProjectUser,listImportProjectUser, listProjectUser,
addImportProjectUserList,delRelProjectUser,listProjectRole, listImportProjectUser,
delRelProjectRole,addImportProjectRoleList,listImportProjectRole, addImportProjectUserList,
listMenu,getMenuTree,getImportSelectedMenuList,addImportProjectMenuList, delRelProjectUser,
listLeftRole, listRightRole, addRightRoleList,delRightRoleList, listProjectRole,
getFpMenuTree,getSelectedFpMenuList,addFpMenuList} from "@/api/system/projectPermission" delRelProjectRole,
addImportProjectRoleList,
listImportProjectRole,
listMenu,
getMenuTree,
getImportSelectedMenuList,
addImportProjectMenuList,
listLeftRole,
listRightRole,
addRightRoleList,
delRightRoleList,
getFpMenuTree,
getSelectedFpMenuList,
addFpMenuList
} from '@/api/system/projectPermission'
import { ref } from 'vue' import { ref } from 'vue'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_status,sys_scope,sys_no_yes,sys_user_status,sys_gender,sys_role_type,sys_menu_type } = proxy.useDict("sys_status","sys_scope","sys_no_yes","sys_user_status","sys_gender","sys_role_type","sys_menu_type") const {
sys_status,
sys_scope,
sys_no_yes,
sys_user_status,
sys_gender,
sys_role_type,
sys_menu_type
} = proxy.useDict(
'sys_status',
'sys_scope',
'sys_no_yes',
'sys_user_status',
'sys_gender',
'sys_role_type',
'sys_menu_type'
)
const route = useRoute() const route = useRoute()
const activeTab = ref('user') const activeTab = ref('user')
...@@ -540,10 +670,10 @@ const userTotal = ref(0) ...@@ -540,10 +670,10 @@ const userTotal = ref(0)
const importProjectUserListTotal = ref(0) const importProjectUserListTotal = ref(0)
const roleTotal = ref(0) const roleTotal = ref(0)
const importProjectRoleListTotal = ref(0) const importProjectRoleListTotal = ref(0)
const title = ref("") const title = ref('')
const importUserTitle = ref("") const importUserTitle = ref('')
const importRoleTitle = ref("") const importRoleTitle = ref('')
const importMenuTitle = ref("") const importMenuTitle = ref('')
const importProjectProjectListOpen = ref(false) const importProjectProjectListOpen = ref(false)
const importProjectUserListOpen = ref(false) const importProjectUserListOpen = ref(false)
const importProjectRoleListOpen = ref(false) const importProjectRoleListOpen = ref(false)
...@@ -555,7 +685,6 @@ const importProjectUserListNames = ref([]) ...@@ -555,7 +685,6 @@ const importProjectUserListNames = ref([])
const importProjectRoleListIds = ref([]) const importProjectRoleListIds = ref([])
const importProjectRoleListNames = ref([]) const importProjectRoleListNames = ref([])
// 菜单树数据(直接从接口获取树形结构) // 菜单树数据(直接从接口获取树形结构)
const menuTree = ref([]) const menuTree = ref([])
// 当前项目绑定的选中菜单ID // 当前项目绑定的选中菜单ID
...@@ -563,7 +692,7 @@ const checkedKeys = ref([]) ...@@ -563,7 +692,7 @@ const checkedKeys = ref([])
// 树组件引用 // 树组件引用
const treeRef = ref(null) const treeRef = ref(null)
// 添加响应式变量控制严格模式 // 添加响应式变量控制严格模式
const isCheckStrictly = ref(false); // 默认关闭严格模式(启用联动) const isCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置 // 树形配置
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
...@@ -619,10 +748,17 @@ const data = reactive({ ...@@ -619,10 +748,17 @@ const data = reactive({
menuName: undefined menuName: undefined
} }
}) })
const { queryParams,importProjectProjectListQueryParams, userQueryParams, const {
importProjectUserListQueryParams,roleQueryParams,importProjectRoleListQueryParams, queryParams,
menuQueryParams,form, rules } = toRefs(data) importProjectProjectListQueryParams,
userQueryParams,
importProjectUserListQueryParams,
roleQueryParams,
importProjectRoleListQueryParams,
menuQueryParams,
form,
rules
} = toRefs(data)
//========用户-列表逻辑开始========= //========用户-列表逻辑开始=========
/** 查询项目用户关系列表 */ /** 查询项目用户关系列表 */
...@@ -641,7 +777,7 @@ function userHandleQuery() { ...@@ -641,7 +777,7 @@ function userHandleQuery() {
} }
/** 用户列表-重置按钮操作 */ /** 用户列表-重置按钮操作 */
function userResetQuery() { function userResetQuery() {
proxy.resetForm("userQueryRef") proxy.resetForm('userQueryRef')
userHandleQuery() userHandleQuery()
} }
/** 删除项目用户关系 */ /** 删除项目用户关系 */
...@@ -649,16 +785,19 @@ function userHandleDelete(row) { ...@@ -649,16 +785,19 @@ function userHandleDelete(row) {
//项目和用户关系表主键id //项目和用户关系表主键id
const relProjectUserId = row.id const relProjectUserId = row.id
const userName = row.userName const userName = row.userName
proxy.$modal.confirm('是否确认删除用户账号为"' + userName + '"的项目和用户关系的数据项?').then(function () { proxy.$modal
return delRelProjectUser(relProjectUserId) .confirm('是否确认删除用户账号为"' + userName + '"的项目和用户关系的数据项?')
}).then(() => { .then(function () {
getUserList() return delRelProjectUser(relProjectUserId)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getUserList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
//========用户-列表逻辑结束========= //========用户-列表逻辑结束=========
//========用户-导入逻辑开始========= //========用户-导入逻辑开始=========
/** 用户列表-导入-查询导入项目用户关系列表 */ /** 用户列表-导入-查询导入项目用户关系列表 */
function getImportProjectUserList() { function getImportProjectUserList() {
...@@ -673,7 +812,7 @@ function getImportProjectUserList() { ...@@ -673,7 +812,7 @@ function getImportProjectUserList() {
function handleImportProjectUserList() { function handleImportProjectUserList() {
getImportProjectUserList() getImportProjectUserList()
importProjectUserListOpen.value = true importProjectUserListOpen.value = true
importUserTitle.value = "导入用户" importUserTitle.value = '导入用户'
} }
/** 用户列表-导入-搜索按钮操作 */ /** 用户列表-导入-搜索按钮操作 */
function importProjectUserListHandleQuery() { function importProjectUserListHandleQuery() {
...@@ -682,7 +821,7 @@ function importProjectUserListHandleQuery() { ...@@ -682,7 +821,7 @@ function importProjectUserListHandleQuery() {
} }
/** 用户列表-导入-重置按钮操作 */ /** 用户列表-导入-重置按钮操作 */
function importProjectUserListResetQuery() { function importProjectUserListResetQuery() {
proxy.resetForm("importProjectUserListQueryRef") proxy.resetForm('importProjectUserListQueryRef')
importProjectUserListHandleQuery() importProjectUserListHandleQuery()
} }
/** 用户列表-导入-多选框选中数据 */ /** 用户列表-导入-多选框选中数据 */
...@@ -696,22 +835,25 @@ function importProjectUserListSubmitForm() { ...@@ -696,22 +835,25 @@ function importProjectUserListSubmitForm() {
const importProjectUserListNameList = importProjectUserListNames.value const importProjectUserListNameList = importProjectUserListNames.value
const projectBizId = route.query.projectBizId const projectBizId = route.query.projectBizId
proxy.$modal.confirm('是否确认导入用户账号为"' + importProjectUserListNameList + '"的数据项?').then(function () { proxy.$modal
return addImportProjectUserList(importProjectUserListIdList,projectBizId) .confirm('是否确认导入用户账号为"' + importProjectUserListNameList + '"的数据项?')
}).then(() => { .then(function () {
importProjectUserListOpen.value = false return addImportProjectUserList(importProjectUserListIdList, projectBizId)
getUserList() })
proxy.$modal.msgSuccess("导入成功") .then(() => {
}).catch(() => {}) importProjectUserListOpen.value = false
getUserList()
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
} }
/** 用户列表-导入-取消 */ /** 用户列表-导入-取消 */
function importProjectUserListCancel() { function importProjectUserListCancel() {
importProjectUserListOpen.value = false importProjectUserListOpen.value = false
proxy.resetForm("importProjectUserListQueryRef") proxy.resetForm('importProjectUserListQueryRef')
} }
//========用户-导入逻辑结束========= //========用户-导入逻辑结束=========
//========角色-列表逻辑开始========= //========角色-列表逻辑开始=========
/** 查询项目角色关系列表 */ /** 查询项目角色关系列表 */
function getRoleList() { function getRoleList() {
...@@ -729,7 +871,7 @@ function roleHandleQuery() { ...@@ -729,7 +871,7 @@ function roleHandleQuery() {
} }
/** 角色列表-重置按钮操作 */ /** 角色列表-重置按钮操作 */
function roleResetQuery() { function roleResetQuery() {
proxy.resetForm("roleQueryRef") proxy.resetForm('roleQueryRef')
roleHandleQuery() roleHandleQuery()
} }
/** 删除项目角色关系 */ /** 删除项目角色关系 */
...@@ -737,16 +879,19 @@ function roleHandleDelete(row) { ...@@ -737,16 +879,19 @@ function roleHandleDelete(row) {
//项目和角色关系表主键id //项目和角色关系表主键id
const relProjectRoleId = row.id const relProjectRoleId = row.id
const roleName = row.roleName const roleName = row.roleName
proxy.$modal.confirm('是否确认删除角色名称为"' + roleName + '"的项目和角色关系的数据项?').then(function () { proxy.$modal
return delRelProjectRole(relProjectRoleId) .confirm('是否确认删除角色名称为"' + roleName + '"的项目和角色关系的数据项?')
}).then(() => { .then(function () {
getRoleList() return delRelProjectRole(relProjectRoleId)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getRoleList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
//========角色-列表逻辑结束========= //========角色-列表逻辑结束=========
//========角色-导入逻辑开始========= //========角色-导入逻辑开始=========
/** 角色列表-导入-查询导入项目角色关系列表 */ /** 角色列表-导入-查询导入项目角色关系列表 */
function getImportProjectRoleList() { function getImportProjectRoleList() {
...@@ -761,7 +906,7 @@ function getImportProjectRoleList() { ...@@ -761,7 +906,7 @@ function getImportProjectRoleList() {
function handleImportProjectRoleList() { function handleImportProjectRoleList() {
getImportProjectRoleList() getImportProjectRoleList()
importProjectRoleListOpen.value = true importProjectRoleListOpen.value = true
importRoleTitle.value = "导入角色" importRoleTitle.value = '导入角色'
} }
/** 角色列表-导入-搜索按钮操作 */ /** 角色列表-导入-搜索按钮操作 */
function importProjectRoleListHandleQuery() { function importProjectRoleListHandleQuery() {
...@@ -770,7 +915,7 @@ function importProjectRoleListHandleQuery() { ...@@ -770,7 +915,7 @@ function importProjectRoleListHandleQuery() {
} }
/** 角色列表-导入-重置按钮操作 */ /** 角色列表-导入-重置按钮操作 */
function importProjectRoleListResetQuery() { function importProjectRoleListResetQuery() {
proxy.resetForm("importProjectRoleListQueryRef") proxy.resetForm('importProjectRoleListQueryRef')
importProjectRoleListHandleQuery() importProjectRoleListHandleQuery()
} }
/** 角色列表-导入-多选框选中数据 */ /** 角色列表-导入-多选框选中数据 */
...@@ -784,28 +929,31 @@ function importProjectRoleListSubmitForm() { ...@@ -784,28 +929,31 @@ function importProjectRoleListSubmitForm() {
const importProjectRoleListNameList = importProjectRoleListNames.value const importProjectRoleListNameList = importProjectRoleListNames.value
const projectBizId = route.query.projectBizId const projectBizId = route.query.projectBizId
proxy.$modal.confirm('是否确认导入角色名称为"' + importProjectRoleListNameList + '"的数据项?').then(function () { proxy.$modal
return addImportProjectRoleList(importProjectRoleListIdList,projectBizId) .confirm('是否确认导入角色名称为"' + importProjectRoleListNameList + '"的数据项?')
}).then(() => { .then(function () {
importProjectRoleListOpen.value = false return addImportProjectRoleList(importProjectRoleListIdList, projectBizId)
getRoleList() })
proxy.$modal.msgSuccess("导入成功") .then(() => {
}).catch(() => {}) importProjectRoleListOpen.value = false
getRoleList()
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
} }
/** 角色列表-导入-取消 */ /** 角色列表-导入-取消 */
function importProjectRoleListCancel() { function importProjectRoleListCancel() {
importProjectRoleListOpen.value = false importProjectRoleListOpen.value = false
proxy.resetForm("importProjectRoleListQueryRef") proxy.resetForm('importProjectRoleListQueryRef')
} }
//========角色-导入逻辑结束========= //========角色-导入逻辑结束=========
//========菜单-列表逻辑结束========= //========菜单-列表逻辑结束=========
/** 查询菜单列表 */ /** 查询菜单列表 */
function getMenuList() { function getMenuList() {
menuLoading.value = true menuLoading.value = true
listMenu(menuQueryParams.value).then(response => { listMenu(menuQueryParams.value).then(response => {
menuList.value = proxy.handleTree(response.data.records, "menuBizId") menuList.value = proxy.handleTree(response.data.records, 'menuBizId')
menuLoading.value = false menuLoading.value = false
}) })
} }
...@@ -824,18 +972,17 @@ function menuHandleQuery() { ...@@ -824,18 +972,17 @@ function menuHandleQuery() {
} }
/** 菜单列表-重置按钮操作 */ /** 菜单列表-重置按钮操作 */
function menuResetQuery() { function menuResetQuery() {
proxy.resetForm("menuQueryRef") proxy.resetForm('menuQueryRef')
menuHandleQuery() menuHandleQuery()
} }
//========菜单-列表逻辑结束========= //========菜单-列表逻辑结束=========
//========菜单-导入逻辑开始========= //========菜单-导入逻辑开始=========
/** 菜单列表-导入菜单 */ /** 菜单列表-导入菜单 */
function handleImportProjectMenuList() { function handleImportProjectMenuList() {
loadMenuTree() loadMenuTree()
importProjectMenuListOpen.value = true importProjectMenuListOpen.value = true
importMenuTitle.value = "导入菜单" importMenuTitle.value = '导入菜单'
} }
// 加载菜单树 // 加载菜单树
const loadMenuTree = async () => { const loadMenuTree = async () => {
...@@ -851,32 +998,31 @@ const loadMenuTree = async () => { ...@@ -851,32 +998,31 @@ const loadMenuTree = async () => {
// 修改加载选中菜单列表的逻辑 // 修改加载选中菜单列表的逻辑
const loadImportSelectedMenuList = async () => { const loadImportSelectedMenuList = async () => {
try { try {
const res = await getImportSelectedMenuList(route.query.projectBizId); const res = await getImportSelectedMenuList(route.query.projectBizId)
const targetKeys = res.data || []; const targetKeys = res.data || []
// 开启严格模式(禁用联动) // 开启严格模式(禁用联动)
isCheckStrictly.value = true; isCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效) // 等待DOM更新(确保严格模式生效)
await nextTick(); await nextTick()
//清空并设置选中状态(此时不会触发联动) //清空并设置选中状态(此时不会触发联动)
if (treeRef.value) { if (treeRef.value) {
treeRef.value.setCheckedKeys([]); treeRef.value.setCheckedKeys([])
treeRef.value.setCheckedKeys(targetKeys); treeRef.value.setCheckedKeys(targetKeys)
} }
//等待选中状态渲染完成 //等待选中状态渲染完成
await nextTick(); await nextTick()
//关闭严格模式(恢复联动) //关闭严格模式(恢复联动)
isCheckStrictly.value = false; isCheckStrictly.value = false
} catch (error) { } catch (error) {
console.error('加载选中的菜单列表失败:', error); console.error('加载选中的菜单列表失败:', error)
ElMessage.error('加载选中的菜单列表失败'); ElMessage.error('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态 // 异常时也要恢复严格模式状态
isCheckStrictly.value = false; isCheckStrictly.value = false
} }
} }
// 保存 // 保存
...@@ -897,7 +1043,6 @@ const saveImportSelectedMenuList = async () => { ...@@ -897,7 +1043,6 @@ const saveImportSelectedMenuList = async () => {
getMenuList() getMenuList()
importProjectMenuListOpen.value = false importProjectMenuListOpen.value = false
ElMessage.success('更新成功') ElMessage.success('更新成功')
} catch (error) { } catch (error) {
console.error('更新失败:', error) console.error('更新失败:', error)
ElMessage.error('更新失败') ElMessage.error('更新失败')
...@@ -905,12 +1050,10 @@ const saveImportSelectedMenuList = async () => { ...@@ -905,12 +1050,10 @@ const saveImportSelectedMenuList = async () => {
} }
//========菜单-导入逻辑结束========= //========菜单-导入逻辑结束=========
//========分配角色-导入逻辑开始========= //========分配角色-导入逻辑开始=========
// 弹窗显隐与标题 // 弹窗显隐与标题
const assignRoleDialogVisible = ref(false) const assignRoleDialogVisible = ref(false)
const assignRoleTitle = ref("分配角色") const assignRoleTitle = ref('分配角色')
// 左侧可选角色(查询参数 + 列表数据) // 左侧可选角色(查询参数 + 列表数据)
const leftQuery = reactive({ const leftQuery = reactive({
...@@ -925,7 +1068,6 @@ const leftLoading = ref(false) ...@@ -925,7 +1068,6 @@ const leftLoading = ref(false)
const leftTotal = ref(0) const leftTotal = ref(0)
const leftSelectedRoles = ref([]) // 左侧选中的角色 const leftSelectedRoles = ref([]) // 左侧选中的角色
// 右侧可选角色(查询参数 + 列表数据) // 右侧可选角色(查询参数 + 列表数据)
const rightQuery = reactive({ const rightQuery = reactive({
pageNo: 1, pageNo: 1,
...@@ -964,13 +1106,15 @@ function getLeftRoleList() { ...@@ -964,13 +1106,15 @@ function getLeftRoleList() {
leftLoading.value = true leftLoading.value = true
listLeftRole({ listLeftRole({
...leftQuery ...leftQuery
}).then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
}).catch(() => {
leftLoading.value = false
}) })
.then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
})
.catch(() => {
leftLoading.value = false
})
} }
/** 获取右侧已选角色分页列表 */ /** 获取右侧已选角色分页列表 */
...@@ -978,13 +1122,15 @@ function getRightRoleList() { ...@@ -978,13 +1122,15 @@ function getRightRoleList() {
rightLoading.value = true rightLoading.value = true
listRightRole({ listRightRole({
...rightQuery ...rightQuery
}).then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
}).catch(() => {
rightLoading.value = false
}) })
.then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
})
.catch(() => {
rightLoading.value = false
})
} }
/** 左侧表格选中事件 */ /** 左侧表格选中事件 */
...@@ -1005,7 +1151,7 @@ function queryLeftRoleList() { ...@@ -1005,7 +1151,7 @@ function queryLeftRoleList() {
/** 重置左侧搜索 */ /** 重置左侧搜索 */
function resetLeftRoleQuery() { function resetLeftRoleQuery() {
proxy.resetForm("leftQueryRef") proxy.resetForm('leftQueryRef')
leftQuery.pageNo = 1 leftQuery.pageNo = 1
leftQuery.roleName = '' leftQuery.roleName = ''
getLeftRoleList() getLeftRoleList()
...@@ -1019,7 +1165,7 @@ function queryRightRoleList() { ...@@ -1019,7 +1165,7 @@ function queryRightRoleList() {
/** 重置右侧搜索 */ /** 重置右侧搜索 */
function resetRightRoleQuery() { function resetRightRoleQuery() {
proxy.resetForm("rightQueryRef") proxy.resetForm('rightQueryRef')
rightQuery.pageNo = 1 rightQuery.pageNo = 1
rightQuery.roleName = '' rightQuery.roleName = ''
getRightRoleList() getRightRoleList()
...@@ -1038,15 +1184,19 @@ function moveToRight() { ...@@ -1038,15 +1184,19 @@ function moveToRight() {
userBizId: leftQuery.userBizId, userBizId: leftQuery.userBizId,
roleBizIdList: roleBizIdList roleBizIdList: roleBizIdList
} }
proxy.$modal.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?').then(function () { proxy.$modal
return addRightRoleList(data) .confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?')
}).then(() => { .then(function () {
// 加载左侧待选角色分页列表(排除已选) return addRightRoleList(data)
getLeftRoleList() })
// 加载右侧已选角色分页列表 .then(() => {
getRightRoleList() // 加载左侧待选角色分页列表(排除已选)
proxy.$modal.msgSuccess("添加成功") getLeftRoleList()
}).catch(() => {}) // 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess('添加成功')
})
.catch(() => {})
} }
/** 向左箭头:将右侧选中角色移除左侧,删除关系*/ /** 向左箭头:将右侧选中角色移除左侧,删除关系*/
...@@ -1062,15 +1212,19 @@ function moveToLeft() { ...@@ -1062,15 +1212,19 @@ function moveToLeft() {
userBizId: rightQuery.userBizId, userBizId: rightQuery.userBizId,
roleBizIdList: roleBizIdList roleBizIdList: roleBizIdList
} }
proxy.$modal.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?').then(function () { proxy.$modal
return delRightRoleList(data) .confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?')
}).then(() => { .then(function () {
// 加载左侧待选角色分页列表(排除已选) return delRightRoleList(data)
getLeftRoleList() })
// 加载右侧已选角色分页列表 .then(() => {
getRightRoleList() // 加载左侧待选角色分页列表(排除已选)
proxy.$modal.msgSuccess("移除成功") getLeftRoleList()
}).catch(() => {}) // 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess('移除成功')
})
.catch(() => {})
} }
/** 取消操作 */ /** 取消操作 */
function cancelAssignRole() { function cancelAssignRole() {
...@@ -1078,15 +1232,13 @@ function cancelAssignRole() { ...@@ -1078,15 +1232,13 @@ function cancelAssignRole() {
} }
//========分配角色-导入逻辑结束========= //========分配角色-导入逻辑结束=========
//========分配菜单-导入逻辑开始========= //========分配菜单-导入逻辑开始=========
// 菜单树数据(直接从接口获取树形结构) // 菜单树数据(直接从接口获取树形结构)
const fpMenuTree = ref([]) const fpMenuTree = ref([])
// 树组件引用 // 树组件引用
const fpMenuRef = ref(null) const fpMenuRef = ref(null)
// 添加响应式变量控制严格模式 // 添加响应式变量控制严格模式
const fpMenuIsCheckStrictly = ref(false); // 默认关闭严格模式(启用联动) const fpMenuIsCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置 // 树形配置
const fpMenuProps = { const fpMenuProps = {
children: 'children', children: 'children',
...@@ -1105,7 +1257,7 @@ const getFpMenuTreeParams = reactive({ ...@@ -1105,7 +1257,7 @@ const getFpMenuTreeParams = reactive({
projectBizId: route.query.projectBizId, projectBizId: route.query.projectBizId,
menuName: undefined menuName: undefined
}) })
const fpMenuTitle = ref("") const fpMenuTitle = ref('')
const fpMenuOpen = ref(false) const fpMenuOpen = ref(false)
/** 分配菜单弹出 */ /** 分配菜单弹出 */
...@@ -1114,48 +1266,47 @@ function handleFpMenu(row) { ...@@ -1114,48 +1266,47 @@ function handleFpMenu(row) {
getSelectedFpMenuListParams.roleBizId = row.roleBizId getSelectedFpMenuListParams.roleBizId = row.roleBizId
loadFpMenuTree() loadFpMenuTree()
fpMenuOpen.value = true fpMenuOpen.value = true
fpMenuTitle.value = "分配菜单" fpMenuTitle.value = '分配菜单'
} }
// 加载菜单树 // 加载菜单树
const loadFpMenuTree = async () => { const loadFpMenuTree = async () => {
try { try {
const res = await getFpMenuTree({...getFpMenuTreeParams}) const res = await getFpMenuTree({ ...getFpMenuTreeParams })
fpMenuTree.value = res.data // 直接使用后端返回的树形结构 fpMenuTree.value = res.data // 直接使用后端返回的树形结构
loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选 loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) { } catch (error) {
console.error('加载菜单树失败:', error) console.error('加载菜单树失败:', error)
proxy.$modal.msgSuccess("菜单加载失败") proxy.$modal.msgSuccess('菜单加载失败')
} }
} }
// 修改加载选中菜单列表的逻辑 // 修改加载选中菜单列表的逻辑
const loadSelectedFpMenuList = async () => { const loadSelectedFpMenuList = async () => {
try { try {
const res = await getSelectedFpMenuList({...getSelectedFpMenuListParams}); const res = await getSelectedFpMenuList({ ...getSelectedFpMenuListParams })
const targetKeys = res.data || []; const targetKeys = res.data || []
// 开启严格模式(禁用联动) // 开启严格模式(禁用联动)
fpMenuIsCheckStrictly.value = true; fpMenuIsCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效) // 等待DOM更新(确保严格模式生效)
await nextTick(); await nextTick()
//清空并设置选中状态(此时不会触发联动) //清空并设置选中状态(此时不会触发联动)
if (fpMenuRef.value) { if (fpMenuRef.value) {
fpMenuRef.value.setCheckedKeys([]); fpMenuRef.value.setCheckedKeys([])
fpMenuRef.value.setCheckedKeys(targetKeys); fpMenuRef.value.setCheckedKeys(targetKeys)
} }
//等待选中状态渲染完成 //等待选中状态渲染完成
await nextTick(); await nextTick()
//关闭严格模式(恢复联动) //关闭严格模式(恢复联动)
fpMenuIsCheckStrictly.value = false; fpMenuIsCheckStrictly.value = false
} catch (error) { } catch (error) {
console.error('加载选中的菜单列表失败:', error); console.error('加载选中的菜单列表失败:', error)
proxy.$modal.msgSuccess("加载选中的菜单列表失败") proxy.$modal.msgSuccess('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态 // 异常时也要恢复严格模式状态
fpMenuIsCheckStrictly.value = false; fpMenuIsCheckStrictly.value = false
} }
} }
// 保存 // 保存
...@@ -1177,30 +1328,26 @@ const saveFpMenuList = async () => { ...@@ -1177,30 +1328,26 @@ const saveFpMenuList = async () => {
getRoleList() getRoleList()
fpMenuOpen.value = false fpMenuOpen.value = false
proxy.$modal.msgSuccess("更新成功") proxy.$modal.msgSuccess('更新成功')
} catch (error) { } catch (error) {
console.error('更新失败:', error) console.error('更新失败:', error)
proxy.$modal.msgSuccess("更新失败") proxy.$modal.msgSuccess('更新失败')
} }
} }
//========分配菜单-导入逻辑结束========= //========分配菜单-导入逻辑结束=========
const handleTabChange = tabName => {
const handleTabChange = (tabName) => {
console.log('切换到:', tabName) console.log('切换到:', tabName)
if (tabName === "user"){ if (tabName === 'user') {
//用户tab获取用户列表数据 //用户tab获取用户列表数据
getUserList() getUserList()
}else if (tabName === "role"){ } else if (tabName === 'role') {
//用户tab获取角色列表数据 //用户tab获取角色列表数据
getRoleList() getRoleList()
}else if (tabName === "menu"){ } else if (tabName === 'menu') {
//用户tab获取菜单树数据 //用户tab获取菜单树数据
getMenuList() getMenuList()
} }
} }
getUserList() getUserList()
...@@ -1216,19 +1363,19 @@ getUserList() ...@@ -1216,19 +1363,19 @@ getUserList()
/* 自定义选项卡:核心样式 */ /* 自定义选项卡:核心样式 */
.custom-tabs { .custom-tabs {
--active-tab-color: #fff; /* 激活选项卡文字颜色 */ --active-tab-color: #fff; /* 激活选项卡文字颜色 */
--active-tab-bg: #409eff; /* 激活选项卡背景色 */ --active-tab-bg: #409eff; /* 激活选项卡背景色 */
--inactive-tab-color: #606266; /* 未激活文字颜色 */ --inactive-tab-color: #606266; /* 未激活文字颜色 */
--inactive-tab-bg: #f5f7fa; /* 未激活背景色 */ --inactive-tab-bg: #f5f7fa; /* 未激活背景色 */
--border-color: #dcdfe6; /* 边框颜色 */ --border-color: #dcdfe6; /* 边框颜色 */
--radius: 8px; /* 圆角 */ --radius: 8px; /* 圆角 */
width: 100%; width: 100%;
} }
/* 选项卡头部:消除默认样式,重构凹陷效果 */ /* 选项卡头部:消除默认样式,重构凹陷效果 */
::v-deep .el-tabs__header { ::v-deep .el-tabs__header {
line-height: 1; /* 重置行高,避免影响高度计算 */ line-height: 1; /* 重置行高,避免影响高度计算 */
padding: 0; /* 清除默认内边距 */ padding: 0; /* 清除默认内边距 */
} }
/* 选项卡导航容器:核心凹陷逻辑 */ /* 选项卡导航容器:核心凹陷逻辑 */
...@@ -1283,7 +1430,7 @@ getUserList() ...@@ -1283,7 +1430,7 @@ getUserList()
/* 激活态:覆盖父容器的底部边框,实现“凹陷” */ /* 激活态:覆盖父容器的底部边框,实现“凹陷” */
::v-deep .el-tabs__item.is-active::before { ::v-deep .el-tabs__item.is-active::before {
content: ""; content: '';
position: absolute; position: absolute;
bottom: -1px; /* 覆盖父容器的 border-bottom */ bottom: -1px; /* 覆盖父容器的 border-bottom */
left: 0; left: 0;
...@@ -1309,8 +1456,6 @@ getUserList() ...@@ -1309,8 +1456,6 @@ getUserList()
color: #909399; color: #909399;
} }
.permission-section { .permission-section {
margin-top: 20px; margin-top: 20px;
} }
...@@ -1434,7 +1579,6 @@ getUserList() ...@@ -1434,7 +1579,6 @@ getUserList()
background-color: #fff; background-color: #fff;
} }
/* 右侧标题 */ /* 右侧标题 */
.selected-title { .selected-title {
font-size: 14px; font-size: 14px;
......
...@@ -336,8 +336,6 @@ function handleDelete(row) { ...@@ -336,8 +336,6 @@ function handleDelete(row) {
/** 角色状态修改 */ /** 角色状态修改 */
function handleStatusChange(row) { function handleStatusChange(row) {
console.log('row', row.status)
const newStatus = row.status === 0 ? 0 : 1 // 获取切换后的新状态 const newStatus = row.status === 0 ? 0 : 1 // 获取切换后的新状态
const text = newStatus === 0 ? '停用' : '启用' // 根据新状态显示文本 const text = newStatus === 0 ? '停用' : '启用' // 根据新状态显示文本
console.log('newStatus', newStatus) console.log('newStatus', newStatus)
......
<template>
<div>
<el-row :gutter="20">
<splitpanes :horizontal="appStore.device === 'mobile'" class="default-theme">
<!--部门数据-->
<pane size="16">
<el-col>
<div class="head-container">
<el-button
type="info"
plain
icon="Upload"
@click="handleImportTenantMenuList"
style="margin-bottom: 20px"
>导入</el-button
>
</div>
<div class="head-container">
<el-input
v-model="deptTreeParams.deptName"
placeholder="请输入部门名称"
clearable
prefix-icon="Search"
style="margin-bottom: 20px"
/>
</div>
<div class="head-container">
<el-tree
:data="deptOptions"
:props="{ label: 'deptName', children: 'children' }"
:expand-on-click-node="false"
:filter-node-method="filterNode"
ref="deptTreeRef"
node-key="deptBizId"
highlight-current
default-expand-all
@node-click="handleNodeClick"
/>
</div>
</el-col>
</pane>
<!--用户数据-->
<pane size="84">
<el-col>
<el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="68px"
>
<el-form-item label="账号" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入账号"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号码"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportTenantUserList"
>导入</el-button
>
</el-form-item>
</el-form>
<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="userName" prop="userName" />
<el-table-column
label="真实姓名"
align="center"
key="realName"
prop="realName"
:show-overflow-tooltip="true"
/>
<el-table-column
label="用户昵称"
align="center"
key="nickName"
prop="nickName"
:show-overflow-tooltip="true"
/>
<el-table-column
label="手机号码"
align="center"
key="mobile"
prop="mobile"
width="120"
/>
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
width="150"
class-name="small-padding fixed-width"
>
<template #default="scope">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-col>
</pane>
</splitpanes>
</el-row>
<!-- 部门导入(根据权限从部门池导入进来) -->
<el-dialog
:title="importMenuTitle"
v-model="importTenantMenuListOpen"
width="500px"
append-to-body
>
<!-- 权限分配区域 -->
<div class="permission-section">
<el-tree
ref="treeRef"
:data="menuTree"
:props="defaultProps"
node-key="deptBizId"
show-checkbox
highlight-current
:check-strictly="isCheckStrictly"
:expand-on-click-node="false"
class="permission-tree"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ data.deptName }}</span>
</span>
</template>
</el-tree>
<div class="tree-actions mt-4">
<el-button type="primary" @click="saveImportSelectedMenuList">确定</el-button>
</div>
<!-- <el-card :header="importMenuTitle" class="mt-4">
</el-card> -->
</div>
</el-dialog>
<!-- 用户导入(根据权限从用户池导入进来) -->
<el-dialog
:title="importUserTitle"
v-model="importTenantUserListOpen"
width="800px"
append-to-body
>
<el-form
:model="importTenantUserListQueryParams"
ref="importTenantUserListQueryRef"
v-show="importTenantUserListShowSearch"
:inline="true"
label-width="55px"
>
<el-form-item label="账号" prop="userName">
<el-input
v-model="importTenantUserListQueryParams.userName"
placeholder="请输入账号"
clearable
style="width: 240px"
@keyup.enter="importTenantUserListHandleQuery"
/>
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input
v-model="importTenantUserListQueryParams.realName"
placeholder="请输入姓名"
clearable
style="width: 240px"
@keyup.enter="importTenantUserListHandleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="importTenantUserListQueryParams.mobile"
placeholder="请输入手机号"
clearable
style="width: 240px"
@keyup.enter="importTenantUserListHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="importTenantUserListHandleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="importTenantUserListResetQuery">重置</el-button>
</el-form-item>
</el-form>
<el-table
v-loading="importTenantUserListLoading"
:data="importTenantUserList"
@selection-change="importTenantUserListHandleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" />
<el-table-column label="昵称" prop="nickName" />
<el-table-column label="邮箱" prop="email" />
<el-table-column label="手机号" prop="mobile" />
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
</el-table>
<pagination
v-show="importTenantUserListTotal > 0"
:total="importTenantUserListTotal"
v-model:page="importTenantUserListQueryParams.pageNo"
v-model:limit="importTenantUserListQueryParams.pageSize"
@pagination="getImportTenantUserList"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="importTenantUserListSubmitForm">确 定</el-button>
<el-button @click="importTenantUserListCancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User">
import useAppStore from '@/store/modules/app'
import {
deptTreeList,
importGetDeptTreeList,
getDeptTreeIds,
addDeptPermissionTreeList,
getDeptUserList,
getImportDeptUserList,
addImportDeptUserList,
deleteImportDeptUser
} from '@/api/system/tenantPermission'
// delUser,
import { Splitpanes, Pane } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'
import { ref } from 'vue'
const props = defineProps({
tenantBizId: {
type: String,
default: () => undefined
},
activeTab: {
type: String,
default: () => 'project'
}
})
const appStore = useAppStore()
const { proxy } = getCurrentInstance()
const { sys_gender } = proxy.useDict('sys_gender')
const userList = ref([])
const loading = ref(false)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const dateRange = ref([])
const deptName = ref('')
const deptOptions = ref(undefined)
const enabledDeptOptions = ref(undefined)
const importTenantMenuListOpen = ref(false)
const importMenuTitle = ref('')
const importUserTitle = ref('')
const importTenantUserListShowSearch = ref(true)
const importTenantUserListLoading = ref(true)
const importTenantUserList = ref([])
const importTenantUserListTotal = ref(0)
const importTenantUserListIds = ref([])
const importTenantUserListOpen = ref(false)
const importTenantUserListNames = ref([])
const deptTreeRef = ref(null)
const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
userName: undefined,
mobile: undefined,
status: undefined,
tenantBizId: props.tenantBizId,
deptBizId: undefined
},
importTenantUserListQueryParams: {
pageNo: 1,
pageSize: 10,
tenantBizId: props.tenantBizId,
userName: undefined,
realName: undefined,
mobile: undefined,
deptBizId: undefined
},
deptTreeParams: {
deptName: undefined,
tenantBizId: props.tenantBizId
},
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' },
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\\ |', 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 menuTree = ref([])
// 树组件引用
const treeRef = ref(null)
// 添加响应式变量控制严格模式
const isCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
const deptBizIdValue = ref(undefined) //部门Id
// 树形配置
const defaultProps = {
children: 'children',
label: 'deptName'
}
const { queryParams, form, rules, importTenantUserListQueryParams, deptTreeParams } = toRefs(data)
//========部门-导入逻辑开始=========
/** 部门列表-导入部门 */
const handleImportTenantMenuList = () => {
loadMenuTree()
importTenantMenuListOpen.value = true
importMenuTitle.value = '导入部门'
}
// 加载部门树
const loadMenuTree = async () => {
try {
const res = await importGetDeptTreeList(deptTreeParams.value)
menuTree.value = res.data // 直接使用后端返回的树形结构
loadImportSelectedMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) {
console.error('加载菜单树失败:', error)
proxy.$modal.msgError('菜单加载失败')
}
}
// 修改加载选中部门列表的逻辑
const loadImportSelectedMenuList = async () => {
try {
const res = await getDeptTreeIds(props.tenantBizId)
const targetKeys = res.data || []
// 开启严格模式(禁用联动)
isCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效)
await nextTick()
//清空并设置选中状态(此时不会触发联动)
if (treeRef.value) {
treeRef.value.setCheckedKeys([])
treeRef.value.setCheckedKeys(targetKeys)
}
//等待选中状态渲染完成
await nextTick()
//关闭严格模式(恢复联动)
isCheckStrictly.value = false
} catch (error) {
console.error('加载选中的菜单列表失败:', error)
proxy.$modal.msgError('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态
isCheckStrictly.value = false
}
}
// 保存
const saveImportSelectedMenuList = async () => {
try {
// 获取当前选中的节点
const checkedKeys = treeRef.value.getCheckedKeys()
// 获取半选中的节点(下级勾选,上级没勾选,把上级没勾选带出来)
const halfCheckedKeys = treeRef.value.getHalfCheckedKeys()
// 合并选中节点(根据业务需求选择是否包含半选节点)
const allCheckedKeys = [...checkedKeys, ...halfCheckedKeys]
await addDeptPermissionTreeList({
tenantBizId: props.tenantBizId,
deptBizIdList: allCheckedKeys
})
getDeptTree()
importTenantMenuListOpen.value = false
proxy.$modal.msgSuccess('更新成功')
} catch (error) {
console.error('更新失败:', error)
proxy.$modal.msgError('更新失败')
}
}
//========部门-导入逻辑结束=========
//========用户-导入逻辑开始=========
/** 用户列表-导入-查询导入租户用户关系列表 */
function getImportTenantUserList() {
importTenantUserListLoading.value = true
importTenantUserListQueryParams.value.deptBizId = deptBizIdValue.value
getImportDeptUserList(importTenantUserListQueryParams.value).then(response => {
importTenantUserList.value = response.data.records
importTenantUserListTotal.value = response.data.total
importTenantUserListLoading.value = false
})
}
/** 用户列表-导入用户 */
function handleImportTenantUserList() {
getImportTenantUserList()
importTenantUserListOpen.value = true
importUserTitle.value = '导入用户'
}
/** 用户列表-导入-搜索按钮操作 做到这了*/
function importTenantUserListHandleQuery() {
importTenantUserListQueryParams.value.pageNo = 1
getImportTenantUserList()
}
/** 用户列表-导入-重置按钮操作 */
function importTenantUserListResetQuery() {
proxy.resetForm('importTenantUserListQueryRef')
importTenantUserListHandleQuery()
}
/** 用户列表-导入-多选框选中数据 */
function importTenantUserListHandleSelectionChange(selection) {
importTenantUserListIds.value = selection.map(item => item.userBizId)
importTenantUserListNames.value = selection.map(item => item.userName)
}
/** 用户列表-导入-导入用户提交 */
function importTenantUserListSubmitForm() {
const userBizIdList = importTenantUserListIds.value
const importTenantUserListNameList = importTenantUserListNames.value
const tenantBizId = props.tenantBizId
const deptBizId = deptBizIdValue.value
proxy.$modal
.confirm('是否确认导入用户账号为"' + importTenantUserListNameList + '"的数据项?')
.then(function () {
const data = {
userBizIdList: userBizIdList,
tenantBizId: tenantBizId,
deptBizId: deptBizId
}
return addImportDeptUserList(data)
})
.then(() => {
importTenantUserListOpen.value = false
getList()
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
}
/** 用户列表-导入-取消 */
function importTenantUserListCancel() {
importTenantUserListOpen.value = false
proxy.resetForm('importTenantUserListQueryRef')
}
//========用户-导入逻辑结束=========
/** 通过条件过滤节点 */
const filterNode = (value, data) => {
if (!value) return true
return data.label.indexOf(value) !== -1
}
/** 根据名称筛选部门树 */
watch(deptName, val => {
proxy.$refs['deptTreeRef'].filter(val)
})
/** 查询用户列表 */
const getList = () => {
queryParams.value.deptBizId = deptBizIdValue.value
loading.value = true
getDeptUserList(queryParams.value).then(res => {
if (res.code == 200) {
loading.value = false
userList.value = res.data.records
total.value = res.data.total
} else {
proxy.$modal.msgError(res.msg)
loading.value = false
}
})
}
/** 查询部门下拉树结构 */
const getDeptTree = () => {
deptTreeList(deptTreeParams.value).then(response => {
if (response.code == 200) {
if (response.data && response.data.length > 0) {
deptBizIdValue.value = response.data[0].deptBizId
deptOptions.value = response.data
enabledDeptOptions.value = filterDisabledDept(JSON.parse(JSON.stringify(response.data)))
getList()
// 等待 DOM 更新后选中第一个节点
nextTick(() => {
selectFirstTreeNode()
})
}
} else {
proxy.$modal.msgError(response.msg)
}
})
}
/** 过滤禁用的部门 */
const filterDisabledDept = deptList => {
return deptList.filter(dept => {
if (dept.disabled) {
return false
}
if (dept.children && dept.children.length) {
dept.children = filterDisabledDept(dept.children)
}
return true
})
}
// 选中树形结构的第一个节点
const selectFirstTreeNode = () => {
if (deptOptions.value && deptOptions.value.length > 0) {
// 获取第一个节点的 key (使用 deptBizId)
const firstNodeKey = findFirstNodeKey(deptOptions.value[0])
if (firstNodeKey && deptTreeRef.value) {
// 设置当前选中节点
deptTreeRef.value.setCurrentKey(firstNodeKey)
// 如果需要触发节点点击事件
deptBizIdValue.value = firstNodeKey
handleQuery() // 重新获取列表数据
}
}
}
// 递归查找第一个节点的 key
const findFirstNodeKey = node => {
if (node.deptBizId) {
return node.deptBizId
}
// 如果有子节点,继续查找
if (node.children && node.children.length > 0) {
return findFirstNodeKey(node.children[0])
}
return null
}
/** 节点单击事件 */
const handleNodeClick = data => {
deptBizIdValue.value = data.deptBizId
handleQuery()
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
dateRange.value = []
proxy.resetForm('queryRef')
queryParams.value.deptId = undefined
proxy.$refs.deptTreeRef.setCurrentKey(null)
handleQuery()
}
/** 删除部门用户关系 */
function handleDelete(row) {
//租户和项目关系表主键id
const id = row.id
const userName = row.userName
proxy.$modal
.confirm('是否确认删除账号为"' + userName + '"的租户和部门关系的数据项?')
.then(function () {
return deleteImportDeptUser({ id: id })
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 选择条数 */
const handleSelectionChange = selection => {
ids.value = selection.map(item => item.userId)
single.value = selection.length != 1
multiple.value = !selection.length
}
// 监听tab切换
watch(
() => props.activeTab,
newTab => {
if (newTab == 'dept') {
getDeptTree()
resetQuery()
}
},
{ immediate: true }
)
</script>
<style scoped>
.tree-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
<template> <template>
<div class="app-container"> <div class="app-container">
<!--租户数据--> <!--租户数据-->
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="85px"> <el-form
:model="queryParams"
ref="queryRef"
:inline="true"
v-show="showSearch"
label-width="85px"
>
<el-form-item label="租户名称" prop="tenantName"> <el-form-item label="租户名称" prop="tenantName">
<el-input v-model="queryParams.tenantName" placeholder="请输入租户名称" clearable style="width: 240px" @keyup.enter="handleQuery" /> <el-input
v-model="queryParams.tenantName"
placeholder="请输入租户名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="联系人姓名" prop="contactName"> <el-form-item label="联系人姓名" prop="contactName">
<el-input v-model="queryParams.contactName" placeholder="请输入联系人姓名" clearable style="width: 240px" @keyup.enter="handleQuery" /> <el-input
v-model="queryParams.contactName"
placeholder="请输入联系人姓名"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="联系电话" prop="contactPhone"> <el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="queryParams.contactPhone" placeholder="请输入联系电话" clearable style="width: 240px" @keyup.enter="handleQuery" /> <el-input
v-model="queryParams.contactPhone"
placeholder="请输入联系电话"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-select <el-select
v-model="queryParams.status" v-model="queryParams.status"
placeholder="租户状态" placeholder="租户状态"
clearable clearable
style="width: 240px" style="width: 240px"
> >
<el-option <el-option
v-for="dict in sys_status" v-for="dict in sys_status"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="创建时间" style="width: 308px"> <el-form-item label="创建时间" style="width: 308px">
<el-date-picker v-model="dateRange" value-format="YYYY-MM-DD" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"></el-date-picker> <el-date-picker
v-model="dateRange"
value-format="YYYY-MM-DD"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
...@@ -36,10 +67,27 @@ ...@@ -36,10 +67,27 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table v-loading="loading" :data="tenantList" > <el-table v-loading="loading" :data="tenantList">
<el-table-column label="租户LOGO" align="center" prop="logoUrl"/> <el-table-column label="租户LOGO" align="center" prop="logoUrl" width="100">
<template #default="scope">
<!-- :preview-src-list="[scope.row.logoUrl]" -->
<el-image
v-if="scope.row.logoUrl"
:src="scope.row.logoUrl"
class="logo-image"
fit="cover"
>
<template #error>
<div class="image-slot">
<el-icon><Picture /></el-icon>
</div>
</template>
</el-image>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="租户名称" align="center" prop="tenantName" /> <el-table-column label="租户名称" align="center" prop="tenantName" />
<el-table-column label="联系人姓名" align="center" prop="contactName"/> <el-table-column label="联系人姓名" align="center" prop="contactName" />
<el-table-column label="联系人电话" align="center" prop="contactPhone" /> <el-table-column label="联系人电话" align="center" prop="contactPhone" />
<el-table-column label="服务到期时间" align="center" prop="expireTime"> <el-table-column label="服务到期时间" align="center" prop="expireTime">
<template #default="scope"> <template #default="scope">
...@@ -51,10 +99,10 @@ ...@@ -51,10 +99,10 @@
<el-table-column label="状态" align="center"> <el-table-column label="状态" align="center">
<template #default="scope"> <template #default="scope">
<el-switch <el-switch
v-model="scope.row.status" v-model="scope.row.status"
:active-value="1" :active-value="1"
:inactive-value="0" :inactive-value="0"
@change="(val) => handleStatusChange(scope.row, $event)" @change="val => handleStatusChange(scope.row, $event)"
></el-switch> ></el-switch>
</template> </template>
</el-table-column> </el-table-column>
...@@ -63,15 +111,32 @@ ...@@ -63,15 +111,32 @@
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width"> <el-table-column
label="操作"
align="center"
width="160"
class-name="small-padding fixed-width"
>
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Edit" @click="handlePermission(scope.row)" >分配权限</el-button> <el-button link type="primary" icon="Edit" @click="handlePermission(scope.row)"
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" >修改</el-button> >分配权限</el-button
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" >删除</el-button> >
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
>修改</el-button
>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination v-show="total >= 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> <pagination
v-show="total >= 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改租户对话框 --> <!-- 添加或修改租户对话框 -->
<el-dialog :title="title" v-model="open" width="650px" append-to-body> <el-dialog :title="title" v-model="open" width="650px" append-to-body>
...@@ -128,26 +193,38 @@ ...@@ -128,26 +193,38 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="服务到期时间" prop="expireTime"> <el-form-item label="服务到期时间" prop="expireTime">
<el-date-picker <el-date-picker
v-model="form.expireTime" v-model="form.expireTime"
type="datetime" type="datetime"
placeholder="选择到期日期" placeholder="选择到期日期"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%" style="width: 100%"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="状态"> <el-form-item label="状态">
<el-radio-group v-model="form.status"> <el-radio-group v-model="form.status">
<!-- <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>--> <el-radio
v-for="dict in sys_status"
:key="dict.value"
:value="Number(dict.value)"
>{{ dict.label }}</el-radio
>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="租户LOGO"> <el-form-item label="租户LOGO" prop="logoUrl">
<el-input v-model="form.logoUrl" type="textarea" placeholder="请输入内容"></el-input> <image-upload
v-model="form.logoUrl"
:action="'/oss/api/oss/upload'"
:limit="1"
:file-size="3"
:file-type="['png', 'jpg', 'jpeg']"
:is-show-tip="true"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
...@@ -159,38 +236,34 @@ ...@@ -159,38 +236,34 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup name="Tenant"> <script setup name="Tenant">
import { getToken } from "@/utils/auth" import {
import useAppStore from '@/store/modules/app' listTenant,
import { changeUserStatus, listTenant, resetUserPwd, delUser, getTenant, updateTenant, addTenant,changeTenantStatus } from "@/api/system/tenant" delUser,
getTenant,
updateTenant,
addTenant,
changeTenantStatus
} from '@/api/system/tenant'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组件
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const appStore = useAppStore()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_status,sys_scope,sys_no_yes } = proxy.useDict("sys_status","sys_scope","sys_no_yes") const { sys_status, sys_scope, sys_no_yes } = proxy.useDict('sys_status', 'sys_scope', 'sys_no_yes')
const tenantList = ref([]) const tenantList = ref([])
const open = ref(false) const open = ref(false)
const loading = ref(true) const loading = ref(true)
const showSearch = ref(true) const showSearch = ref(true)
const ids = ref([]) const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0) const total = ref(0)
const title = ref("") const title = ref('')
const dateRange = ref([]) const dateRange = ref([])
const deptName = ref("")
const deptOptions = ref(undefined)
const enabledDeptOptions = ref(undefined)
const initPassword = ref(undefined)
const postOptions = ref([])
const roleOptions = ref([])
const data = reactive({ const data = reactive({
form: {}, form: {},
...@@ -204,20 +277,16 @@ const data = reactive({ ...@@ -204,20 +277,16 @@ const data = reactive({
status: undefined status: undefined
}, },
rules: { rules: {
tenantName: [{ required: true, message: "租户名称不能为空", trigger: "blur" }], tenantName: [{ required: true, message: '租户名称不能为空', trigger: 'blur' }],
contactEmail: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }], contactEmail: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
contactPhone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }] contactPhone: [
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
} }
}) })
const { queryParams, form, rules } = toRefs(data) const { queryParams, form, rules } = toRefs(data)
/** 通过条件过滤节点 */
const filterNode = (value, data) => {
if (!value) return true
return data.label.indexOf(value) !== -1
}
/** 查询租户列表 */ /** 查询租户列表 */
function getList() { function getList() {
loading.value = true loading.value = true
...@@ -228,25 +297,6 @@ function getList() { ...@@ -228,25 +297,6 @@ function getList() {
}) })
} }
/** 过滤禁用的部门 */
function filterDisabledDept(deptList) {
return deptList.filter(dept => {
if (dept.disabled) {
return false
}
if (dept.children && dept.children.length) {
dept.children = filterDisabledDept(dept.children)
}
return true
})
}
/** 节点单击事件 */
function handleNodeClick(data) {
queryParams.value.deptId = data.id
handleQuery()
}
/** 搜索按钮操作 */ /** 搜索按钮操作 */
function handleQuery() { function handleQuery() {
queryParams.value.pageNum = 1 queryParams.value.pageNum = 1
...@@ -256,113 +306,37 @@ function handleQuery() { ...@@ -256,113 +306,37 @@ function handleQuery() {
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
dateRange.value = [] dateRange.value = []
proxy.resetForm("queryRef") proxy.resetForm('queryRef')
handleQuery() handleQuery()
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
function handleDelete(row) { function handleDelete(row) {
const userIds = row.userId || ids.value const userIds = row.userId || ids.value
proxy.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function () { proxy.$modal
return delUser(userIds) .confirm('是否确认删除用户编号为"' + userIds + '"的数据项?')
}).then(() => { .then(function () {
getList() return delUser(userIds)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
} getList()
proxy.$modal.msgSuccess('删除成功')
/** 导出按钮操作 */ })
function handleExport() { .catch(() => {})
proxy.download("system/user/export", {
...queryParams.value,
},`user_${new Date().getTime()}.xlsx`)
} }
/** 租户状态修改 */ /** 租户状态修改 */
function handleStatusChange(row, event) { function handleStatusChange(row, event) {
let text = row.status === 0 ? "停用" : "启用" let text = row.status === 0 ? '停用' : '启用'
const tenantName = row.tenantName const tenantName = row.tenantName
proxy.$modal.confirm(`确认要${text}"${tenantName}"租户吗?`) proxy.$modal
.then(() => changeTenantStatus({tenantBizId: row.tenantBizId, status: row.status})) .confirm(`确认要${text}"${tenantName}"租户吗?`)
.then(() => proxy.$modal.msgSuccess(`${text}成功`)) .then(() => changeTenantStatus({ tenantBizId: row.tenantBizId, status: row.status }))
.catch(() => { .then(() => proxy.$modal.msgSuccess(`${text}成功`))
// 操作取消时恢复原状态 .catch(() => {
row.status = row.status === 0 ? 1 : 0 // 操作取消时恢复原状态
}) row.status = row.status === 0 ? 1 : 0
}
/** 更多操作 */
function handleCommand(command, row) {
switch (command) {
case "handleResetPwd":
handleResetPwd(row)
break
case "handleAuthRole":
handleAuthRole(row)
break
default:
break
}
}
/** 跳转角色分配 */
function handleAuthRole(row) {
const userId = row.userId
router.push("/system/user-auth/role/" + userId)
}
/** 重置密码按钮操作 */
function handleResetPwd(row) {
proxy.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
inputErrorMessage: "用户密码长度必须介于 5 和 20 之间",
inputValidator: (value) => {
if (/<|>|"|'|\||\\/.test(value)) {
return "不能包含非法字符:< > \" ' \\\ |"
}
},
}).then(({ value }) => {
resetUserPwd(row.userId, value).then(response => {
proxy.$modal.msgSuccess("修改成功,新密码是:" + value)
}) })
}).catch(() => {})
}
/** 选择条数 */
function handleSelectionChange(selection) {
ids.value = selection.map(item => item.userId)
single.value = selection.length != 1
multiple.value = !selection.length
}
/** 下载模板操作 */
function importTemplate() {
proxy.download("system/user/importTemplate", {
}, `user_template_${new Date().getTime()}.xlsx`)
}
/**文件上传中处理 */
const handleFileUploadProgress = (event, file, fileList) => {
upload.isUploading = true
}
/** 文件上传成功处理 */
const handleFileSuccess = (response, file, fileList) => {
upload.open = false
upload.isUploading = false
proxy.$refs["uploadRef"].handleRemove(file)
proxy.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true })
getList()
} }
/** 提交上传文件 */
function submitFileForm() {
proxy.$refs["uploadRef"].submit()
}
/** 重置操作表单 */ /** 重置操作表单 */
function reset() { function reset() {
form.value = { form.value = {
...@@ -374,12 +348,12 @@ function reset() { ...@@ -374,12 +348,12 @@ function reset() {
contactEmail: undefined, contactEmail: undefined,
industry: undefined, industry: undefined,
address: undefined, address: undefined,
status: "0", status: '0',
expireTime: undefined, expireTime: undefined,
maxProject: undefined, maxProject: undefined,
maxUser: undefined maxUser: undefined
} }
proxy.resetForm("tenantRef") proxy.resetForm('tenantRef')
} }
/** 取消按钮 */ /** 取消按钮 */
...@@ -392,7 +366,7 @@ function cancel() { ...@@ -392,7 +366,7 @@ function cancel() {
function handleAdd() { function handleAdd() {
reset() reset()
open.value = true open.value = true
title.value = "添加租户" title.value = '添加租户'
} }
/** 修改按钮操作 */ /** 修改按钮操作 */
...@@ -402,7 +376,7 @@ function handleUpdate(row) { ...@@ -402,7 +376,7 @@ function handleUpdate(row) {
getTenant(tenantBizId).then(response => { getTenant(tenantBizId).then(response => {
form.value = response.data form.value = response.data
open.value = true open.value = true
title.value = "修改用户" title.value = '修改用户'
}) })
} }
...@@ -413,18 +387,20 @@ function handlePermission(row) { ...@@ -413,18 +387,20 @@ function handlePermission(row) {
/** 新增或者修改租户提交按钮 */ /** 新增或者修改租户提交按钮 */
function submitForm() { function submitForm() {
proxy.$refs["tenantRef"].validate(valid => { proxy.$refs['tenantRef'].validate(valid => {
if (valid) { if (valid) {
if (form.value.tenantBizId != undefined) { if (form.value.tenantBizId != undefined) {
updateTenant(form.value).then(response => { updateTenant(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功") proxy.$modal.msgSuccess('修改成功')
open.value = false open.value = false
reset()
getList() getList()
}) })
} else { } else {
addTenant(form.value).then(response => { addTenant(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功") proxy.$modal.msgSuccess('新增成功')
open.value = false open.value = false
reset()
getList() getList()
}) })
} }
...@@ -434,3 +410,91 @@ function submitForm() { ...@@ -434,3 +410,91 @@ function submitForm() {
getList() getList()
</script> </script>
<style scoped lang="scss">
.logo-image {
width: 70px;
height: 70px;
border-radius: 4px;
object-fit: cover;
}
.avatarBox {
display: flex;
flex-direction: column;
width: 100%;
.avatar-uploader {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
width: 120px;
height: 120px;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-uploader:hover {
border-color: var(--el-color-primary);
}
.avatar-uploader-icon {
font-size: 40px;
color: #8c939d;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 50px;
}
.avatar {
width: 80%;
height: 80%;
object-fit: contain;
}
.upload-progress {
margin-top: 10px;
width: 50%;
}
.progress-text {
display: block;
text-align: left;
margin-top: 5px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.el-upload__tip {
margin-top: 10px;
color: var(--el-text-color-secondary);
font-size: 12px;
}
.deleteIcon {
color: #fff;
font-size: 18px;
cursor: pointer;
}
.avatar-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s;
border-radius: 6px;
}
.avatar-overlay:hover {
opacity: 1;
}
}
</style>
<template>
<div>
<el-form
:model="roleQueryParams"
ref="roleQueryRef"
v-show="roleShowSearch"
:inline="true"
label-width="80px"
>
<el-form-item label="产品名称" prop="productName">
<el-input
v-model="roleQueryParams.productName"
placeholder="请输入产品名称"
clearable
style="width: 240px"
@keyup.enter="roleHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="insuranceResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportTenantRoleList"
>导入</el-button
>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="roleLoading" :data="tenantRoleList">
<el-table-column label="产品名称" prop="productName" width="150" align="left" fixed="left" />
<el-table-column prop="scope" label="作用域" width="150" align="left">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column label="产品类型" prop="productType" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_product_type" :value="scope.row.productType" />
</template>
</el-table-column>
<el-table-column label="货币类型" prop="currency" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_currency_type" :value="scope.row.currency" />
</template>
</el-table-column>
<el-table-column
label="供款年期(年)"
width="150"
align="left"
prop="paymentTerm"
></el-table-column>
<el-table-column
label="受保年龄范围(岁)"
prop="insuredAgeRange"
:show-overflow-tooltip="true"
width="150"
align="left"
/>
<el-table-column
label="基础费率(%)"
prop="premiumRate"
:show-overflow-tooltip="true"
width="150"
/>
<el-table-column label="区分吸烟" prop="smokingAllowed" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_smoking_allowed" :value="scope.row.smokingAllowed" />
</template>
</el-table-column>
<el-table-column
label="保障内容"
prop="coverageContent"
:show-overflow-tooltip="true"
width="200"
align="left"
/>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="200"
fixed="right"
>
<template #default="scope">
<el-button link type="primary" icon="Delete" @click="ProductHandleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="roleTotal > 0"
:total="roleTotal"
v-model:page="roleQueryParams.pageNo"
v-model:limit="roleQueryParams.pageSize"
@pagination="getProductList"
/>
<!-- 保险产品导入(根据权限从保险产品池导入进来) -->
<el-dialog
:title="importRoleTitle"
v-model="importTenantRoleListOpen"
width="800px"
append-to-body
>
<el-form
:model="importTenantRoleListQueryParams"
ref="importTenantRoleListQueryRef"
v-show="importTenantRoleListShowSearch"
:inline="true"
label-width="70px"
>
<el-form-item label="产品名称" prop="productName">
<el-input
v-model="importTenantRoleListQueryParams.productName"
placeholder="请输入产品名称"
clearable
style="width: 240px"
@keyup.enter="importTenantRoleListHandleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="importTenantRoleListHandleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="importTenantRoleListResetQuery">重置</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table
v-loading="importTenantRoleListLoading"
:data="importTenantRoleList"
@selection-change="importTenantRoleListHandleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="产品类型" prop="productType" width="150" fixed="left" align="left">
<template #default="scope">
<dict-tag :options="bx_product_type" :value="scope.row.productType" />
</template>
</el-table-column>
<el-table-column
label="产品名称"
prop="productName"
:show-overflow-tooltip="true"
width="150"
align="left"
/>
<el-table-column label="产品状态" prop="productStatus" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_product_status" :value="scope.row.productStatus" />
</template>
</el-table-column>
<el-table-column label="作用域" prop="scope" width="150" align="left">
<template #default="scope">
<dict-tag :options="sys_scope" :value="scope.row.scope" />
</template>
</el-table-column>
<el-table-column label="货币类型" prop="currency" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_currency_type" :value="scope.row.currency" />
</template>
</el-table-column>
<el-table-column
label="供款年期(年)"
width="150"
align="left"
prop="paymentTerm"
></el-table-column>
<el-table-column
label="受保年龄范围(岁)"
prop="insuredAgeRange"
:show-overflow-tooltip="true"
width="150"
align="left"
/>
<el-table-column
label="基础费率(%)"
prop="premiumRate"
:show-overflow-tooltip="true"
width="150"
/>
<el-table-column label="区分吸烟" prop="smokingAllowed" width="150" align="left">
<template #default="scope">
<dict-tag :options="bx_smoking_allowed" :value="scope.row.smokingAllowed" />
</template>
</el-table-column>
<el-table-column
label="保障内容"
prop="coverageContent"
:show-overflow-tooltip="true"
width="200"
align="left"
/>
</el-table>
<pagination
v-show="importTenantRoleListTotal > 0"
:total="importTenantRoleListTotal"
v-model:page="importTenantRoleListQueryParams.pageNo"
v-model:limit="importTenantRoleListQueryParams.pageSize"
@pagination="getImportTenantRoleList"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="importTenantRoleListSubmitForm">确 定</el-button>
<el-button @click="importTenantRoleListCancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="insuranceList">
import {
listTenantProject,
listImportTenantProject,
addImportTenantProjectList,
delRelTenantProject,
listTenantUser,
listImportTenantUser,
addImportTenantUserList,
delRelTenantUser,
listTenantRole,
delRelTenantRole,
addImportTenantRoleList,
listImportTenantRole,
listMenu,
getMenuTree,
getImportSelectedMenuList,
addImportTenantMenuList,
listLeftRole,
listRightRole,
addRightRoleList,
delRightRoleList,
getFpMenuTree,
getSelectedFpMenuList,
addFpMenuList
} from '@/api/system/tenantPermission'
import {
getRelTenantInsuranceList,
getImportRelTenantInsuranceList,
addImportInsuranceProductList,
delInsuranceProduct
} from '@/api/insurance/index'
import { ref } from 'vue'
const { proxy } = getCurrentInstance()
const props = defineProps({
tenantBizId: {
type: String,
default: () => undefined
},
activeTab: {
type: String,
default: () => 'project'
}
})
const { sys_role_type, bx_product_type, sys_scope, bx_smoking_allowed, bx_currency_type } =
proxy.useDict(
'sys_scope',
'sys_role_type',
'bx_product_type',
'bx_product_status',
'bx_smoking_allowed',
'bx_currency_type'
)
const route = useRoute()
const tenantRoleList = ref([])
const importTenantRoleList = ref([])
const roleShowSearch = ref(true)
const importTenantRoleListShowSearch = ref(true)
const roleLoading = ref(true)
const importTenantRoleListLoading = ref(true)
const roleTotal = ref(0)
const importTenantRoleListTotal = ref(0)
const title = ref('')
const importUserTitle = ref('')
const importRoleTitle = ref('')
const importMenuTitle = ref('')
const importTenantProjectListOpen = ref(false)
const importTenantUserListOpen = ref(false)
const importTenantRoleListOpen = ref(false)
const importTenantMenuListOpen = ref(false)
const importTenantProjectListIds = ref([])
const importTenantProjectListNames = ref([])
const importTenantUserListIds = ref([])
const importTenantUserListNames = ref([])
const importTenantRoleListIds = ref([])
const importTenantRoleListNames = ref([])
const data = reactive({
importTenantRoleListQueryParams: {
pageNo: 1,
pageSize: 10,
tenantBizId: props.tenantBizId,
productName: undefined
},
roleQueryParams: {
pageNo: 1,
pageSize: 10,
tenantBizId: props.tenantBizId,
productName: undefined
}
})
const { roleQueryParams, importTenantRoleListQueryParams } = toRefs(data)
//========保险产品-列表逻辑开始=========
/** 查询租户保险产品关系列表 */
function getProductList() {
roleLoading.value = true
getRelTenantInsuranceList(roleQueryParams.value).then(response => {
tenantRoleList.value = response.data.records
roleTotal.value = response.data.total
roleLoading.value = false
})
}
/** 保险产品列表-搜索 */
function roleHandleQuery() {
roleQueryParams.value.pageNo = 1
getProductList()
}
/** 保险产品列表-重置按钮操作 */
function insuranceResetQuery() {
proxy.resetForm('roleQueryRef')
roleHandleQuery()
}
/** 删除租户保险产品关系 */
function ProductHandleDelete(row) {
//租户和保险产品关系表主键id
const id = row.id
const productName = row.productName
proxy.$modal
.confirm('是否确认删除产品名称为"' + productName + '"的租户和保险产品关系的数据项?')
.then(function () {
return delInsuranceProduct(id)
})
.then(() => {
getProductList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
//========保险产品-列表逻辑结束=========
//========保险产品-导入逻辑开始=========
/** 保险产品列表-导入-查询导入租户保险产品关系列表 */
function getImportTenantRoleList() {
importTenantRoleListLoading.value = true
getImportRelTenantInsuranceList(importTenantRoleListQueryParams.value).then(response => {
if (response.code === 200) {
if (response.data && response.data.records.length > 0) {
importTenantRoleList.value = response.data.records
importTenantRoleListTotal.value = response.data.total
importTenantRoleListLoading.value = false
} else {
importTenantRoleList.value = []
importTenantRoleListTotal.value = 0
importTenantRoleListLoading.value = false
}
} else {
proxy.$modal.msgError(response.msg)
importTenantRoleListLoading.value = false
}
})
}
/** 保险产品列表-导入保险产品 */
function handleImportTenantRoleList() {
getImportTenantRoleList()
importTenantRoleListOpen.value = true
importRoleTitle.value = '导保险产品'
}
/** 保险产品列表-导入-搜索按钮操作 */
function importTenantRoleListHandleQuery() {
importTenantRoleListQueryParams.value.pageNo = 1
getImportTenantRoleList()
}
/** 保险产品列表-导入-重置按钮操作 */
function importTenantRoleListResetQuery() {
proxy.resetForm('importTenantRoleListQueryRef')
importTenantRoleListHandleQuery()
}
/** 保险产品列表-导入-多选框选中数据 */
function importTenantRoleListHandleSelectionChange(selection) {
importTenantRoleListIds.value = selection.map(item => item.productBizId)
importTenantRoleListNames.value = selection.map(item => item.productName)
}
/** 保险产品列表-导入-导入角色提交 */
function importTenantRoleListSubmitForm() {
const productBizIdList = importTenantRoleListIds.value
const importTenantRoleListNameList = importTenantRoleListNames.value
const tenantBizId = props.tenantBizId
proxy.$modal
.confirm('是否确认导入产品名称为"' + importTenantRoleListNameList + '"的数据项?')
.then(function () {
const data = {
productBizIdList,
tenantBizId
}
return addImportInsuranceProductList(data)
})
.then(() => {
importTenantRoleListOpen.value = false
getProductList()
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
}
/** 保险产品列表-导入-取消 */
function importTenantRoleListCancel() {
importTenantRoleListOpen.value = false
proxy.resetForm('importTenantRoleListQueryRef')
}
//========保险产品-导入逻辑结束=========
// 监听tab切换
watch(
() => props.activeTab,
newTab => {
if (newTab == 'insurance') {
insuranceResetQuery()
}
},
{ immediate: true }
)
</script>
<style lang="scss" scoped></style>
<template> <template>
<div class="app-container"> <div class="app-container">
<!-- 选项卡组件:增加 custom-tabs 类名 --> <!-- 选项卡组件:增加 custom-tabs 类名 -->
<el-tabs <el-tabs v-model="activeTab" type="card" @tab-change="handleTabChange" class="custom-tabs">
v-model="activeTab"
type="card"
@tab-change="handleTabChange"
class="custom-tabs"
>
<!-- 项目权限选项卡 --> <!-- 项目权限选项卡 -->
<el-tab-pane label="项目" name="project" class="tab-pane-content"> <el-tab-pane label="项目" name="project" class="tab-pane-content">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true" label-width="68px"> <el-form
:model="queryParams"
ref="queryRef"
v-show="showSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="项目名称" prop="roleName"> <el-form-item label="项目名称" prop="roleName">
<el-input <el-input
v-model="queryParams.projectName" v-model="queryParams.projectName"
placeholder="请输入项目名称" placeholder="请输入项目名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportTenantProjectList">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImportTenantProjectList"
>导入</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -41,38 +44,56 @@ ...@@ -41,38 +44,56 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" >删除</el-button> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="total > 0" v-show="total > 0"
:total="total" :total="total"
v-model:page="queryParams.pageNo" v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize" v-model:limit="queryParams.pageSize"
@pagination="getList" @pagination="getList"
/> />
<!-- 项目导入(根据权限从项目池导入进来) --> <!-- 项目导入(根据权限从项目池导入进来) -->
<el-dialog :title="title" v-model="importTenantProjectListOpen" width="700px" append-to-body> <el-dialog
<el-form :model="importTenantProjectListQueryParams" ref="importTenantProjectListQueryRef" :inline="true" label-width="68px"> :title="title"
v-model="importTenantProjectListOpen"
width="700px"
append-to-body
>
<el-form
:model="importTenantProjectListQueryParams"
ref="importTenantProjectListQueryRef"
:inline="true"
label-width="68px"
>
<el-form-item label="项目名称" prop="projectName"> <el-form-item label="项目名称" prop="projectName">
<el-input <el-input
v-model="importTenantProjectListQueryParams.projectName" v-model="importTenantProjectListQueryParams.projectName"
placeholder="请输入项目名称" placeholder="请输入项目名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importTenantProjectListHandleQuery" @keyup.enter="importTenantProjectListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="importTenantProjectListHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="importTenantProjectListHandleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="importTenantProjectListResetQuery">重置</el-button> <el-button icon="Refresh" @click="importTenantProjectListResetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 表格数据 --> <!-- 表格数据 -->
<el-table v-loading="importTenantProjectListLoading" :data="importTenantProjectList" @selection-change="importTenantProjectListHandleSelectionChange"> <el-table
v-loading="importTenantProjectListLoading"
:data="importTenantProjectList"
@selection-change="importTenantProjectListHandleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目名称" prop="projectName" /> <el-table-column label="项目名称" prop="projectName" />
<el-table-column prop="scope" label="作用域"> <el-table-column prop="scope" label="作用域">
...@@ -82,11 +103,11 @@ ...@@ -82,11 +103,11 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="importTenantProjectListTotal > 0" v-show="importTenantProjectListTotal > 0"
:total="importTenantProjectListTotal" :total="importTenantProjectListTotal"
v-model:page="importTenantProjectListQueryParams.pageNo" v-model:page="importTenantProjectListQueryParams.pageNo"
v-model:limit="importTenantProjectListQueryParams.pageSize" v-model:limit="importTenantProjectListQueryParams.pageSize"
@pagination="getImportTenantProjectList" @pagination="getImportTenantProjectList"
/> />
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
...@@ -95,42 +116,49 @@ ...@@ -95,42 +116,49 @@
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
</el-tab-pane> </el-tab-pane>
<!-- 用户权限选项卡 --> <!-- 用户权限选项卡 -->
<el-tab-pane label="用户" name="user" class="tab-pane-content"> <el-tab-pane label="用户" name="user" class="tab-pane-content">
<el-form :model="userQueryParams" ref="userQueryRef" v-show="userShowSearch" :inline="true" label-width="68px"> <el-form
:model="userQueryParams"
ref="userQueryRef"
v-show="userShowSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="账号" prop="userName"> <el-form-item label="账号" prop="userName">
<el-input <el-input
v-model="userQueryParams.userName" v-model="userQueryParams.userName"
placeholder="请输入账号" placeholder="请输入账号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="userHandleQuery" @keyup.enter="userHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="姓名" prop="realName"> <el-form-item label="姓名" prop="realName">
<el-input <el-input
v-model="userQueryParams.realName" v-model="userQueryParams.realName"
placeholder="请输入姓名" placeholder="请输入姓名"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="userHandleQuery" @keyup.enter="userHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="手机号" prop="mobile"> <el-form-item label="手机号" prop="mobile">
<el-input <el-input
v-model="userQueryParams.mobile" v-model="userQueryParams.mobile"
placeholder="请输入手机号" placeholder="请输入手机号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="userHandleQuery" @keyup.enter="userHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="userHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="userHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="userResetQuery">重置</el-button> <el-button icon="Refresh" @click="userResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportTenantUserList">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImportTenantUserList"
>导入</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -146,59 +174,85 @@ ...@@ -146,59 +174,85 @@
<dict-tag :options="sys_gender" :value="scope.row.gender" /> <dict-tag :options="sys_gender" :value="scope.row.gender" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px"> <el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="160px"
>
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)" >分配角色</el-button> <el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)"
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)" >删除</el-button> >分配角色</el-button
>
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="userTotal > 0" v-show="userTotal > 0"
:total="userTotal" :total="userTotal"
v-model:page="userQueryParams.pageNo" v-model:page="userQueryParams.pageNo"
v-model:limit="userQueryParams.pageSize" v-model:limit="userQueryParams.pageSize"
@pagination="getUserList" @pagination="getUserList"
/> />
<!-- 用户导入(根据权限从用户池导入进来) --> <!-- 用户导入(根据权限从用户池导入进来) -->
<el-dialog :title="importUserTitle" v-model="importTenantUserListOpen" width="800px" append-to-body> <el-dialog
<el-form :model="importTenantUserListQueryParams" ref="importTenantUserListQueryRef" v-show="importTenantUserListShowSearch" :inline="true" label-width="55px"> :title="importUserTitle"
v-model="importTenantUserListOpen"
width="800px"
append-to-body
>
<el-form
:model="importTenantUserListQueryParams"
ref="importTenantUserListQueryRef"
v-show="importTenantUserListShowSearch"
:inline="true"
label-width="55px"
>
<el-form-item label="账号" prop="userName"> <el-form-item label="账号" prop="userName">
<el-input <el-input
v-model="importTenantUserListQueryParams.userName" v-model="importTenantUserListQueryParams.userName"
placeholder="请输入账号" placeholder="请输入账号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importTenantUserListHandleQuery" @keyup.enter="importTenantUserListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="姓名" prop="realName"> <el-form-item label="姓名" prop="realName">
<el-input <el-input
v-model="importTenantUserListQueryParams.realName" v-model="importTenantUserListQueryParams.realName"
placeholder="请输入姓名" placeholder="请输入姓名"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importTenantUserListHandleQuery" @keyup.enter="importTenantUserListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="手机号" prop="mobile"> <el-form-item label="手机号" prop="mobile">
<el-input <el-input
v-model="importTenantUserListQueryParams.mobile" v-model="importTenantUserListQueryParams.mobile"
placeholder="请输入手机号" placeholder="请输入手机号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importTenantUserListHandleQuery" @keyup.enter="importTenantUserListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="importTenantUserListHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="importTenantUserListHandleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="importTenantUserListResetQuery">重置</el-button> <el-button icon="Refresh" @click="importTenantUserListResetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 表格数据 --> <!-- 表格数据 -->
<el-table v-loading="importTenantUserListLoading" :data="importTenantUserList" @selection-change="importTenantUserListHandleSelectionChange"> <el-table
v-loading="importTenantUserListLoading"
:data="importTenantUserList"
@selection-change="importTenantUserListHandleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="账号" prop="userName" /> <el-table-column label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" /> <el-table-column label="姓名" prop="realName" />
...@@ -212,11 +266,11 @@ ...@@ -212,11 +266,11 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="importTenantUserListTotal > 0" v-show="importTenantUserListTotal > 0"
:total="importTenantUserListTotal" :total="importTenantUserListTotal"
v-model:page="importTenantUserListQueryParams.pageNo" v-model:page="importTenantUserListQueryParams.pageNo"
v-model:limit="importTenantUserListQueryParams.pageSize" v-model:limit="importTenantUserListQueryParams.pageSize"
@pagination="getImportTenantUserList" @pagination="getImportTenantUserList"
/> />
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
...@@ -228,35 +282,43 @@ ...@@ -228,35 +282,43 @@
<!-- 用户分配角色弹窗 --> <!-- 用户分配角色弹窗 -->
<el-dialog <el-dialog
:title="assignRoleTitle" :title="assignRoleTitle"
v-model="assignRoleDialogVisible" v-model="assignRoleDialogVisible"
width="1100px" width="1100px"
append-to-body append-to-body
> >
<!-- 弹窗内容:左侧可选角色 + 中间箭头 + 右侧已选角色 --> <!-- 弹窗内容:左侧可选角色 + 中间箭头 + 右侧已选角色 -->
<div class="assign-role-container"> <div class="assign-role-container">
<!-- 左侧:可选角色列表(带复选框) --> <!-- 左侧:可选角色列表(带复选框) -->
<div class="left-panel"> <div class="left-panel">
<el-form :model="leftQuery" ref="leftQueryRef" :inline="true" label-width="68px" class="search-form"> <el-form
:model="leftQuery"
ref="leftQueryRef"
:inline="true"
label-width="68px"
class="search-form"
>
<el-form-item label="角色名称"> <el-form-item label="角色名称">
<el-input <el-input
v-model="leftQuery.roleName" v-model="leftQuery.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
@keyup.enter="getLeftRoleList" @keyup.enter="getLeftRoleList"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="queryLeftRoleList">搜索</el-button> <el-button type="primary" icon="Search" @click="queryLeftRoleList"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetLeftRoleQuery">重置</el-button> <el-button icon="Refresh" @click="resetLeftRoleQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table <el-table
v-loading="leftLoading" v-loading="leftLoading"
:data="leftRoleList" :data="leftRoleList"
@selection-change="handleLeftSelectionChange" @selection-change="handleLeftSelectionChange"
border border
> >
<el-table-column type="selection" align="center" /> <el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" /> <el-table-column label="角色名称" prop="roleName" />
...@@ -273,11 +335,11 @@ ...@@ -273,11 +335,11 @@
</el-table> </el-table>
<pagination <pagination
v-show="leftTotal > 0" v-show="leftTotal > 0"
:total="leftTotal" :total="leftTotal"
v-model:page="leftQuery.pageNo" v-model:page="leftQuery.pageNo"
v-model:limit="leftQuery.pageSize" v-model:limit="leftQuery.pageSize"
@pagination="getLeftRoleList" @pagination="getLeftRoleList"
/> />
</div> </div>
...@@ -287,20 +349,20 @@ ...@@ -287,20 +349,20 @@
<!-- 向右箭头:添加到右侧 --> <!-- 向右箭头:添加到右侧 -->
<el-tooltip content="添加选中角色到已选列表" placement="top"> <el-tooltip content="添加选中角色到已选列表" placement="top">
<el-button <el-button
icon="ArrowRight" icon="ArrowRight"
@click="moveToRight" @click="moveToRight"
:disabled="leftSelectedRoles.length === 0" :disabled="leftSelectedRoles.length === 0"
class="arrow-btn" class="arrow-btn"
/> />
</el-tooltip> </el-tooltip>
<!-- 向左箭头:移除到左侧 --> <!-- 向左箭头:移除到左侧 -->
<el-tooltip content="移除选中角色到可选列表" placement="top"> <el-tooltip content="移除选中角色到可选列表" placement="top">
<el-button <el-button
icon="ArrowLeft" icon="ArrowLeft"
@click="moveToLeft" @click="moveToLeft"
:disabled="rightSelectedRoles.length === 0" :disabled="rightSelectedRoles.length === 0"
class="arrow-btn" class="arrow-btn"
/> />
</el-tooltip> </el-tooltip>
</div> </div>
...@@ -310,26 +372,34 @@ ...@@ -310,26 +372,34 @@
<div class="right-panel"> <div class="right-panel">
<div class="selected-title">已选角色({{ rightRoleList.length }}个)</div> <div class="selected-title">已选角色({{ rightRoleList.length }}个)</div>
<el-form :model="rightQuery" ref="rightQueryRef" :inline="true" label-width="68px" class="search-form"> <el-form
:model="rightQuery"
ref="rightQueryRef"
:inline="true"
label-width="68px"
class="search-form"
>
<el-form-item label="角色名称"> <el-form-item label="角色名称">
<el-input <el-input
v-model="rightQuery.roleName" v-model="rightQuery.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
@keyup.enter="getRightRoleList" @keyup.enter="getRightRoleList"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="queryRightRoleList">搜索</el-button> <el-button type="primary" icon="Search" @click="queryRightRoleList"
>搜索</el-button
>
<el-button icon="Refresh" @click="resetRightRoleQuery">重置</el-button> <el-button icon="Refresh" @click="resetRightRoleQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-table <el-table
v-loading="rightLoading" v-loading="rightLoading"
:data="rightRoleList" :data="rightRoleList"
@selection-change="handleRightSelectionChange" @selection-change="handleRightSelectionChange"
border border
> >
<el-table-column type="selection" align="center" /> <el-table-column type="selection" align="center" />
<el-table-column label="角色名称" prop="roleName" /> <el-table-column label="角色名称" prop="roleName" />
...@@ -346,11 +416,11 @@ ...@@ -346,11 +416,11 @@
</el-table> </el-table>
<pagination <pagination
v-show="rightTotal > 0" v-show="rightTotal > 0"
:total="rightTotal" :total="rightTotal"
v-model:page="rightQuery.pageNo" v-model:page="rightQuery.pageNo"
v-model:limit="rightQuery.pageSize" v-model:limit="rightQuery.pageSize"
@pagination="getRightRoleList" @pagination="getRightRoleList"
/> />
</div> </div>
</div> </div>
...@@ -358,7 +428,6 @@ ...@@ -358,7 +428,6 @@
<!-- 底部统一保存按钮 --> <!-- 底部统一保存按钮 -->
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<!-- <el-button type="primary" @click="saveAllRoles">确 定</el-button>-->
<el-button @click="cancelAssignRole">取 消</el-button> <el-button @click="cancelAssignRole">取 消</el-button>
</div> </div>
</template> </template>
...@@ -366,20 +435,28 @@ ...@@ -366,20 +435,28 @@
</el-tab-pane> </el-tab-pane>
<!-- 角色权限选项卡 --> <!-- 角色权限选项卡 -->
<el-tab-pane label="角色" name="role" class="tab-pane-content"> <el-tab-pane label="角色" name="role" class="tab-pane-content">
<el-form :model="roleQueryParams" ref="roleQueryRef" v-show="roleShowSearch" :inline="true" label-width="68px"> <el-form
:model="roleQueryParams"
ref="roleQueryRef"
v-show="roleShowSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="角色名称" prop="roleName"> <el-form-item label="角色名称" prop="roleName">
<el-input <el-input
v-model="roleQueryParams.roleName" v-model="roleQueryParams.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="roleHandleQuery" @keyup.enter="roleHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="roleResetQuery">重置</el-button> <el-button icon="Refresh" @click="roleResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportTenantRoleList">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImportTenantRoleList"
>导入</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
...@@ -396,20 +473,29 @@ ...@@ -396,20 +473,29 @@
<dict-tag :options="sys_role_type" :value="scope.row.roleType" /> <dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="160px"> <el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="160px"
>
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)" >分配菜单</el-button> <el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)"
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)" >删除</el-button> >分配菜单</el-button
>
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="roleTotal > 0" v-show="roleTotal > 0"
:total="roleTotal" :total="roleTotal"
v-model:page="roleQueryParams.pageNo" v-model:page="roleQueryParams.pageNo"
v-model:limit="roleQueryParams.pageSize" v-model:limit="roleQueryParams.pageSize"
@pagination="getRoleList" @pagination="getRoleList"
/> />
<!-- 角色配置-分配菜单弹出框 --> <!-- 角色配置-分配菜单弹出框 -->
...@@ -418,15 +504,15 @@ ...@@ -418,15 +504,15 @@
<div class="permission-section"> <div class="permission-section">
<el-card header="分配菜单" class="mt-4"> <el-card header="分配菜单" class="mt-4">
<el-tree <el-tree
ref="fpMenuRef" ref="fpMenuRef"
:data="fpMenuTree" :data="fpMenuTree"
:props="fpMenuProps" :props="fpMenuProps"
node-key="menuBizId" node-key="menuBizId"
show-checkbox show-checkbox
highlight-current highlight-current
:check-strictly="fpMenuIsCheckStrictly" :check-strictly="fpMenuIsCheckStrictly"
:expand-on-click-node="false" :expand-on-click-node="false"
class="permission-tree" class="permission-tree"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
...@@ -443,24 +529,41 @@ ...@@ -443,24 +529,41 @@
</el-dialog> </el-dialog>
<!-- 角色导入(根据权限从角色池导入进来) --> <!-- 角色导入(根据权限从角色池导入进来) -->
<el-dialog :title="importRoleTitle" v-model="importTenantRoleListOpen" width="800px" append-to-body> <el-dialog
<el-form :model="importTenantRoleListQueryParams" ref="importTenantRoleListQueryRef" v-show="importTenantRoleListShowSearch" :inline="true" label-width="70px"> :title="importRoleTitle"
v-model="importTenantRoleListOpen"
width="800px"
append-to-body
>
<el-form
:model="importTenantRoleListQueryParams"
ref="importTenantRoleListQueryRef"
v-show="importTenantRoleListShowSearch"
:inline="true"
label-width="70px"
>
<el-form-item label="角色名称" prop="roleName"> <el-form-item label="角色名称" prop="roleName">
<el-input <el-input
v-model="importTenantRoleListQueryParams.roleName" v-model="importTenantRoleListQueryParams.roleName"
placeholder="请输入角色名称" placeholder="请输入角色名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="importTenantRoleListHandleQuery" @keyup.enter="importTenantRoleListHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="importTenantRoleListHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="importTenantRoleListHandleQuery"
>搜索</el-button
>
<el-button icon="Refresh" @click="importTenantRoleListResetQuery">重置</el-button> <el-button icon="Refresh" @click="importTenantRoleListResetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 表格数据 --> <!-- 表格数据 -->
<el-table v-loading="importTenantRoleListLoading" :data="importTenantRoleList" @selection-change="importTenantRoleListHandleSelectionChange"> <el-table
v-loading="importTenantRoleListLoading"
:data="importTenantRoleList"
@selection-change="importTenantRoleListHandleSelectionChange"
>
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色名称" prop="roleName" /> <el-table-column label="角色名称" prop="roleName" />
<el-table-column prop="scope" label="作用域"> <el-table-column prop="scope" label="作用域">
...@@ -475,11 +578,11 @@ ...@@ -475,11 +578,11 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="importTenantRoleListTotal > 0" v-show="importTenantRoleListTotal > 0"
:total="importTenantRoleListTotal" :total="importTenantRoleListTotal"
v-model:page="importTenantRoleListQueryParams.pageNo" v-model:page="importTenantRoleListQueryParams.pageNo"
v-model:limit="importTenantRoleListQueryParams.pageSize" v-model:limit="importTenantRoleListQueryParams.pageSize"
@pagination="getImportTenantRoleList" @pagination="getImportTenantRoleList"
/> />
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
...@@ -491,44 +594,54 @@ ...@@ -491,44 +594,54 @@
</el-tab-pane> </el-tab-pane>
<!-- 菜单权限选项卡 --> <!-- 菜单权限选项卡 -->
<el-tab-pane label="菜单" name="menu" class="tab-pane-content"> <el-tab-pane label="菜单" name="menu" class="tab-pane-content">
<el-form :model="menuQueryParams" ref="menuQueryRef" v-show="menuShowSearch" :inline="true" label-width="68px"> <el-form
:model="menuQueryParams"
ref="menuQueryRef"
v-show="menuShowSearch"
:inline="true"
label-width="68px"
>
<el-form-item label="菜单名称" prop="menuName"> <el-form-item label="菜单名称" prop="menuName">
<el-input <el-input
v-model="menuQueryParams.menuName" v-model="menuQueryParams.menuName"
placeholder="请输入菜单名称" placeholder="请输入菜单名称"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="menuHandleQuery" @keyup.enter="menuHandleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" icon="Search" @click="menuHandleQuery">搜索</el-button> <el-button type="primary" icon="Search" @click="menuHandleQuery">搜索</el-button>
<el-button icon="Refresh" @click="menuResetQuery">重置</el-button> <el-button icon="Refresh" @click="menuResetQuery">重置</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportTenantMenuList">导入</el-button> <el-button type="info" plain icon="Upload" @click="handleImportTenantMenuList"
>导入</el-button
>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-row :gutter="10" class="mb8"> <el-row :gutter="10" class="mb8">
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col> </el-col>
<right-toolbar v-model:showSearch="menuShowSearch" @queryTable="getMenuList"></right-toolbar> <right-toolbar
v-model:showSearch="menuShowSearch"
@queryTable="getMenuList"
></right-toolbar>
</el-row> </el-row>
<el-table <el-table
v-if="refreshTable" v-if="refreshTable"
v-loading="menuLoading" v-loading="menuLoading"
:data="menuList" :data="menuList"
row-key="menuBizId" row-key="menuBizId"
:default-expand-all="isExpandAll" :default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
> >
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true"></el-table-column> <el-table-column
prop="menuName"
label="菜单名称"
:show-overflow-tooltip="true"
></el-table-column>
<el-table-column prop="icon" label="图标" align="center"> <el-table-column prop="icon" label="图标" align="center">
<template #default="scope"> <template #default="scope">
<svg-icon :icon-class="scope.row.icon" /> <svg-icon :icon-class="scope.row.icon" />
...@@ -546,27 +659,38 @@ ...@@ -546,27 +659,38 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-if="scope.row.parentBizId === '0'">删除</el-button> <el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-if="scope.row.parentBizId === '0'"
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- 菜单导入(根据权限从菜单池导入进来) --> <!-- 菜单导入(根据权限从菜单池导入进来) -->
<el-dialog :title="importMenuTitle" v-model="importTenantMenuListOpen" width="500px" append-to-body> <el-dialog
:title="importMenuTitle"
v-model="importTenantMenuListOpen"
width="500px"
append-to-body
>
<!-- 权限分配区域 --> <!-- 权限分配区域 -->
<div class="permission-section"> <div class="permission-section">
<el-card header="导入菜单" class="mt-4"> <el-card header="导入菜单" class="mt-4">
<el-tree <el-tree
ref="treeRef" ref="treeRef"
:data="menuTree" :data="menuTree"
:props="defaultProps" :props="defaultProps"
node-key="menuBizId" node-key="menuBizId"
show-checkbox show-checkbox
highlight-current highlight-current
:check-strictly="isCheckStrictly" :check-strictly="isCheckStrictly"
:expand-on-click-node="false" :expand-on-click-node="false"
class="permission-tree" class="permission-tree"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
...@@ -582,24 +706,64 @@ ...@@ -582,24 +706,64 @@
</div> </div>
</el-dialog> </el-dialog>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="部门" name="dept" class="tab-pane-content">
<DeptList :tenantBizId="route.query.tenantBizId" :activeTab="activeTab" />
</el-tab-pane>
<el-tab-pane label="保险产品" name="insurance" class="tab-pane-content">
<InsuranceList :tenantBizId="route.query.tenantBizId" :activeTab="activeTab" />
</el-tab-pane>
</el-tabs> </el-tabs>
</div> </div>
</template> </template>
<script setup name="TenantPermission"> <script setup name="TenantPermission">
import {
import { listTenantProject,listImportTenantProject,addImportTenantProjectList, listTenantProject,
delRelTenantProject,listTenantUser,listImportTenantUser, listImportTenantProject,
addImportTenantUserList,delRelTenantUser,listTenantRole, addImportTenantProjectList,
delRelTenantRole,addImportTenantRoleList,listImportTenantRole, delRelTenantProject,
listMenu,getMenuTree,getImportSelectedMenuList,addImportTenantMenuList, listTenantUser,
listLeftRole, listRightRole,addRightRoleList,delRightRoleList, listImportTenantUser,
getFpMenuTree,getSelectedFpMenuList,addFpMenuList} from "@/api/system/tenantPermission" addImportTenantUserList,
delRelTenantUser,
listTenantRole,
delRelTenantRole,
addImportTenantRoleList,
listImportTenantRole,
listMenu,
getMenuTree,
getImportSelectedMenuList,
addImportTenantMenuList,
listLeftRole,
listRightRole,
addRightRoleList,
delRightRoleList,
getFpMenuTree,
getSelectedFpMenuList,
addFpMenuList
} from '@/api/system/tenantPermission'
import DeptList from './deptList.vue'
import InsuranceList from './insuranceList.vue'
import { ref } from 'vue' import { ref } from 'vue'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_status,sys_scope,sys_no_yes,sys_user_status,sys_gender,sys_role_type,sys_menu_type } = proxy.useDict("sys_status","sys_scope","sys_no_yes","sys_user_status","sys_gender","sys_role_type","sys_menu_type") const {
sys_status,
sys_scope,
sys_no_yes,
sys_user_status,
sys_gender,
sys_role_type,
sys_menu_type
} = proxy.useDict(
'sys_status',
'sys_scope',
'sys_no_yes',
'sys_user_status',
'sys_gender',
'sys_role_type',
'sys_menu_type'
)
const route = useRoute() const route = useRoute()
const activeTab = ref('project') const activeTab = ref('project')
...@@ -632,10 +796,10 @@ const userTotal = ref(0) ...@@ -632,10 +796,10 @@ const userTotal = ref(0)
const importTenantUserListTotal = ref(0) const importTenantUserListTotal = ref(0)
const roleTotal = ref(0) const roleTotal = ref(0)
const importTenantRoleListTotal = ref(0) const importTenantRoleListTotal = ref(0)
const title = ref("") const title = ref('')
const importUserTitle = ref("") const importUserTitle = ref('')
const importRoleTitle = ref("") const importRoleTitle = ref('')
const importMenuTitle = ref("") const importMenuTitle = ref('')
const importTenantProjectListOpen = ref(false) const importTenantProjectListOpen = ref(false)
const importTenantUserListOpen = ref(false) const importTenantUserListOpen = ref(false)
const importTenantRoleListOpen = ref(false) const importTenantRoleListOpen = ref(false)
...@@ -647,13 +811,12 @@ const importTenantUserListNames = ref([]) ...@@ -647,13 +811,12 @@ const importTenantUserListNames = ref([])
const importTenantRoleListIds = ref([]) const importTenantRoleListIds = ref([])
const importTenantRoleListNames = ref([]) const importTenantRoleListNames = ref([])
// 菜单树数据(直接从接口获取树形结构) // 菜单树数据(直接从接口获取树形结构)
const menuTree = ref([]) const menuTree = ref([])
// 树组件引用 // 树组件引用
const treeRef = ref(null) const treeRef = ref(null)
// 添加响应式变量控制严格模式 // 添加响应式变量控制严格模式
const isCheckStrictly = ref(false); // 默认关闭严格模式(启用联动) const isCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置 // 树形配置
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
...@@ -709,9 +872,17 @@ const data = reactive({ ...@@ -709,9 +872,17 @@ const data = reactive({
menuName: undefined menuName: undefined
} }
}) })
const { queryParams,importTenantProjectListQueryParams, userQueryParams, const {
importTenantUserListQueryParams,roleQueryParams,importTenantRoleListQueryParams, queryParams,
menuQueryParams,form, rules } = toRefs(data) importTenantProjectListQueryParams,
userQueryParams,
importTenantUserListQueryParams,
roleQueryParams,
importTenantRoleListQueryParams,
menuQueryParams,
form,
rules
} = toRefs(data)
//========项目-列表逻辑========= //========项目-列表逻辑=========
/** 查询租户项目关系列表 */ /** 查询租户项目关系列表 */
...@@ -730,7 +901,7 @@ function handleQuery() { ...@@ -730,7 +901,7 @@ function handleQuery() {
} }
/** 项目列表-重置按钮操作 */ /** 项目列表-重置按钮操作 */
function resetQuery() { function resetQuery() {
proxy.resetForm("queryRef") proxy.resetForm('queryRef')
handleQuery() handleQuery()
} }
/** 删除租户项目关系 */ /** 删除租户项目关系 */
...@@ -738,12 +909,16 @@ function handleDelete(row) { ...@@ -738,12 +909,16 @@ function handleDelete(row) {
//租户和项目关系表主键id //租户和项目关系表主键id
const relTenantProjectId = row.id const relTenantProjectId = row.id
const projectName = row.projectName const projectName = row.projectName
proxy.$modal.confirm('是否确认删除项目名称为"' + projectName + '"的租户和项目关系的数据项?').then(function () { proxy.$modal
return delRelTenantProject(relTenantProjectId) .confirm('是否确认删除项目名称为"' + projectName + '"的租户和项目关系的数据项?')
}).then(() => { .then(function () {
getList() return delRelTenantProject(relTenantProjectId)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
//========项目-列表结束========= //========项目-列表结束=========
...@@ -761,7 +936,7 @@ function getImportTenantProjectList() { ...@@ -761,7 +936,7 @@ function getImportTenantProjectList() {
function handleImportTenantProjectList() { function handleImportTenantProjectList() {
getImportTenantProjectList() getImportTenantProjectList()
importTenantProjectListOpen.value = true importTenantProjectListOpen.value = true
title.value = "导入项目" title.value = '导入项目'
} }
/** 搜索按钮操作 */ /** 搜索按钮操作 */
function importTenantProjectListHandleQuery() { function importTenantProjectListHandleQuery() {
...@@ -770,7 +945,7 @@ function importTenantProjectListHandleQuery() { ...@@ -770,7 +945,7 @@ function importTenantProjectListHandleQuery() {
} }
/** 重置按钮操作 */ /** 重置按钮操作 */
function importTenantProjectListResetQuery() { function importTenantProjectListResetQuery() {
proxy.resetForm("importTenantProjectListQueryRef") proxy.resetForm('importTenantProjectListQueryRef')
importTenantProjectListHandleQuery() importTenantProjectListHandleQuery()
} }
/** 多选框选中数据 */ /** 多选框选中数据 */
...@@ -784,22 +959,25 @@ function importTenantProjectListSubmitForm() { ...@@ -784,22 +959,25 @@ function importTenantProjectListSubmitForm() {
const importTenantProjectListNameList = importTenantProjectListNames.value const importTenantProjectListNameList = importTenantProjectListNames.value
const tenantBizId = route.query.tenantBizId const tenantBizId = route.query.tenantBizId
proxy.$modal.confirm('是否确认导入项目名称为"' + importTenantProjectListNameList + '"的数据项?').then(function () { proxy.$modal
return addImportTenantProjectList(importTenantProjectListIdList,tenantBizId) .confirm('是否确认导入项目名称为"' + importTenantProjectListNameList + '"的数据项?')
}).then(() => { .then(function () {
importTenantProjectListOpen.value = false return addImportTenantProjectList(importTenantProjectListIdList, tenantBizId)
getList() })
proxy.$modal.msgSuccess("导入成功") .then(() => {
}).catch(() => {}) importTenantProjectListOpen.value = false
getList()
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
} }
/** 导入项目取消 */ /** 导入项目取消 */
function importTenantProjectListCancel(){ function importTenantProjectListCancel() {
importTenantProjectListOpen.value = false importTenantProjectListOpen.value = false
proxy.resetForm("importTenantProjectListQueryRef") proxy.resetForm('importTenantProjectListQueryRef')
} }
//========项目-导入逻辑结束========= //========项目-导入逻辑结束=========
//========用户-列表逻辑开始========= //========用户-列表逻辑开始=========
/** 查询租户用户关系列表 */ /** 查询租户用户关系列表 */
function getUserList() { function getUserList() {
...@@ -817,7 +995,7 @@ function userHandleQuery() { ...@@ -817,7 +995,7 @@ function userHandleQuery() {
} }
/** 用户列表-重置按钮操作 */ /** 用户列表-重置按钮操作 */
function userResetQuery() { function userResetQuery() {
proxy.resetForm("userQueryRef") proxy.resetForm('userQueryRef')
userHandleQuery() userHandleQuery()
} }
/** 删除租户用户关系 */ /** 删除租户用户关系 */
...@@ -825,16 +1003,19 @@ function userHandleDelete(row) { ...@@ -825,16 +1003,19 @@ function userHandleDelete(row) {
//租户和用户关系表主键id //租户和用户关系表主键id
const relTenantUserId = row.id const relTenantUserId = row.id
const userName = row.userName const userName = row.userName
proxy.$modal.confirm('是否确认删除用户账号为"' + userName + '"的租户和用户关系的数据项?').then(function () { proxy.$modal
return delRelTenantUser(relTenantUserId) .confirm('是否确认删除用户账号为"' + userName + '"的租户和用户关系的数据项?')
}).then(() => { .then(function () {
getUserList() return delRelTenantUser(relTenantUserId)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getUserList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
//========用户-列表逻辑结束========= //========用户-列表逻辑结束=========
//========用户-导入逻辑开始========= //========用户-导入逻辑开始=========
/** 用户列表-导入-查询导入租户用户关系列表 */ /** 用户列表-导入-查询导入租户用户关系列表 */
function getImportTenantUserList() { function getImportTenantUserList() {
...@@ -849,7 +1030,7 @@ function getImportTenantUserList() { ...@@ -849,7 +1030,7 @@ function getImportTenantUserList() {
function handleImportTenantUserList() { function handleImportTenantUserList() {
getImportTenantUserList() getImportTenantUserList()
importTenantUserListOpen.value = true importTenantUserListOpen.value = true
importUserTitle.value = "导入用户" importUserTitle.value = '导入用户'
} }
/** 用户列表-导入-搜索按钮操作 */ /** 用户列表-导入-搜索按钮操作 */
function importTenantUserListHandleQuery() { function importTenantUserListHandleQuery() {
...@@ -858,7 +1039,7 @@ function importTenantUserListHandleQuery() { ...@@ -858,7 +1039,7 @@ function importTenantUserListHandleQuery() {
} }
/** 用户列表-导入-重置按钮操作 */ /** 用户列表-导入-重置按钮操作 */
function importTenantUserListResetQuery() { function importTenantUserListResetQuery() {
proxy.resetForm("importTenantUserListQueryRef") proxy.resetForm('importTenantUserListQueryRef')
importTenantUserListHandleQuery() importTenantUserListHandleQuery()
} }
/** 用户列表-导入-多选框选中数据 */ /** 用户列表-导入-多选框选中数据 */
...@@ -872,22 +1053,25 @@ function importTenantUserListSubmitForm() { ...@@ -872,22 +1053,25 @@ function importTenantUserListSubmitForm() {
const importTenantUserListNameList = importTenantUserListNames.value const importTenantUserListNameList = importTenantUserListNames.value
const tenantBizId = route.query.tenantBizId const tenantBizId = route.query.tenantBizId
proxy.$modal.confirm('是否确认导入用户账号为"' + importTenantUserListNameList + '"的数据项?').then(function () { proxy.$modal
return addImportTenantUserList(importTenantUserListIdList,tenantBizId) .confirm('是否确认导入用户账号为"' + importTenantUserListNameList + '"的数据项?')
}).then(() => { .then(function () {
importTenantUserListOpen.value = false return addImportTenantUserList(importTenantUserListIdList, tenantBizId)
getUserList() })
proxy.$modal.msgSuccess("导入成功") .then(() => {
}).catch(() => {}) importTenantUserListOpen.value = false
getUserList()
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
} }
/** 用户列表-导入-取消 */ /** 用户列表-导入-取消 */
function importTenantUserListCancel() { function importTenantUserListCancel() {
importTenantUserListOpen.value = false importTenantUserListOpen.value = false
proxy.resetForm("importTenantUserListQueryRef") proxy.resetForm('importTenantUserListQueryRef')
} }
//========用户-导入逻辑结束========= //========用户-导入逻辑结束=========
//========角色-列表逻辑开始========= //========角色-列表逻辑开始=========
/** 查询租户角色关系列表 */ /** 查询租户角色关系列表 */
function getRoleList() { function getRoleList() {
...@@ -905,7 +1089,7 @@ function roleHandleQuery() { ...@@ -905,7 +1089,7 @@ function roleHandleQuery() {
} }
/** 角色列表-重置按钮操作 */ /** 角色列表-重置按钮操作 */
function roleResetQuery() { function roleResetQuery() {
proxy.resetForm("roleQueryRef") proxy.resetForm('roleQueryRef')
roleHandleQuery() roleHandleQuery()
} }
/** 删除租户角色关系 */ /** 删除租户角色关系 */
...@@ -913,16 +1097,19 @@ function roleHandleDelete(row) { ...@@ -913,16 +1097,19 @@ function roleHandleDelete(row) {
//租户和角色关系表主键id //租户和角色关系表主键id
const relTenantRoleId = row.id const relTenantRoleId = row.id
const roleName = row.roleName const roleName = row.roleName
proxy.$modal.confirm('是否确认删除角色名称为"' + roleName + '"的租户和角色关系的数据项?').then(function () { proxy.$modal
return delRelTenantRole(relTenantRoleId) .confirm('是否确认删除角色名称为"' + roleName + '"的租户和角色关系的数据项?')
}).then(() => { .then(function () {
getRoleList() return delRelTenantRole(relTenantRoleId)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getRoleList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
//========角色-列表逻辑结束========= //========角色-列表逻辑结束=========
//========角色-导入逻辑开始========= //========角色-导入逻辑开始=========
/** 角色列表-导入-查询导入租户角色关系列表 */ /** 角色列表-导入-查询导入租户角色关系列表 */
function getImportTenantRoleList() { function getImportTenantRoleList() {
...@@ -937,7 +1124,7 @@ function getImportTenantRoleList() { ...@@ -937,7 +1124,7 @@ function getImportTenantRoleList() {
function handleImportTenantRoleList() { function handleImportTenantRoleList() {
getImportTenantRoleList() getImportTenantRoleList()
importTenantRoleListOpen.value = true importTenantRoleListOpen.value = true
importRoleTitle.value = "导入角色" importRoleTitle.value = '导入角色'
} }
/** 角色列表-导入-搜索按钮操作 */ /** 角色列表-导入-搜索按钮操作 */
function importTenantRoleListHandleQuery() { function importTenantRoleListHandleQuery() {
...@@ -946,7 +1133,7 @@ function importTenantRoleListHandleQuery() { ...@@ -946,7 +1133,7 @@ function importTenantRoleListHandleQuery() {
} }
/** 角色列表-导入-重置按钮操作 */ /** 角色列表-导入-重置按钮操作 */
function importTenantRoleListResetQuery() { function importTenantRoleListResetQuery() {
proxy.resetForm("importTenantRoleListQueryRef") proxy.resetForm('importTenantRoleListQueryRef')
importTenantRoleListHandleQuery() importTenantRoleListHandleQuery()
} }
/** 角色列表-导入-多选框选中数据 */ /** 角色列表-导入-多选框选中数据 */
...@@ -960,28 +1147,31 @@ function importTenantRoleListSubmitForm() { ...@@ -960,28 +1147,31 @@ function importTenantRoleListSubmitForm() {
const importTenantRoleListNameList = importTenantRoleListNames.value const importTenantRoleListNameList = importTenantRoleListNames.value
const tenantBizId = route.query.tenantBizId const tenantBizId = route.query.tenantBizId
proxy.$modal.confirm('是否确认导入角色名称为"' + importTenantRoleListNameList + '"的数据项?').then(function () { proxy.$modal
return addImportTenantRoleList(importTenantRoleListIdList,tenantBizId) .confirm('是否确认导入角色名称为"' + importTenantRoleListNameList + '"的数据项?')
}).then(() => { .then(function () {
importTenantRoleListOpen.value = false return addImportTenantRoleList(importTenantRoleListIdList, tenantBizId)
getRoleList() })
proxy.$modal.msgSuccess("导入成功") .then(() => {
}).catch(() => {}) importTenantRoleListOpen.value = false
getRoleList()
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
} }
/** 角色列表-导入-取消 */ /** 角色列表-导入-取消 */
function importTenantRoleListCancel() { function importTenantRoleListCancel() {
importTenantRoleListOpen.value = false importTenantRoleListOpen.value = false
proxy.resetForm("importTenantRoleListQueryRef") proxy.resetForm('importTenantRoleListQueryRef')
} }
//========角色-导入逻辑结束========= //========角色-导入逻辑结束=========
//========菜单-列表逻辑结束========= //========菜单-列表逻辑结束=========
/** 查询菜单列表 */ /** 查询菜单列表 */
function getMenuList() { function getMenuList() {
menuLoading.value = true menuLoading.value = true
listMenu(menuQueryParams.value).then(response => { listMenu(menuQueryParams.value).then(response => {
menuList.value = proxy.handleTree(response.data.records, "menuBizId") menuList.value = proxy.handleTree(response.data.records, 'menuBizId')
menuLoading.value = false menuLoading.value = false
}) })
} }
...@@ -1000,18 +1190,17 @@ function menuHandleQuery() { ...@@ -1000,18 +1190,17 @@ function menuHandleQuery() {
} }
/** 菜单列表-重置按钮操作 */ /** 菜单列表-重置按钮操作 */
function menuResetQuery() { function menuResetQuery() {
proxy.resetForm("menuQueryRef") proxy.resetForm('menuQueryRef')
menuHandleQuery() menuHandleQuery()
} }
//========菜单-列表逻辑结束========= //========菜单-列表逻辑结束=========
//========菜单-导入逻辑开始========= //========菜单-导入逻辑开始=========
/** 菜单列表-导入菜单 */ /** 菜单列表-导入菜单 */
function handleImportTenantMenuList() { function handleImportTenantMenuList() {
loadMenuTree() loadMenuTree()
importTenantMenuListOpen.value = true importTenantMenuListOpen.value = true
importMenuTitle.value = "导入菜单" importMenuTitle.value = '导入菜单'
} }
// 加载菜单树 // 加载菜单树
const loadMenuTree = async () => { const loadMenuTree = async () => {
...@@ -1027,32 +1216,31 @@ const loadMenuTree = async () => { ...@@ -1027,32 +1216,31 @@ const loadMenuTree = async () => {
// 修改加载选中菜单列表的逻辑 // 修改加载选中菜单列表的逻辑
const loadImportSelectedMenuList = async () => { const loadImportSelectedMenuList = async () => {
try { try {
const res = await getImportSelectedMenuList(route.query.tenantBizId); const res = await getImportSelectedMenuList(route.query.tenantBizId)
const targetKeys = res.data || []; const targetKeys = res.data || []
// 开启严格模式(禁用联动) // 开启严格模式(禁用联动)
isCheckStrictly.value = true; isCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效) // 等待DOM更新(确保严格模式生效)
await nextTick(); await nextTick()
//清空并设置选中状态(此时不会触发联动) //清空并设置选中状态(此时不会触发联动)
if (treeRef.value) { if (treeRef.value) {
treeRef.value.setCheckedKeys([]); treeRef.value.setCheckedKeys([])
treeRef.value.setCheckedKeys(targetKeys); treeRef.value.setCheckedKeys(targetKeys)
} }
//等待选中状态渲染完成 //等待选中状态渲染完成
await nextTick(); await nextTick()
//关闭严格模式(恢复联动) //关闭严格模式(恢复联动)
isCheckStrictly.value = false; isCheckStrictly.value = false
} catch (error) { } catch (error) {
console.error('加载选中的菜单列表失败:', error); console.error('加载选中的菜单列表失败:', error)
proxy.$modal.msgError('加载选中的菜单列表失败'); proxy.$modal.msgError('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态 // 异常时也要恢复严格模式状态
isCheckStrictly.value = false; isCheckStrictly.value = false
} }
} }
// 保存 // 保存
...@@ -1073,7 +1261,6 @@ const saveImportSelectedMenuList = async () => { ...@@ -1073,7 +1261,6 @@ const saveImportSelectedMenuList = async () => {
getMenuList() getMenuList()
importTenantMenuListOpen.value = false importTenantMenuListOpen.value = false
proxy.$modal.msgSuccess('更新成功') proxy.$modal.msgSuccess('更新成功')
} catch (error) { } catch (error) {
console.error('更新失败:', error) console.error('更新失败:', error)
proxy.$modal.msgError('更新失败') proxy.$modal.msgError('更新失败')
...@@ -1081,14 +1268,10 @@ const saveImportSelectedMenuList = async () => { ...@@ -1081,14 +1268,10 @@ const saveImportSelectedMenuList = async () => {
} }
//========菜单-导入逻辑结束========= //========菜单-导入逻辑结束=========
//========分配角色-导入逻辑开始========= //========分配角色-导入逻辑开始=========
// 弹窗显隐与标题 // 弹窗显隐与标题
const assignRoleDialogVisible = ref(false) const assignRoleDialogVisible = ref(false)
const assignRoleTitle = ref("分配角色") const assignRoleTitle = ref('分配角色')
// 左侧可选角色(查询参数 + 列表数据) // 左侧可选角色(查询参数 + 列表数据)
const leftQuery = reactive({ const leftQuery = reactive({
...@@ -1103,7 +1286,6 @@ const leftLoading = ref(false) ...@@ -1103,7 +1286,6 @@ const leftLoading = ref(false)
const leftTotal = ref(0) const leftTotal = ref(0)
const leftSelectedRoles = ref([]) // 左侧选中的角色 const leftSelectedRoles = ref([]) // 左侧选中的角色
// 右侧可选角色(查询参数 + 列表数据) // 右侧可选角色(查询参数 + 列表数据)
const rightQuery = reactive({ const rightQuery = reactive({
pageNo: 1, pageNo: 1,
...@@ -1142,13 +1324,15 @@ function getLeftRoleList() { ...@@ -1142,13 +1324,15 @@ function getLeftRoleList() {
leftLoading.value = true leftLoading.value = true
listLeftRole({ listLeftRole({
...leftQuery ...leftQuery
}).then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
}).catch(() => {
leftLoading.value = false
}) })
.then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
})
.catch(() => {
leftLoading.value = false
})
} }
/** 获取右侧已选角色分页列表 */ /** 获取右侧已选角色分页列表 */
...@@ -1156,13 +1340,15 @@ function getRightRoleList() { ...@@ -1156,13 +1340,15 @@ function getRightRoleList() {
rightLoading.value = true rightLoading.value = true
listRightRole({ listRightRole({
...rightQuery ...rightQuery
}).then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
}).catch(() => {
rightLoading.value = false
}) })
.then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
})
.catch(() => {
rightLoading.value = false
})
} }
/** 左侧表格选中事件 */ /** 左侧表格选中事件 */
...@@ -1183,7 +1369,7 @@ function queryLeftRoleList() { ...@@ -1183,7 +1369,7 @@ function queryLeftRoleList() {
/** 重置左侧搜索 */ /** 重置左侧搜索 */
function resetLeftRoleQuery() { function resetLeftRoleQuery() {
proxy.resetForm("leftQueryRef") proxy.resetForm('leftQueryRef')
leftQuery.pageNo = 1 leftQuery.pageNo = 1
leftQuery.roleName = '' leftQuery.roleName = ''
getLeftRoleList() getLeftRoleList()
...@@ -1197,7 +1383,7 @@ function queryRightRoleList() { ...@@ -1197,7 +1383,7 @@ function queryRightRoleList() {
/** 重置右侧搜索 */ /** 重置右侧搜索 */
function resetRightRoleQuery() { function resetRightRoleQuery() {
proxy.resetForm("rightQueryRef") proxy.resetForm('rightQueryRef')
rightQuery.pageNo = 1 rightQuery.pageNo = 1
rightQuery.roleName = '' rightQuery.roleName = ''
getRightRoleList() getRightRoleList()
...@@ -1216,15 +1402,19 @@ function moveToRight() { ...@@ -1216,15 +1402,19 @@ function moveToRight() {
userBizId: leftQuery.userBizId, userBizId: leftQuery.userBizId,
roleBizIdList: roleBizIdList roleBizIdList: roleBizIdList
} }
proxy.$modal.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?').then(function () { proxy.$modal
return addRightRoleList(data) .confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?')
}).then(() => { .then(function () {
// 加载左侧待选角色分页列表(排除已选) return addRightRoleList(data)
getLeftRoleList() })
// 加载右侧已选角色分页列表 .then(() => {
getRightRoleList() // 加载左侧待选角色分页列表(排除已选)
proxy.$modal.msgSuccess("添加成功") getLeftRoleList()
}).catch(() => {}) // 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess('添加成功')
})
.catch(() => {})
} }
/** 向左箭头:将右侧选中角色移除左侧,删除关系*/ /** 向左箭头:将右侧选中角色移除左侧,删除关系*/
...@@ -1240,15 +1430,19 @@ function moveToLeft() { ...@@ -1240,15 +1430,19 @@ function moveToLeft() {
userBizId: rightQuery.userBizId, userBizId: rightQuery.userBizId,
roleBizIdList: roleBizIdList roleBizIdList: roleBizIdList
} }
proxy.$modal.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?').then(function () { proxy.$modal
return delRightRoleList(data) .confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?')
}).then(() => { .then(function () {
// 加载左侧待选角色分页列表(排除已选) return delRightRoleList(data)
getLeftRoleList() })
// 加载右侧已选角色分页列表 .then(() => {
getRightRoleList() // 加载左侧待选角色分页列表(排除已选)
proxy.$modal.msgSuccess("移除成功") getLeftRoleList()
}).catch(() => {}) // 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess('移除成功')
})
.catch(() => {})
} }
/** 底部保存按钮:统一提交所有变更 */ /** 底部保存按钮:统一提交所有变更 */
...@@ -1263,18 +1457,21 @@ function saveAllRoles() { ...@@ -1263,18 +1457,21 @@ function saveAllRoles() {
const removeIds = oldRoleIds.filter(id => !newRoleIds.includes(id)) const removeIds = oldRoleIds.filter(id => !newRoleIds.includes(id))
// 调用接口统一保存(一次请求) // 调用接口统一保存(一次请求)
proxy.$modal.confirm(`确认要分配 ${newRoleIds.length} 个角色吗?`).then(() => { proxy.$modal
return assignUserRole({ .confirm(`确认要分配 ${newRoleIds.length} 个角色吗?`)
userId: leftQuery.userId, .then(() => {
addRoleIds: addIds, return assignUserRole({
removeRoleIds: removeIds userId: leftQuery.userId,
addRoleIds: addIds,
removeRoleIds: removeIds
})
})
.then(() => {
proxy.$modal.msgSuccess('角色分配成功')
assignRoleDialogVisible.value = false
// 可触发父组件刷新用户列表
// emit('refreshUserList')
}) })
}).then(() => {
proxy.$modal.msgSuccess("角色分配成功")
assignRoleDialogVisible.value = false
// 可触发父组件刷新用户列表
// emit('refreshUserList')
})
} }
/** 取消操作 */ /** 取消操作 */
function cancelAssignRole() { function cancelAssignRole() {
...@@ -1282,15 +1479,13 @@ function cancelAssignRole() { ...@@ -1282,15 +1479,13 @@ function cancelAssignRole() {
} }
//========分配角色-导入逻辑结束========= //========分配角色-导入逻辑结束=========
//========分配菜单-导入逻辑开始========= //========分配菜单-导入逻辑开始=========
// 菜单树数据(直接从接口获取树形结构) // 菜单树数据(直接从接口获取树形结构)
const fpMenuTree = ref([]) const fpMenuTree = ref([])
// 树组件引用 // 树组件引用
const fpMenuRef = ref(null) const fpMenuRef = ref(null)
// 添加响应式变量控制严格模式 // 添加响应式变量控制严格模式
const fpMenuIsCheckStrictly = ref(false); // 默认关闭严格模式(启用联动) const fpMenuIsCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置 // 树形配置
const fpMenuProps = { const fpMenuProps = {
children: 'children', children: 'children',
...@@ -1309,7 +1504,7 @@ const getFpMenuTreeParams = reactive({ ...@@ -1309,7 +1504,7 @@ const getFpMenuTreeParams = reactive({
tenantBizId: route.query.tenantBizId, tenantBizId: route.query.tenantBizId,
menuName: undefined menuName: undefined
}) })
const fpMenuTitle = ref("") const fpMenuTitle = ref('')
const fpMenuOpen = ref(false) const fpMenuOpen = ref(false)
/** 分配菜单弹出 */ /** 分配菜单弹出 */
...@@ -1318,12 +1513,12 @@ function handleFpMenu(row) { ...@@ -1318,12 +1513,12 @@ function handleFpMenu(row) {
getSelectedFpMenuListParams.roleBizId = row.roleBizId getSelectedFpMenuListParams.roleBizId = row.roleBizId
loadFpMenuTree() loadFpMenuTree()
fpMenuOpen.value = true fpMenuOpen.value = true
fpMenuTitle.value = "分配菜单" fpMenuTitle.value = '分配菜单'
} }
// 加载菜单树 // 加载菜单树
const loadFpMenuTree = async () => { const loadFpMenuTree = async () => {
try { try {
const res = await getFpMenuTree({...getFpMenuTreeParams}) const res = await getFpMenuTree({ ...getFpMenuTreeParams })
fpMenuTree.value = res.data // 直接使用后端返回的树形结构 fpMenuTree.value = res.data // 直接使用后端返回的树形结构
loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选 loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) { } catch (error) {
...@@ -1334,32 +1529,31 @@ const loadFpMenuTree = async () => { ...@@ -1334,32 +1529,31 @@ const loadFpMenuTree = async () => {
// 修改加载选中菜单列表的逻辑 // 修改加载选中菜单列表的逻辑
const loadSelectedFpMenuList = async () => { const loadSelectedFpMenuList = async () => {
try { try {
const res = await getSelectedFpMenuList({...getSelectedFpMenuListParams}); const res = await getSelectedFpMenuList({ ...getSelectedFpMenuListParams })
const targetKeys = res.data || []; const targetKeys = res.data || []
// 开启严格模式(禁用联动) // 开启严格模式(禁用联动)
fpMenuIsCheckStrictly.value = true; fpMenuIsCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效) // 等待DOM更新(确保严格模式生效)
await nextTick(); await nextTick()
//清空并设置选中状态(此时不会触发联动) //清空并设置选中状态(此时不会触发联动)
if (fpMenuRef.value) { if (fpMenuRef.value) {
fpMenuRef.value.setCheckedKeys([]); fpMenuRef.value.setCheckedKeys([])
fpMenuRef.value.setCheckedKeys(targetKeys); fpMenuRef.value.setCheckedKeys(targetKeys)
} }
//等待选中状态渲染完成 //等待选中状态渲染完成
await nextTick(); await nextTick()
//关闭严格模式(恢复联动) //关闭严格模式(恢复联动)
fpMenuIsCheckStrictly.value = false; fpMenuIsCheckStrictly.value = false
} catch (error) { } catch (error) {
console.error('加载选中的菜单列表失败:', error); console.error('加载选中的菜单列表失败:', error)
proxy.$modal.msgError('加载选中的菜单列表失败'); proxy.$modal.msgError('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态 // 异常时也要恢复严格模式状态
fpMenuIsCheckStrictly.value = false; fpMenuIsCheckStrictly.value = false
} }
} }
// 保存 // 保存
...@@ -1382,7 +1576,6 @@ const saveFpMenuList = async () => { ...@@ -1382,7 +1576,6 @@ const saveFpMenuList = async () => {
getRoleList() getRoleList()
fpMenuOpen.value = false fpMenuOpen.value = false
proxy.$modal.msgSuccess('更新成功') proxy.$modal.msgSuccess('更新成功')
} catch (error) { } catch (error) {
console.error('更新失败:', error) console.error('更新失败:', error)
proxy.$modal.msgError('更新失败') proxy.$modal.msgError('更新失败')
...@@ -1390,23 +1583,21 @@ const saveFpMenuList = async () => { ...@@ -1390,23 +1583,21 @@ const saveFpMenuList = async () => {
} }
//========分配菜单-导入逻辑结束========= //========分配菜单-导入逻辑结束=========
const handleTabChange = tabName => {
const handleTabChange = (tabName) => {
console.log('切换到:', tabName) console.log('切换到:', tabName)
if (tabName === "project") { if (tabName === 'project') {
//项目tab获取项目列表数据 //项目tab获取项目列表数据
getList() getList()
}else if (tabName === "user"){ } else if (tabName === 'user') {
//用户tab获取用户列表数据 //用户tab获取用户列表数据
getUserList() getUserList()
}else if (tabName === "role"){ } else if (tabName === 'role') {
//用户tab获取角色列表数据 //用户tab获取角色列表数据
getRoleList() getRoleList()
}else if (tabName === "menu"){ } else if (tabName === 'menu') {
//用户tab获取菜单树数据 //用户tab获取菜单树数据
getMenuList() getMenuList()
} }
} }
getList() getList()
...@@ -1422,19 +1613,19 @@ getList() ...@@ -1422,19 +1613,19 @@ getList()
/* 自定义选项卡:核心样式 */ /* 自定义选项卡:核心样式 */
.custom-tabs { .custom-tabs {
--active-tab-color: #fff; /* 激活选项卡文字颜色 */ --active-tab-color: #fff; /* 激活选项卡文字颜色 */
--active-tab-bg: #409eff; /* 激活选项卡背景色 */ --active-tab-bg: #409eff; /* 激活选项卡背景色 */
--inactive-tab-color: #606266; /* 未激活文字颜色 */ --inactive-tab-color: #606266; /* 未激活文字颜色 */
--inactive-tab-bg: #f5f7fa; /* 未激活背景色 */ --inactive-tab-bg: #f5f7fa; /* 未激活背景色 */
--border-color: #dcdfe6; /* 边框颜色 */ --border-color: #dcdfe6; /* 边框颜色 */
--radius: 8px; /* 圆角 */ --radius: 8px; /* 圆角 */
width: 100%; width: 100%;
} }
/* 选项卡头部:消除默认样式,重构凹陷效果 */ /* 选项卡头部:消除默认样式,重构凹陷效果 */
::v-deep .el-tabs__header { ::v-deep .el-tabs__header {
line-height: 1; /* 重置行高,避免影响高度计算 */ line-height: 1; /* 重置行高,避免影响高度计算 */
padding: 0; /* 清除默认内边距 */ padding: 0; /* 清除默认内边距 */
} }
/* 选项卡导航容器:核心凹陷逻辑 */ /* 选项卡导航容器:核心凹陷逻辑 */
...@@ -1489,7 +1680,7 @@ getList() ...@@ -1489,7 +1680,7 @@ getList()
/* 激活态:覆盖父容器的底部边框,实现“凹陷” */ /* 激活态:覆盖父容器的底部边框,实现“凹陷” */
::v-deep .el-tabs__item.is-active::before { ::v-deep .el-tabs__item.is-active::before {
content: ""; content: '';
position: absolute; position: absolute;
bottom: -1px; /* 覆盖父容器的 border-bottom */ bottom: -1px; /* 覆盖父容器的 border-bottom */
left: 0; left: 0;
...@@ -1515,8 +1706,6 @@ getList() ...@@ -1515,8 +1706,6 @@ getList()
color: #909399; color: #909399;
} }
.permission-section { .permission-section {
margin-top: 20px; margin-top: 20px;
} }
...@@ -1640,7 +1829,6 @@ getList() ...@@ -1640,7 +1829,6 @@ getList()
background-color: #fff; background-color: #fff;
} }
/* 右侧标题 */ /* 右侧标题 */
.selected-title { .selected-title {
font-size: 14px; font-size: 14px;
......
<template> <template>
<div class="app-container"> <div class="app-container">
<el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true"> <el-form :model="queryParams" ref="queryRef" v-show="showSearch" :inline="true">
<el-form-item label="账号" prop="userName"> <el-form-item label="账号" prop="userName">
<el-input <el-input
v-model="queryParams.userName" v-model="queryParams.userName"
placeholder="请输入账号" placeholder="请输入账号"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="姓名" prop="realName"> <el-form-item label="姓名" prop="realName">
<el-input <el-input
v-model="queryParams.realName" v-model="queryParams.realName"
placeholder="请输入姓名" placeholder="请输入姓名"
clearable clearable
style="width: 240px" style="width: 240px"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input
v-model="queryParams.mobile"
placeholder="请输入手机号"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="是否超级管理员" prop="isSuperAdmin">
<el-select
v-model="queryParams.isSuperAdmin"
placeholder="是否超级管理员"
clearable
style="width: 240px"
>
<el-option
v-for="dict in sys_no_yes"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="用户状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="用户状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in sys_user_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/> />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
<!-- 表格数据 -->
<el-table v-loading="loading" :data="userList">
<el-table-column label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" />
<el-table-column label="昵称" prop="nickName" />
<el-table-column label="邮箱" prop="email" />
<el-table-column label="手机号" prop="mobile" />
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="val => handleStatusChange(scope.row, $event)"
></el-switch>
</template>
</el-table-column>
<el-table-column prop="isSuperAdmin" label="是否超级管理员">
<template #default="scope">
<dict-tag :options="sys_no_yes" :value="scope.row.isSuperAdmin" />
</template>
</el-table-column>
<el-table-column label="最后登录IP" prop="lastLoginIp" />
<el-table-column label="最后登录时间" align="center" prop="lastLoginTime">
<template #default="scope">
<span>{{ parseTime(scope.row.lastLoginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="最后修改密码时间" align="center" prop="lastPasswordTime">
<template #default="scope">
<span>{{ parseTime(scope.row.lastPasswordTime) }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column
label="操作"
align="center"
class-name="small-padding fixed-width"
width="200px"
>
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
>修改</el-button
>
<el-button link type="primary" icon="Edit" @click="handleResetPwd(scope.row)"
>重置密码</el-button
>
<!-- <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)" >详情</el-button>-->
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" >删除</el-button>-->
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改用户对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="userRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="账号" prop="userName">
<el-input v-model="form.userName" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="密码" prop="password" v-if="!form.userBizId">
<el-input v-model="form.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item> </el-form-item>
<el-form-item label="手机号" prop="mobile"> <el-form-item label="手机号" prop="mobile">
<el-input <el-input v-model="form.mobile" placeholder="请输入手机号" />
v-model="queryParams.mobile"
placeholder="请输入手机号"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item> </el-form-item>
<el-form-item label="是否超级管理员" prop="isSuperAdmin"> <el-form-item label="用户属性">
<el-select <el-select v-model="form.attribute" placeholder="请输入">
v-model="queryParams.isSuperAdmin"
placeholder="是否超级管理员"
clearable
style="width: 240px"
>
<el-option <el-option
v-for="dict in sys_no_yes" v-for="item in sys_user_attribute"
:key="dict.value" :key="item.value"
:label="dict.label" :label="item.label"
:value="dict.value" :value="Number(item.value)"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="用户状态" prop="status"> <el-form-item label="性别">
<el-select <el-radio-group v-model="form.gender">
v-model="queryParams.status" <el-radio
placeholder="用户状态" v-for="dict in sys_gender"
clearable :key="Number(dict.value)"
style="width: 240px" :value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_status"
:key="Number(dict.value)"
:value="Number(dict.value)"
> >
<el-option {{ dict.label }}
v-for="dict in sys_user_status" </el-radio>
:key="dict.value" </el-radio-group>
:label="dict.label" </el-form-item>
:value="dict.value" <el-form-item label="头像" prop="avatar">
/> <el-input v-model="form.avatar" placeholder="请输入头像" />
</el-select> </el-form-item>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form> </el-form>
<template #footer>
<!-- 表格数据 --> <div class="dialog-footer">
<el-table v-loading="loading" :data="userList"> <el-button type="primary" @click="submitForm">确定</el-button>
<el-table-column label="账号" prop="userName" /> <el-button @click="cancel">取 消</el-button>
<el-table-column label="姓名" prop="realName" /> </div>
<el-table-column label="昵称" prop="nickName" /> </template>
<el-table-column label="邮箱" prop="email" /> </el-dialog>
<el-table-column label="手机号" prop="mobile" /> </div>
<el-table-column prop="gender" label="性别">
<template #default="scope">
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</el-table-column>
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="(val) => handleStatusChange(scope.row, $event)"
></el-switch>
</template>
</el-table-column>
<el-table-column prop="isSuperAdmin" label="是否超级管理员">
<template #default="scope">
<dict-tag :options="sys_no_yes" :value="scope.row.isSuperAdmin" />
</template>
</el-table-column>
<el-table-column label="最后登录IP" prop="lastLoginIp" />
<el-table-column label="最后登录时间" align="center" prop="lastLoginTime">
<template #default="scope">
<span>{{ parseTime(scope.row.lastLoginTime) }}</span>
</template>
</el-table-column>
<el-table-column label="最后修改密码时间" align="center" prop="lastPasswordTime">
<template #default="scope">
<span>{{ parseTime(scope.row.lastPasswordTime) }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200px">
<template #default="scope">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" >修改</el-button>
<el-button link type="primary" icon="Edit" @click="handleResetPwd(scope.row)" >重置密码</el-button>
<!-- <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)" >详情</el-button>-->
<!-- <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" >删除</el-button>-->
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改用户对话框 -->
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
<el-form ref="userRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="账号" prop="userName">
<el-input v-model="form.userName" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="密码" prop="password" v-show="!form.userBizId">
<el-input v-model="form.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item label="姓名" prop="realName">
<el-input v-model="form.realName" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="form.gender">
<el-radio
v-for="dict in sys_gender"
:key="Number(dict.value)"
:value="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in sys_status"
:key="Number(dict.value)"
:value="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="头像" prop="avatar">
<el-input v-model="form.avatar" placeholder="请输入头像" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template> </template>
<script setup name="User"> <script setup name="User">
import {
import { listUser,getUser,updateUser,addUser,changeUserStatus,resetUserPwd } from "@/api/system/user" listUser,
getUser,
updateUser,
addUser,
changeUserStatus,
resetUserPwd
} from '@/api/system/user'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const { sys_status,sys_scope,sys_no_yes,sys_user_status,sys_gender } = proxy.useDict("sys_status","sys_scope","sys_no_yes","sys_user_status","sys_gender") const { sys_status, sys_scope, sys_no_yes, sys_user_status, sys_gender, sys_user_attribute } =
proxy.useDict(
'sys_status',
'sys_scope',
'sys_no_yes',
'sys_user_status',
'sys_gender',
'sys_user_attribute'
)
const userList = ref([]) const userList = ref([])
const open = ref(false) const open = ref(false)
...@@ -198,7 +232,7 @@ const loading = ref(true) ...@@ -198,7 +232,7 @@ const loading = ref(true)
const showSearch = ref(true) const showSearch = ref(true)
const ids = ref([]) const ids = ref([])
const total = ref(0) const total = ref(0)
const title = ref("") const title = ref('')
const data = reactive({ const data = reactive({
form: {}, form: {},
...@@ -213,16 +247,26 @@ const data = reactive({ ...@@ -213,16 +247,26 @@ const data = reactive({
isSuperAdmin: undefined isSuperAdmin: undefined
}, },
rules: { rules: {
userName: [{ required: true, message: "账号不能为空", trigger: "blur" }], userName: [{ required: true, message: '账号不能为空', trigger: 'blur' }],
realName: [{ required: true, message: "姓名不能为空", trigger: "blur" }], realName: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
nickName: [{ required: true, message: "昵称不能为空", trigger: "blur" }], nickName: [{ required: true, message: '昵称不能为空', trigger: 'blur' }],
email: [{ required: true, message: "邮箱不能为空", trigger: "blur" },{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }], email: [
mobile: [{ required: true, message: "手机号不能为空", trigger: "blur" },{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }], { required: true, message: '邮箱不能为空', trigger: 'blur' },
gender: [{ required: true, message: "性别不能为空", trigger: "blur" }], { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
status: [{ required: true, message: "状态不能为空", trigger: "blur" }], ],
avatar: [{ required: true, message: "头像URL不能为空", trigger: "blur" }], mobile: [
password: [{ required: true, message: "用户密码不能为空", trigger: "blur" }, { min: 5, max: 20, message: "用户密码长度必须介于 5 和 20 之间", trigger: "blur" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur" }] { required: true, message: '手机号不能为空', trigger: 'blur' },
}, { pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
gender: [{ required: true, message: '性别不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
avatar: [{ required: true, message: '头像URL不能为空', trigger: 'blur' }],
password: [
{ required: true, message: '用户密码不能为空', trigger: 'blur' },
{ min: 5, max: 20, message: '用户密码长度必须介于 5 和 20 之间', trigger: 'blur' },
{ pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\\ |', trigger: 'blur' }
]
}
}) })
const { queryParams, form, rules } = toRefs(data) const { queryParams, form, rules } = toRefs(data)
...@@ -244,33 +288,38 @@ function handleQuery() { ...@@ -244,33 +288,38 @@ function handleQuery() {
/** 重置按钮操作 */ /** 重置按钮操作 */
function resetQuery() { function resetQuery() {
proxy.resetForm("queryRef") proxy.resetForm('queryRef')
handleQuery() handleQuery()
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
function handleDelete(row) { function handleDelete(row) {
const roleIds = row.roleId || ids.value const roleIds = row.roleId || ids.value
proxy.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () { proxy.$modal
return delRole(roleIds) .confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?')
}).then(() => { .then(function () {
getList() return delRole(roleIds)
proxy.$modal.msgSuccess("删除成功") })
}).catch(() => {}) .then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
} }
/** 用户状态修改 */ /** 用户状态修改 */
function handleStatusChange(row, event) { function handleStatusChange(row, event) {
debugger debugger
let text = row.status === 0 ? "停用" : "启用" let text = row.status === 0 ? '停用' : '启用'
const userName = row.userName const userName = row.userName
proxy.$modal.confirm(`确认要${text}"${userName}"用户吗?`) proxy.$modal
.then(() => changeUserStatus({userBizId: row.userBizId, status: row.status})) .confirm(`确认要${text}"${userName}"用户吗?`)
.then(() => proxy.$modal.msgSuccess(`${text}成功`)) .then(() => changeUserStatus({ userBizId: row.userBizId, status: row.status }))
.catch(() => { .then(() => proxy.$modal.msgSuccess(`${text}成功`))
// 操作取消时恢复原状态 .catch(() => {
row.status = row.status === 0 ? 1 : 0 // 操作取消时恢复原状态
}) row.status = row.status === 0 ? 1 : 0
})
} }
/** 重置新增的表单以及其他数据 */ /** 重置新增的表单以及其他数据 */
...@@ -287,14 +336,14 @@ function reset() { ...@@ -287,14 +336,14 @@ function reset() {
gender: 0, gender: 0,
status: 1 status: 1
} }
proxy.resetForm("userRef") proxy.resetForm('userRef')
} }
/** 添加用户 */ /** 添加用户 */
function handleAdd() { function handleAdd() {
reset() reset()
open.value = true open.value = true
title.value = "添加用户" title.value = '添加用户'
} }
/** 修改用户弹框 */ /** 修改用户弹框 */
...@@ -304,45 +353,48 @@ function handleUpdate(row) { ...@@ -304,45 +353,48 @@ function handleUpdate(row) {
getUser(userBizId).then(response => { getUser(userBizId).then(response => {
form.value = response.data form.value = response.data
open.value = true open.value = true
title.value = "修改项目" title.value = '修改项目'
}) })
} }
/** 重置密码按钮操作 */ /** 重置密码按钮操作 */
function handleResetPwd(row) { function handleResetPwd(row) {
proxy.$prompt('请输入"' + row.userName + '"的新密码', "提示", { proxy
confirmButtonText: "确定", .$prompt('请输入"' + row.userName + '"的新密码', '提示', {
cancelButtonText: "取消", confirmButtonText: '确定',
closeOnClickModal: false, cancelButtonText: '取消',
inputPattern: /^.{5,20}$/, closeOnClickModal: false,
inputErrorMessage: "用户密码长度必须介于 5 和 20 之间", inputPattern: /^.{5,20}$/,
inputValidator: (value) => { inputErrorMessage: '用户密码长度必须介于 5 和 20 之间',
if (/<|>|"|'|\||\\/.test(value)) { inputValidator: value => {
return "不能包含非法字符:< > \" ' \\\ |" if (/<|>|"|'|\||\\/.test(value)) {
return '不能包含非法字符:< > " \' \\\ |'
}
} }
},
}).then(({ value }) => {
resetUserPwd({userBizId: row.userBizId,password: value}).then(response => {
getList()
proxy.$modal.msgSuccess("修改成功,新密码是:" + value)
}) })
}).catch(() => {}) .then(({ value }) => {
resetUserPwd({ userBizId: row.userBizId, password: value }).then(response => {
getList()
proxy.$modal.msgSuccess('修改成功,新密码是:' + value)
})
})
.catch(() => {})
} }
/** 添加和修改用户提交按钮 */ /** 添加和修改用户提交按钮 */
function submitForm() { function submitForm() {
debugger proxy.$refs['userRef'].validate(valid => {
proxy.$refs["userRef"].validate(valid => { console.log('valid', valid)
if (valid) { if (valid) {
if (form.value.userBizId) { if (form.value.userBizId) {
updateUser(form.value).then(response => { updateUser(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功") proxy.$modal.msgSuccess('修改成功')
open.value = false open.value = false
getList() getList()
}) })
} else { } else {
addUser(form.value).then(response => { addUser(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功") proxy.$modal.msgSuccess('新增成功')
open.value = false open.value = false
getList() getList()
}) })
......
<template> <template>
<div class="app-card" @click="handleClick"> <div class="app-card" @click="handleClick">
<div class="app-icon"> <div class="app-icon">
<img :src="project.logoUrl" alt="应用图标" v-if="project.logoUrl"/> <img :src="project.logoUrl" alt="应用图标" v-if="project.logoUrl" />
<div class="default-icon" v-else>{{ appName.substring(0, 2) }}</div> <div class="default-icon" v-else>{{ appName.substring(0, 2) }}</div>
</div> </div>
<div class="app-name">{{ appName }}</div> <div class="app-name">{{ appName }}</div>
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { getVisitPermission } from '@/api/common'
import { getToken } from '@/utils/auth'
const props = defineProps({ const props = defineProps({
project: { project: {
...@@ -23,28 +25,41 @@ const router = useRouter() ...@@ -23,28 +25,41 @@ const router = useRouter()
const appName = computed(() => props.project.projectName) const appName = computed(() => props.project.projectName)
const handleClick = () => { const handleClick = () => {
// 记录最近使用的应用 console.log('点击了应用卡片', props.project)
const recentApps = JSON.parse(localStorage.getItem('recentApps') || '[]') getVisitPermission(props.project.projectBizId).then(response => {
const newRecent = [ if (response.code === 200) {
props.project, // 有权限访问项目,进行跳转
...recentApps.filter(app => app.projectBizId !== props.project.projectBizId) // 记录最近使用的应用
].slice(0, 5) const recentApps = JSON.parse(localStorage.getItem('recentApps') || '[]')
localStorage.setItem('recentApps', JSON.stringify(newRecent)) const newRecent = [
props.project,
...recentApps.filter(app => app.projectBizId !== props.project.projectBizId)
].slice(0, 5)
localStorage.setItem('recentApps', JSON.stringify(newRecent))
// 根据 isIn 字段决定跳转逻辑 // 根据 isIn 字段决定跳转逻辑
if (props.project.isIn === 0 && props.project.projectUrl) { if (props.project.isIn === 0 && props.project.projectUrl) {
// 外部跳转 let newUrl = `${props.project.projectUrl}?projectBizId=${
window.open(props.project.projectUrl, '_blank') props.project.projectBizId
} else { }&tenantBizId=${props.project.tenantBizId}&token=${getToken()}`
// 内部跳转 // 外部跳转
router.push({ window.open(newUrl, '_blank')
path: '/app/dashboard', } else {
query: { // 内部跳转
projectBizId: props.project.projectBizId, router.push({
projectName: props.project.projectName path: '/app/dashboard',
query: {
projectBizId: props.project.projectBizId,
projectName: props.project.projectName
}
})
} }
}) } else {
} // 没访问权限提示信息
proxy.$modal.msgError(response.msg)
}
})
return
} }
</script> </script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment