Commit 7d82bc09 by yuzhenWang

添加保险产品

parent ea074136
# 页面标题
VITE_APP_TITLE = 若依管理系统
VITE_APP_TITLE = 银盾中台系统
# 开发环境配置
VITE_APP_ENV = 'development'
......
# 页面标题
VITE_APP_TITLE = 若依管理系统
VITE_APP_TITLE = 银盾中台系统
# 生产环境配置
VITE_APP_ENV = 'production'
......
# 页面标题
VITE_APP_TITLE = 若依管理系统
VITE_APP_TITLE = 银盾中台系统
# 生产环境配置
VITE_APP_ENV = 'staging'
......
......@@ -6,8 +6,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico">
<title>若依管理系统</title>
<link rel="icon" href="/companyLogo.png">
<title>银盾中台系统</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>
html,
......
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) {
}
// 查询部门详细
export function getDept(deptId) {
export function getDept(deptBizId) {
return request({
url: '/system/dept/' + deptId,
url: '/user/api/sysDept/detail?deptBizId=' + deptBizId,
method: 'get'
})
}
......@@ -28,7 +28,7 @@ export function getDept(deptId) {
// 新增部门
export function addDept(data) {
return request({
url: '/system/dept',
url: '/user/api/sysDept/add',
method: 'post',
data: data
})
......@@ -46,7 +46,39 @@ export function updateDept(data) {
// 删除部门
export function delDept(deptId) {
return request({
url: '/system/dept/' + deptId,
url: '/user/api/sysDept/del?deptBizId=' + deptId,
method: 'delete'
})
}
// 编辑部门状态
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
})
}
......@@ -36,7 +36,6 @@ export function delRelTenantProject(id) {
})
}
// 查询租户用户关系列表
export function listTenantUser(data) {
return request({
......@@ -73,7 +72,6 @@ export function delRelTenantUser(id) {
})
}
// 查询租户角色关系列表
export function listTenantRole(data) {
return request({
......@@ -146,7 +144,6 @@ export function addImportTenantMenuList(data) {
})
}
//分配角色-左侧待选列表
export function listLeftRole(data) {
return request({
......@@ -180,7 +177,6 @@ export function delRightRoleList(data) {
})
}
//租户和菜单关系树形列表查询
export function getFpMenuTree(data) {
return request({
......@@ -205,5 +201,67 @@ export function addFpMenuList(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 @@
<template v-for="(item, index) in options">
<template v-if="values.includes(item.value)">
<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"
:index="index"
:class="item.elTagClass"
>{{ item.label + " " }}</span>
>{{ item.label + ' ' }}</span
>
<el-tag
v-else
:disable-transitions="true"
......@@ -15,7 +19,8 @@
:index="index"
:type="item.elTagType"
:class="item.elTagClass"
>{{ item.label + " " }}</el-tag>
>{{ item.label + ' ' }}</el-tag
>
</template>
</template>
<template v-if="unmatch && showValue">
......@@ -32,30 +37,39 @@ const props = defineProps({
// 数据
options: {
type: Array,
default: null,
default: null
},
// 当前的值
value: [Number, String, Array],
// 当未找到匹配的数据时,显示value
showValue: {
type: Boolean,
default: true,
default: true
},
separator: {
type: String,
default: ",",
default: ','
}
})
const values = computed(() => {
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(() => {
unmatchArray.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 // 添加一个标志来判断是否有未匹配项
values.value.forEach(item => {
......@@ -68,9 +82,9 @@ const unmatch = computed(() => {
})
function handleArray(array) {
if (array.length === 0) return ""
if (array.length === 0) return ''
return array.reduce((pre, cur) => {
return pre + " " + cur
return pre + ' ' + cur
})
}
</script>
......
......@@ -28,28 +28,20 @@
大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
</template>
<template v-if="fileType">
格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
</template>
的文件
</div>
<el-dialog
v-model="dialogVisible"
title="预览"
width="800px"
append-to-body
>
<img
:src="dialogImageUrl"
style="display: block; max-width: 100%; margin: 0 auto"
/>
<el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
<img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
</el-dialog>
</div>
</template>
<script setup>
import { getToken } from "@/utils/auth"
import { isExternal } from "@/utils/validate"
import { getToken } from '@/utils/auth'
import { isExternal } from '@/utils/validate'
import Sortable from 'sortablejs'
const props = defineProps({
......@@ -57,7 +49,7 @@ const props = defineProps({
// 上传接口地址
action: {
type: String,
default: "/common/upload"
default: '/common/upload'
},
// 上传携带的参数
data: {
......@@ -76,7 +68,7 @@ const props = defineProps({
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => ["png", "jpg", "jpeg"]
default: () => ['png', 'jpg', 'jpeg']
},
// 是否显示提示
isShowTip: {
......@@ -99,23 +91,23 @@ const { proxy } = getCurrentInstance()
const emit = defineEmits()
const number = ref(0)
const uploadList = ref([])
const dialogImageUrl = ref("")
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const baseUrl = import.meta.env.VITE_APP_BASE_API
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 showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
)
const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize))
watch(() => props.modelValue, val => {
watch(
() => props.modelValue,
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 => {
if (typeof item === "string") {
if (typeof item === 'string') {
if (item.indexOf(baseUrl) === -1 && !isExternal(item)) {
item = { name: baseUrl + item, url: baseUrl + item }
} else {
......@@ -128,15 +120,17 @@ watch(() => props.modelValue, val => {
fileList.value = []
return []
}
},{ deep: true, immediate: true })
},
{ deep: true, immediate: true }
)
// 上传前loading加载
function handleBeforeUpload(file) {
let isImg = false
if (props.fileType.length) {
let fileExtension = ""
if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1)
let fileExtension = ''
if (file.name.lastIndexOf('.') > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)
}
isImg = props.fileType.some(type => {
if (file.type.indexOf(type) > -1) return true
......@@ -144,10 +138,10 @@ function handleBeforeUpload(file) {
return false
})
} else {
isImg = file.type.indexOf("image") > -1
isImg = file.type.indexOf('image') > -1
}
if (!isImg) {
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join("/")}图片格式文件!`)
proxy.$modal.msgError(`文件格式不正确,请上传${props.fileType.join('/')}图片格式文件!`)
return false
}
if (file.name.includes(',')) {
......@@ -161,7 +155,7 @@ function handleBeforeUpload(file) {
return false
}
}
proxy.$modal.loading("正在上传图片,请稍候...")
proxy.$modal.loading('正在上传图片,请稍候...')
number.value++
}
......@@ -173,7 +167,9 @@ function handleExceed() {
// 上传成功回调
function handleUploadSuccess(res, file) {
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()
} else {
number.value--
......@@ -189,7 +185,7 @@ function handleDelete(file) {
const findex = fileList.value.map(f => f.name).indexOf(file.name)
if (findex > -1 && uploadList.value.length === number.value) {
fileList.value.splice(findex, 1)
emit("update:modelValue", listToString(fileList.value))
emit('update:modelValue', listToString(fileList.value))
return false
}
}
......@@ -200,14 +196,14 @@ function uploadedSuccessfully() {
fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value)
uploadList.value = []
number.value = 0
emit("update:modelValue", listToString(fileList.value))
emit('update:modelValue', listToString(fileList.value))
proxy.$modal.closeLoading()
}
}
// 上传失败
function handleUploadError() {
proxy.$modal.msgError("上传图片失败")
proxy.$modal.msgError('上传图片失败')
proxy.$modal.closeLoading()
}
......@@ -219,14 +215,14 @@ function handlePictureCardPreview(file) {
// 对象转成指定字符串分隔
function listToString(list, separator) {
let strs = ""
separator = separator || ","
let strs = ''
separator = separator || ','
for (let i in list) {
if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
strs += list[i].url.replace(baseUrl, "") + separator
if (undefined !== list[i].url && list[i].url.indexOf('blob:') !== 0) {
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(() => {
nextTick(() => {
const element = proxy.$refs.imageUpload?.$el?.querySelector('.el-upload-list')
Sortable.create(element, {
onEnd: (evt) => {
onEnd: evt => {
const movedItem = fileList.value.splice(evt.oldIndex, 1)[0]
fileList.value.splice(evt.newIndex, 0, movedItem)
emit('update:modelValue', listToString(fileList.value))
......
<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>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
<hamburger
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" />
<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">
<span class="el-dropdown-link">
{{ currentTenantName }}<el-icon class="el-icon--right"><arrow-down /></el-icon>
......@@ -26,13 +35,13 @@
<template v-if="appStore.device !== 'mobile'">
<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" />
</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" />
</el-tooltip>
</el-tooltip> -->
<screenfull id="screenfull" class="right-menu-item hover-effect" />
......@@ -48,23 +57,31 @@
</el-tooltip>
</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">
<img :src="userStore.avatar" class="user-avatar" />
<span class="user-nickname"> {{ userStore.nickName }} </span>
</div>
<template #dropdown>
<el-dropdown-menu>
<router-link to="/user/profile">
<!-- <router-link to="/user/profile">
<el-dropdown-item>个人中心</el-dropdown-item>
</router-link>
</router-link> -->
<el-dropdown-item divided command="logout">
<span>退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</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" />
</div>
</div>
......@@ -98,9 +115,10 @@ const availableTenants = computed(() => {
// 添加空值检查
if (!userStore.tenants) return []
return userStore.tenants.filter(tenant =>
return userStore.tenants.filter(
tenant =>
// (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(() => {
})
// 切换租户处理
const handleTenantChange = (tenant) => {
const handleTenantChange = tenant => {
// 之前的逻辑
userStore.switchTenant(tenant)
}
......@@ -125,10 +144,10 @@ function toggleSideBar() {
function handleCommand(command) {
switch (command) {
case "setLayout":
case 'setLayout':
setLayout()
break
case "logout":
case 'logout':
logout()
break
default:
......@@ -141,11 +160,13 @@ function logout() {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
})
.then(() => {
userStore.logOut().then(() => {
location.href = '/index'
location.href = '/workbench'
})
})
}).catch(() => { })
.catch(() => {})
}
const emits = defineEmits(['setLayout'])
......@@ -158,7 +179,7 @@ function toggleTheme() {
}
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
.tenant-selector {
margin-left: 20px;
cursor: pointer;
......@@ -260,7 +281,7 @@ function toggleTheme() {
border-radius: 50%;
}
.user-nickname{
.user-nickname {
position: relative;
left: 5px;
bottom: 10px;
......
......@@ -14,7 +14,7 @@
</template>
<script setup>
import logo from '@/assets/logo/logo.png'
import logo from '@/assets/logo/companyLogo.png'
import useSettingsStore from '@/store/modules/settings'
import variables from '@/assets/styles/variables.module.scss'
......
......@@ -5,38 +5,39 @@
v-for="tag in visitedViews"
:key="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 }"
class="tags-view-item"
:style="activeStyle(tag)"
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
@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 }}
<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>
</router-link>
</scroll-pane>
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">
<refresh-right style="width: 1em; height: 1em;" /> 刷新页面
<refresh-right style="width: 1em; height: 1em" /> 刷新页面
</li>
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
<close style="width: 1em; height: 1em;" /> 关闭当前
</li>
<li @click="closeOthersTags">
<circle-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 v-if="!isFirstView()" @click="closeLeftTags">
<back style="width: 1em; height: 1em;" /> 关闭左侧
<back style="width: 1em; height: 1em" /> 关闭左侧
</li>
<li v-if="!isLastView()" @click="closeRightTags">
<right style="width: 1em; height: 1em;" /> 关闭右侧
<right style="width: 1em; height: 1em" /> 关闭右侧
</li>
<li @click="closeAllTags(selectedTag)">
<circle-close style="width: 1em; height: 1em;" /> 全部关闭
<circle-close style="width: 1em; height: 1em" /> 全部关闭
</li>
</ul>
</div>
......@@ -64,13 +65,14 @@ const visitedViews = computed(() => useTagsViewStore().visitedViews)
const routes = computed(() => usePermissionStore().routes)
const theme = computed(() => useSettingsStore().theme)
const tagsIcon = computed(() => useSettingsStore().tagsIcon)
console.log('visitedViews', visitedViews.value)
watch(route, () => {
addTags()
moveToCurrentTag()
})
watch(visible, (value) => {
watch(visible, value => {
if (value) {
document.body.addEventListener('click', closeMenu)
} else {
......@@ -90,8 +92,8 @@ function isActive(r) {
function activeStyle(tag) {
if (!isActive(tag)) return {}
return {
"background-color": theme.value,
"border-color": theme.value
'background-color': theme.value,
'border-color': theme.value
}
}
......@@ -101,7 +103,10 @@ function isAffix(tag) {
function isFirstView() {
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) {
return false
}
......@@ -201,7 +206,7 @@ function closeLeftTags() {
}
function closeOthersTags() {
router.push(selectedTag.value).catch(() => { })
router.push(selectedTag.value).catch(() => {})
proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
moveToCurrentTag()
})
......@@ -265,7 +270,7 @@ function handleScroll() {
width: 100%;
background: var(--tags-bg, #fff);
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-item {
......@@ -324,7 +329,7 @@ function handleScroll() {
font-size: 12px;
font-weight: 400;
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);
li {
......@@ -350,11 +355,11 @@ function handleScroll() {
vertical-align: 2px;
border-radius: 50%;
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%;
&:before {
transform: scale(.6);
transform: scale(0.6);
display: inline-block;
vertical-align: -3px;
}
......
......@@ -48,7 +48,7 @@ export const constantRoutes = [
hidden: true
},
{
path: "/:pathMatch(.*)*",
path: '/:pathMatch(.*)*',
component: () => import('@/views/error/404'),
hidden: true
},
......@@ -70,7 +70,7 @@ export const constantRoutes = [
meta: { title: '工作台', icon: 'dashboard', affix: true }
}
]
},
}
// {
// path: '',
// component: Layout,
......@@ -198,7 +198,7 @@ const router = createRouter({
return savedPosition
}
return { top: 0 }
},
}
})
// 重置路由函数
......
......@@ -9,9 +9,7 @@ import useUserStore from '@/store/modules/user'
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue')
const usePermissionStore = defineStore(
'permission',
{
const usePermissionStore = defineStore('permission', {
state: () => ({
routes: [],
addRoutes: [],
......@@ -36,8 +34,9 @@ const usePermissionStore = defineStore(
generateRoutes() {
return new Promise(resolve => {
const userStore = useUserStore()
const tenantBizId = userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId || "tenant_1001"
const tenantBizId =
userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId || 'tenant_1001'
// debugger
getRouters(tenantBizId).then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
......@@ -57,7 +56,7 @@ const usePermissionStore = defineStore(
})
}
}
})
})
// export default usePermissionStore
......@@ -66,28 +65,30 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {
// 确保路由有有效路径
if (!route.path || route.path.trim() === '') {
console.warn('忽略无效路由项(缺少路径):', route);
return false;
console.warn('忽略无效路由项(缺少路径):', route)
return false
}
// 只处理目录(1)和菜单(2)类型
if (route.menuType !== 1 && route.menuType !== 2) {
return false;
return false
}
// 创建必要的元信息
if (!route.meta) {
route.meta = {};
route.meta = {}
}
// 设置菜单标题
route.meta.title = route.menuName;
route.meta.icon = route.icon;
route.meta.isCache = route.isCache === 1;
route.meta.title = route.menuName
route.meta.icon = route.icon
route.meta.isCache = route.isCache === 1
route.name = route.routeName
//确保单子菜单的父菜单也显示
if (route.menuType === 1) {
route.alwaysShow = true; // 强制显示父菜单
route.alwaysShow = true // 强制显示父菜单
}
// 新增:添加标签页显示属性(关键修改)
......@@ -96,36 +97,36 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
// 确保顶级菜单有布局组件
if (route.parentBizId === '0' && !route.component) {
route.component = 'Layout';
route.component = 'Layout'
}
// 组件映射处理
if (route.component) {
if (route.component === 'Layout') {
route.component = Layout;
route.component = Layout
} else if (route.component === 'ParentView') {
route.component = ParentView;
route.component = ParentView
} else if (route.component === 'InnerLink') {
route.component = InnerLink;
route.component = InnerLink
} else {
route.component = loadView(route.component);
route.component = loadView(route.component)
}
}
// 递归处理子路由
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) {
route.redirect = route.children[0].path;
route.redirect = route.children[0].path
}
} else {
delete route.children;
delete route.children
}
return true;
});
return true
})
}
// // 遍历后台传来的路由字符串,转换为组件对象
// function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
......@@ -185,7 +186,7 @@ export function filterDynamicRoutes(routes) {
return res
}
export const loadView = (view) => {
export const loadView = view => {
let res
for (const path in modules) {
const dir = path.split('views/')[1].split('.vue')[0]
......
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 errorCode from '@/utils/errorCode'
import { tansParams, blobValidate } from '@/utils/ruoyi'
......@@ -21,8 +21,8 @@ const service = axios.create({
})
// request拦截器
service.interceptors.request.use(config => {
service.interceptors.request.use(
config => {
// 添加租户ID到请求头
const userStore = useUserStore()
if (userStore.currentTenant) {
......@@ -60,27 +60,34 @@ service.interceptors.request.use(config => {
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
cache.session.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url // 请求地址
const s_data = sessionObj.data // 请求数据
const s_time = sessionObj.time // 请求时间
const interval = 1000 // 间隔时间(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)
}
// 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
}, error => {
},
error => {
console.log(error)
Promise.reject(error)
})
}
)
// 响应拦截器
service.interceptors.response.use(res => {
service.interceptors.response.use(
res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200
// 获取错误信息
......@@ -92,12 +99,20 @@ service.interceptors.response.use(res => {
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
isRelogin.show = false
useUserStore().logOut().then(() => {
useUserStore()
.logOut()
.then(() => {
location.href = '/index'
})
}).catch(() => {
})
.catch(() => {
isRelogin.show = false
})
}
......@@ -118,12 +133,12 @@ service.interceptors.response.use(res => {
error => {
console.log('err' + error)
let { message } = error
if (message == "Network Error") {
message = "后端接口连接异常"
} else if (message.includes("timeout")) {
message = "系统接口请求超时"
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常"
if (message == 'Network Error') {
message = '后端接口连接异常'
} else if (message.includes('timeout')) {
message = '系统接口请求超时'
} else if (message.includes('Request failed with status code')) {
message = '系统接口' + message.substr(message.length - 3) + '异常'
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
......@@ -132,13 +147,22 @@ service.interceptors.response.use(res => {
// 通用下载方法
export function download(url, params, filename, config) {
downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })
return service.post(url, params, {
transformRequest: [(params) => { return tansParams(params) }],
downloadLoadingInstance = ElLoading.service({
text: '正在下载数据,请稍候',
background: 'rgba(0, 0, 0, 0.7)'
})
return service
.post(url, params, {
transformRequest: [
params => {
return tansParams(params)
}
],
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
responseType: 'blob',
...config
}).then(async (data) => {
})
.then(async data => {
const isBlob = blobValidate(data)
if (isBlob) {
const blob = new Blob([data])
......@@ -150,7 +174,8 @@ export function download(url, params, filename, config) {
ElMessage.error(errMsg)
}
downloadLoadingInstance.close()
}).catch((r) => {
})
.catch(r => {
console.error(r)
ElMessage.error('下载文件出现错误,请联系管理员!')
downloadLoadingInstance.close()
......
<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 @@
@click="goTarget('https://gitee.com/y_project/RuoYi-Vue')"
>访问码云</el-button
>
<el-button
icon="HomeFilled"
plain
@click="goTarget('http://ruoyi.vip')"
<el-button icon="HomeFilled" plain @click="goTarget('http://ruoyi.vip')"
>访问主页</el-button
>
</p>
......@@ -87,18 +84,16 @@
<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> 满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>
</p>
<p>
<i class="el-icon-chat-dot-round"></i> 微信:<a
href="javascript:;"
>/ *若依</a
<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
>
</p>
<p><i class="el-icon-chat-dot-round"></i> 微信:<a href="javascript:;">/ *若依</a></p>
<p>
<i class="el-icon-money"></i> 支付宝:<a
href="javascript:;"
class="支付宝信息"
<i class="el-icon-money"></i> 支付宝:<a href="javascript:;" class="支付宝信息"
>/ *若依</a
>
</p>
......@@ -878,9 +873,7 @@
<li>调整表头固定列默认样式</li>
<li>代码生成模板调整,字段为String并且必填则加空串条件</li>
<li>代码生成字典Integer/Long使用parseInt</li>
<li>
修复dict_sort不可update为0的问题&查询返回增加dict_sort升序排序
</li>
<li>修复dict_sort不可update为0的问题&查询返回增加dict_sort升序排序</li>
<li>修正岗位导出权限注解</li>
<li>禁止加密密文返回前端</li>
<li>修复代码生成页面中的查询条件创建时间未生效的问题</li>
......@@ -1042,11 +1035,7 @@
</div>
</template>
<div class="body">
<img
src="@/assets/images/pay.png"
alt="donate"
style="width:100%"
/>
<img src="@/assets/images/pay.png" alt="donate" style="width: 100%" />
<span style="display: inline-block; height: 30px; line-height: 30px"
>你可以请作者喝杯咖啡表示鼓励</span
>
......@@ -1088,7 +1077,7 @@ function goTarget(url) {
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;
color: #676a6c;
overflow-x: hidden;
......@@ -1128,4 +1117,3 @@ function goTarget(url) {
}
}
</style>
......@@ -10,7 +10,9 @@
auto-complete="off"
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-form-item>
<el-form-item prop="password">
......@@ -22,10 +24,12 @@
placeholder="密码"
@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-form-item>
<el-form-item prop="code" v-if="captchaEnabled">
<!-- <el-form-item prop="code" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
size="large"
......@@ -39,20 +43,22 @@
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
<el-form-item style="width:100%;">
</el-form-item> -->
<el-checkbox v-model="loginForm.rememberMe" style="margin: 0px 0px 25px 0px"
>记住密码</el-checkbox
>
<el-form-item style="width: 100%">
<el-button
:loading="loading"
size="large"
type="primary"
style="width:100%;"
style="width: 100%"
@click.prevent="handleLogin"
>
<span v-if="!loading">登 录</span>
<span v-else>登 录 中...</span>
</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>
</div>
</el-form-item>
......@@ -65,9 +71,9 @@
</template>
<script setup>
import { getCodeImg } from "@/api/login"
import Cookies from "js-cookie"
import { encrypt, decrypt } from "@/utils/jsencrypt"
import { getCodeImg } from '@/api/login'
import Cookies from 'js-cookie'
import { encrypt, decrypt } from '@/utils/jsencrypt'
import useUserStore from '@/store/modules/user'
const title = import.meta.env.VITE_APP_TITLE
......@@ -77,20 +83,20 @@ const router = useRouter()
const { proxy } = getCurrentInstance()
const loginForm = ref({
username: "zs",
password: "123",
username: 'zs',
password: '123',
rememberMe: false,
code: "",
uuid: ""
code: '',
uuid: ''
})
const loginRules = {
username: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }]
// code: [{ required: true, trigger: "change", message: "请输入验证码" }]
}
const codeUrl = ref("")
const codeUrl = ref('')
const loading = ref(false)
// 验证码开关
const captchaEnabled = ref(true)
......@@ -98,9 +104,13 @@ const captchaEnabled = ref(true)
const register = ref(false)
const redirect = ref(undefined)
watch(route, (newRoute) => {
watch(
route,
newRoute => {
redirect.value = newRoute.query && newRoute.query.redirect
}, { immediate: true })
},
{ immediate: true }
)
function handleLogin() {
proxy.$refs.loginRef.validate(valid => {
......@@ -108,50 +118,53 @@ function handleLogin() {
loading.value = true
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 })
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
Cookies.set('username', loginForm.value.username, { expires: 30 })
Cookies.set('password', encrypt(loginForm.value.password), { expires: 30 })
Cookies.set('rememberMe', loginForm.value.rememberMe, { expires: 30 })
} else {
// 否则移除
Cookies.remove("username")
Cookies.remove("password")
Cookies.remove("rememberMe")
Cookies.remove('username')
Cookies.remove('password')
Cookies.remove('rememberMe')
}
// 调用action的登录方法
userStore.login(loginForm.value).then(() => {
userStore
.login(loginForm.value)
.then(() => {
const query = route.query
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") {
if (cur !== 'redirect') {
acc[cur] = query[cur]
}
return acc
}, {})
router.push({ path: redirect.value || "/", query: otherQueryParams })
}).catch(() => {
router.push({ path: redirect.value || '/', query: otherQueryParams })
})
.catch(() => {
loading.value = false
// 重新获取验证码
if (captchaEnabled.value) {
getCode()
}
// if (captchaEnabled.value) {
// getCode()
// }
})
}
})
}
function getCode() {
const getCode = () => {
getCodeImg().then(res => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
if (captchaEnabled.value) {
codeUrl.value = "data:image/gif;base64," + res.img
codeUrl.value = 'data:image/gif;base64,' + res.img
loginForm.value.uuid = res.uuid
}
})
}
function getCookie() {
const username = Cookies.get("username")
const password = Cookies.get("password")
const rememberMe = Cookies.get("rememberMe")
const username = Cookies.get('username')
const password = Cookies.get('password')
const rememberMe = Cookies.get('rememberMe')
loginForm.value = {
username: username === undefined ? loginForm.value.username : username,
password: password === undefined ? loginForm.value.password : decrypt(password),
......@@ -159,17 +172,17 @@ function getCookie() {
}
}
getCode()
// getCode()
getCookie()
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url("../assets/images/login-background.jpg");
background-image: url('../assets/images/login-background.jpg');
background-size: cover;
}
.title {
......
......@@ -10,39 +10,16 @@
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable style="width: 200px">
<el-option
v-for="dict in sys_normal_disable"
: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-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['system:dept:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
<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>
......@@ -50,63 +27,162 @@
<el-table
v-if="refreshTable"
v-loading="loading"
:data="deptList"
row-key="deptId"
:data="menuList"
row-key="deptBizId"
:default-expand-all="isExpandAll"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
>
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
<el-table-column prop="orderNum" label="排序" width="200"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<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">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
<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="center" prop="createTime" width="200">
<el-table-column prop="attribute" label="组织属性">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
<dict-tag :options="sys_dept_attribute" :value="scope.row.attribute" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<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">
<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-button v-if="scope.row.parentId != 0" link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:dept:remove']">删除</el-button>
<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="600px" append-to-body>
<el-form ref="deptRef" :model="form" :rules="rules" label-width="80px">
<!-- 添加或修改菜单对话框 -->
<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" v-if="form.parentId !== 0">
<el-form-item label="上级部门" prop="parentId">
<el-col :span="24">
<el-form-item label="上级部门" prop="parentBizId">
<el-tree-select
v-model="form.parentId"
:data="deptOptions"
:props="{ value: 'deptId', label: 'deptName', children: 'children' }"
value-key="deptId"
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="deptName">
<el-input v-model="form.deptName" placeholder="请输入部门名称" />
<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="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
<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="12">
<el-form-item label="负责人" prop="leader">
<el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" />
<el-form-item :label="`${nameDept}名称`" prop="deptName">
<el-input v-model="form.deptName" :placeholder="`请输入${nameDept}名称`" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="组织属性" prop="attribute">
<el-select v-model="form.attribute" placeholder="请输入">
<el-option
v-for="item in sys_dept_attribute"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="scope" label="作用域">
<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">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
......@@ -118,21 +194,51 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门状态">
<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 v-if="form.scope == '2'" 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="form.scope == '3'" 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-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"></el-button>
<el-button type="primary" @click="submitForm"></el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
......@@ -140,47 +246,117 @@
</div>
</template>
<script setup name="Dept">
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
<script setup name="Menu">
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 { 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 loading = ref(true)
const showSearch = ref(true)
const title = ref("")
const deptOptions = ref([])
const isExpandAll = ref(true)
const title = ref('')
const menuOptions = ref([])
const isExpandAll = ref(false)
const refreshTable = ref(true)
const iconSelectRef = ref(null)
const nameDept = ref('部门')
const data = reactive({
form: {},
queryParams: {
deptName: undefined,
status: undefined
pageNo: 1,
pageSize: 999999,
loginTenantBizId: userStore.currentTenant.apiLoginTenantInfoResponse.tenantBizId,
menuName: undefined
},
rules: {
parentId: [{ required: true, message: "上级部门不能为空", trigger: "blur" }],
deptName: [{ required: true, message: "部门名称不能为空", trigger: "blur" }],
orderNum: [{ required: true, message: "显示排序不能为空", trigger: "blur" }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
phone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
},
deptName: [{ required: true, message: `名称不能为空`, trigger: 'blur' }],
orderNum: [{ required: true, message: '顺序不能为空', trigger: 'blur' }],
type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
scope: [{ required: true, 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 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() {
loading.value = true
listDept(queryParams.value).then(response => {
deptList.value = proxy.handleTree(response.data, "deptId")
deptList(queryParams.value).then(response => {
menuList.value = proxy.handleTree(response.data.records, 'deptBizId')
console.log('menuList.value', menuList.value)
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() {
open.value = false
......@@ -189,17 +365,18 @@ function cancel() {
/** 表单重置 */
function reset() {
form.value = {
deptId: undefined,
parentId: undefined,
deptName: undefined,
orderNum: 0,
leader: undefined,
phone: undefined,
email: undefined,
status: "0"
}
proxy.resetForm("deptRef")
form.value = {}
proxy.resetForm('deptRef')
}
/** 展示下拉图标 */
function showSelectIcon() {
iconSelectRef.value.reset()
}
/** 选择图标 */
function selected(name) {
form.value.icon = name
}
/** 搜索按钮操作 */
......@@ -209,21 +386,16 @@ function handleQuery() {
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
proxy.resetForm('queryRef')
handleQuery()
}
/** 新增按钮操作 */
function handleAdd(row) {
function handleAdd() {
reset()
listDept().then(response => {
deptOptions.value = proxy.handleTree(response.data, "deptId")
})
if (row != undefined) {
form.value.parentId = row.deptId
}
getTreeselect()
open.value = true
title.value = "添加部门"
title.value = '添加'
}
/** 展开/折叠操作 */
......@@ -236,31 +408,43 @@ function toggleExpandAll() {
}
/** 修改按钮操作 */
function handleUpdate(row) {
async function handleUpdate(row) {
reset()
listDeptExcludeChild(row.deptId).then(response => {
deptOptions.value = proxy.handleTree(response.data, "deptId")
})
getDept(row.deptId).then(response => {
await getTreeselect()
getDept(row.deptBizId).then(response => {
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
title.value = "修改部门"
title.value = `编辑`
})
}
/** 提交按钮 */
function submitForm() {
proxy.$refs["deptRef"].validate(valid => {
proxy.$refs['deptRef'].validate(valid => {
if (valid) {
if (form.value.deptId != undefined) {
updateDept(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
if (form.value.deptBizId) {
editDept(form.value).then(response => {
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
})
} else {
addDept(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
})
......@@ -271,13 +455,86 @@ function submitForm() {
/** 删除按钮操作 */
function handleDelete(row) {
proxy.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() {
return delDept(row.deptId)
}).then(() => {
proxy.$modal
.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?')
.then(function () {
return delDept(row.deptBizId)
})
.then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
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>
<template>
<div class="app-container">
<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="projectName">
<el-input
v-model="queryParams.projectName"
......@@ -38,7 +44,7 @@
<template #default="scope">
<el-image
v-if="scope.row.logoUrl"
style="width: 40px; height: 40px; border-radius: 4px"
class="logo-image"
:src="scope.row.logoUrl"
:preview-src-list="[scope.row.logoUrl]"
fit="cover"
......@@ -50,7 +56,7 @@
</div>
</template>
</el-image>
<span v-else>无LOGO</span>
<span v-else></span>
</template>
</el-table-column>
<el-table-column label="项目名称" prop="projectName" />
......@@ -68,14 +74,20 @@
<el-table-column label="状态" align="center">
<template #default="scope">
<el-switch
v-if="(userStore.isSuperAdmin === 0 && scope.row.scope !== 1) || userStore.isSuperAdmin === 1"
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)"
@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"/>
<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">
......@@ -88,15 +100,46 @@
<span>{{ parseTime(scope.row.endTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200px">
<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="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>
<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>
......@@ -138,7 +181,8 @@
<el-radio
v-for="dict in sys_no_yes"
:key="Number(dict.value)"
:value="Number(dict.value)">
:value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
......@@ -160,11 +204,7 @@
</el-form-item>
<!-- 所属租户 - 可搜索下拉框 -->
<el-form-item
v-if="[2].includes(form.scope)"
label="所属租户"
prop="tenantBizId"
>
<el-form-item v-if="[2].includes(form.scope)" label="所属租户" prop="tenantBizId">
<el-select
v-model="form.tenantBizId"
filterable
......@@ -184,11 +224,7 @@
</el-form-item>
<!-- 所属项目 - 可搜索下拉框 -->
<el-form-item
v-if="[3].includes(form.scope)"
label="所属项目"
prop="projectBizId"
>
<el-form-item v-if="[3].includes(form.scope)" label="所属项目" prop="projectBizId">
<el-select
v-model="form.projectBizId"
filterable
......@@ -207,20 +243,37 @@
</el-select>
</el-form-item>
<el-form-item label="项目描述">
<el-input v-model="form.description" type="textarea" placeholder="请输入项目描述"></el-input>
<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)">
: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
label="项目图标"
prop="logoUrl"
:rules="[{ required: true, message: '请上传项目图标', trigger: 'change' }]"
>
<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"
@change="handleImageChange"
/>
</el-form-item>
</el-form>
<template #footer>
......@@ -251,9 +304,17 @@
</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-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"
......@@ -278,18 +339,31 @@
</template>
<script setup name="Project">
import { computed } from 'vue'
import { addProject, changeRoleStatus, dataScope, delRole, getProject, listProject, updateProject, deptTreeSelect,searchScopeList,changeProjectStatus } from "@/api/system/project"
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu"
import {
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 {formatIsoToDateTime} from '@/utils/date'
import { formatIsoToDateTime } from '@/utils/date'
import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组件
const userStore = useUserStore()
const router = useRouter()
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 open = ref(false)
......@@ -299,7 +373,7 @@ const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const title = ref('')
const dateRange = ref([])
const menuOptions = ref([])
const menuExpand = ref(false)
......@@ -310,14 +384,15 @@ const deptOptions = ref([])
const openDataScope = ref(false)
const menuRef = ref(null)
const deptRef = ref(null)
//在data中添加一个用于跟踪图片上传状态的变量
const imageUploaded = ref(false)
/** 数据范围选项*/
const dataScopeOptions = ref([
{ value: "1", label: "全部数据权限" },
{ value: "2", label: "自定数据权限" },
{ value: "3", label: "本部门数据权限" },
{ value: "4", label: "本部门及以下数据权限" },
{ value: "5", label: "仅本人数据权限" }
{ value: '1', label: '全部数据权限' },
{ value: '2', label: '自定数据权限' },
{ value: '3', label: '本部门数据权限' },
{ value: '4', label: '本部门及以下数据权限' },
{ value: '5', label: '仅本人数据权限' }
])
const data = reactive({
......@@ -332,13 +407,26 @@ const data = reactive({
status: undefined
},
rules: {
projectName: [{ required: true, message: "项目名称不能为空", trigger: "blur" }],
status: [{ required: true, message: "状态不能为空", trigger: "blur" }],
startTime: [{ required: true, message: "项目开始时间不能为空", trigger: "blur" }],
endTime: [{ required: true, message: "项目结束时间不能为空", trigger: "blur" }],
scope: [{ required: true, message: "作用域不能为空", trigger: "blur" }],
logoUrl: [{ required: true, message: "项目图标地址不能为空", trigger: "blur" }],
isIn: [{ required: true, message: "是否内置项目不能为空", trigger: "blur" }],
projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
startTime: [{ required: true, message: '项目开始时间不能为空', trigger: 'blur' }],
endTime: [{ required: true, message: '项目结束时间不能为空', trigger: 'blur' }],
scope: [{ required: true, message: '作用域不能为空', trigger: 'blur' }],
logoUrl: [{ 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() {
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
proxy.resetForm('queryRef')
handleQuery()
}
/** 删除按钮操作 */
function handleDelete(row) {
const roleIds = row.roleId || ids.value
proxy.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?')
.then(function () {
return delRole(roleIds)
}).then(() => {
})
.then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("system/role/export", {
...queryParams.value,
}, `role_${new Date().getTime()}.xlsx`)
proxy.download(
'system/role/export',
{
...queryParams.value
},
`role_${new Date().getTime()}.xlsx`
)
}
/** 多选框选中数据 */
......@@ -403,10 +499,11 @@ function handleSelectionChange(selection) {
/** 项目状态修改 */
function handleStatusChange(row, event) {
let text = row.status === 0 ? "停用" : "启用"
let text = row.status === 0 ? '停用' : '启用'
const projectName = row.projectName
proxy.$modal.confirm(`确认要${text}"${projectName}"项目吗?`)
.then(() => changeProjectStatus({projectBizId: row.projectBizId, status: row.status}))
proxy.$modal
.confirm(`确认要${text}"${projectName}"项目吗?`)
.then(() => changeProjectStatus({ projectBizId: row.projectBizId, status: row.status }))
.then(() => proxy.$modal.msgSuccess(`${text}成功`))
.catch(() => {
// 操作取消时恢复原状态
......@@ -417,10 +514,10 @@ function handleStatusChange(row, event) {
/** 更多操作 */
function handleCommand(command, row) {
switch (command) {
case "handleDataScope":
case 'handleDataScope':
handleDataScope(row)
break
case "handleAuthUser":
case 'handleAuthUser':
handleAuthUser(row)
break
default:
......@@ -458,14 +555,14 @@ function reset() {
isIn: 0,
projectUrl: undefined
}
proxy.resetForm("projectRef")
proxy.resetForm('projectRef')
}
/** 添加项目 */
function handleAdd() {
reset()
open.value = true
title.value = "添加项目"
title.value = '添加项目'
}
/** 修改项目 */
......@@ -473,30 +570,22 @@ function handleUpdate(row) {
reset()
const projectBizId = row.projectBizId
getProject(projectBizId).then(response => {
const resData = response.data;
const resData = response.data
form.value = {
...resData,
startTime: formatIsoToDateTime(resData.startTime),
endTime: formatIsoToDateTime(resData.endTime)
};
}
// 根据作用域类型预加载关联数据
if (resData.scope === 2) {
tenantOptions.value.push({
tenantBizId: resData.tenantBizId,
tenantName: resData.tenantName
});
})
}
open.value = true
})
title.value = "修改项目"
}
/** 根据角色ID查询菜单树结构 */
function getProjectMenuTreeselect(roleId) {
return roleMenuTreeselect(roleId).then(response => {
menuOptions.value = response.menus
return response
})
title.value = '修改项目'
}
/** 根据角色ID查询部门树结构 */
......@@ -509,12 +598,12 @@ function getDeptTree(roleId) {
/** 树权限(展开/折叠)*/
function handleCheckedTreeExpand(value, type) {
if (type == "menu") {
if (type == 'menu') {
let treeList = menuOptions.value
for (let i = 0; i < treeList.length; i++) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value
}
} else if (type == "dept") {
} else if (type == 'dept') {
let treeList = deptOptions.value
for (let i = 0; i < treeList.length; i++) {
deptRef.value.store.nodesMap[treeList[i].id].expanded = value
......@@ -524,18 +613,18 @@ function handleCheckedTreeExpand(value, type) {
/** 树权限(全选/全不选) */
function handleCheckedTreeNodeAll(value, type) {
if (type == "menu") {
if (type == 'menu') {
menuRef.value.setCheckedNodes(value ? menuOptions.value : [])
} else if (type == "dept") {
} else if (type == 'dept') {
deptRef.value.setCheckedNodes(value ? deptOptions.value : [])
}
}
/** 树权限(父子联动) */
function handleCheckedTreeConnect(value, type) {
if (type == "menu") {
if (type == 'menu') {
form.value.menuCheckStrictly = value ? true : false
} else if (type == "dept") {
} else if (type == 'dept') {
form.value.deptCheckStrictly = value ? true : false
}
}
......@@ -552,18 +641,20 @@ function getMenuAllCheckedKeys() {
/** 添加和修改项目提交按钮 */
function submitForm() {
proxy.$refs["projectRef"].validate(valid => {
proxy.$refs['projectRef'].validate(valid => {
if (valid) {
if (form.value.projectBizId) {
updateProject(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
proxy.$modal.msgSuccess('修改成功')
open.value = false
reset()
getList()
})
} else {
addProject(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
proxy.$modal.msgSuccess('新增成功')
open.value = false
reset()
getList()
})
}
......@@ -579,7 +670,7 @@ function cancel() {
/** 选择角色权限范围触发 */
function dataScopeSelectChange(value) {
if (value !== "2") {
if (value !== '2') {
deptRef.value.setCheckedKeys([])
}
}
......@@ -601,7 +692,7 @@ function handleDataScope(row) {
})
})
})
title.value = "分配数据权限"
title.value = '分配数据权限'
}
/** 提交按钮(数据权限) */
......@@ -609,7 +700,7 @@ function submitDataScope() {
if (form.value.roleId != undefined) {
form.value.deptIds = getDeptAllCheckedKeys()
dataScope(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
proxy.$modal.msgSuccess('修改成功')
openDataScope.value = false
getList()
})
......@@ -622,7 +713,6 @@ function cancelDataScope() {
reset()
}
//========作用域-切换逻辑开始=========
const tenantOptions = ref([])
const tenantLoading = ref(false)
......@@ -630,61 +720,164 @@ const projectOptions = ref([])
const projectLoading = ref(false)
// 作用域变更处理
function handleScopeChange(val) {
console.log('作用域变更:', val);
console.log('作用域变更:', val)
// 清空不需要的字段值
if (val === 2) {
form.value.projectBizId = '';
form.value.projectBizId = ''
// 自动加载租户列表(可选)
searchTenants('');
searchTenants('')
} else if (val === 3) {
form.value.tenantBizId = '';
form.value.tenantBizId = ''
// 自动加载项目列表(可选)
searchProjects('');
searchProjects('')
} else if (val === 1) {
//系统级
form.value.tenantBizId = '';
form.value.projectBizId = '';
form.value.tenantBizId = ''
form.value.projectBizId = ''
}
}
// 搜索租户方法
function searchTenants(query) {
tenantLoading.value = true;
tenantLoading.value = true
try {
const params = {
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 => {
tenantOptions.value = response.data.records
})
} catch (error) {
console.error('租户搜索失败', error);
tenantOptions.value = [];
console.error('租户搜索失败', error)
tenantOptions.value = []
} finally {
tenantLoading.value = false;
tenantLoading.value = false
}
}
// 搜索项目方法
function searchProjects(query) {
projectLoading.value = true;
projectLoading.value = true
try {
const params = {
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 => {
projectOptions.value = response.data.records
})
} catch (error) {
console.error('项目搜索失败', error);
projectOptions.value = [];
console.error('项目搜索失败', error)
projectOptions.value = []
} finally {
projectLoading.value = false;
projectLoading.value = false
}
}
//========作用域-切换逻辑结束=========
// 添加图片变更处理函数
function handleImageChange(value) {
imageUploaded.value = !!value
// 手动触发表单验证
if (proxy.$refs.form) {
proxy.$refs.form.validateField('picture')
}
}
getList()
</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 class="app-container">
<!-- 选项卡组件:增加 custom-tabs 类名 -->
<el-tabs
v-model="activeTab"
type="card"
@tab-change="handleTabChange"
class="custom-tabs"
>
<el-tabs v-model="activeTab" type="card" @tab-change="handleTabChange" class="custom-tabs">
<!-- 用户权限选项卡 -->
<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-input
v-model="userQueryParams.userName"
......@@ -40,7 +41,9 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="userHandleQuery">搜索</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>
<!-- 表格数据 -->
......@@ -55,10 +58,19 @@
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</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">
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)" >分配角色</el-button>
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)" >删除</el-button>
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)"
>分配角色</el-button
>
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
......@@ -72,8 +84,19 @@
/>
<!-- 用户导入(根据权限从用户池导入进来) -->
<el-dialog :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-dialog
: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-input
v-model="importProjectUserListQueryParams.userName"
......@@ -102,12 +125,18 @@
/>
</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-form-item>
</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 label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" />
......@@ -146,7 +175,13 @@
<div class="assign-role-container">
<!-- 左侧:可选角色列表(带复选框) -->
<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-input
v-model="leftQuery.roleName"
......@@ -156,7 +191,9 @@
/>
</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-form-item>
</el-form>
......@@ -219,7 +256,13 @@
<div class="right-panel">
<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-input
v-model="rightQuery.roleName"
......@@ -229,7 +272,9 @@
/>
</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-form-item>
</el-form>
......@@ -275,7 +320,13 @@
</el-tab-pane>
<!-- 角色权限选项卡 -->
<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-input
v-model="roleQueryParams.roleName"
......@@ -288,7 +339,9 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</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>
......@@ -305,10 +358,19 @@
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</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">
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)" >分配菜单</el-button>
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)" >删除</el-button>
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)"
>分配菜单</el-button
>
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
......@@ -322,8 +384,19 @@
/>
<!-- 角色导入(根据权限从角色池导入进来) -->
<el-dialog :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-dialog
: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-input
v-model="importProjectRoleListQueryParams.roleName"
......@@ -334,12 +407,18 @@
/>
</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-form-item>
</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 label="角色名称" prop="roleName" />
<el-table-column prop="scope" label="作用域">
......@@ -400,7 +479,13 @@
</el-tab-pane>
<!-- 菜单权限选项卡 -->
<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-input
v-model="menuQueryParams.menuName"
......@@ -413,20 +498,20 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="menuHandleQuery">搜索</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>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
<el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
</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-table
......@@ -437,7 +522,11 @@
:default-expand-all="isExpandAll"
: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">
<template #default="scope">
<svg-icon :icon-class="scope.row.icon" />
......@@ -455,14 +544,25 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<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>
</el-table-column>
</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">
<el-card header="导入菜单" class="mt-4">
......@@ -496,18 +596,48 @@
</template>
<script setup name="ProjectPermission">
import {listProjectUser,listImportProjectUser,
addImportProjectUserList,delRelProjectUser,listProjectRole,
delRelProjectRole,addImportProjectRoleList,listImportProjectRole,
listMenu,getMenuTree,getImportSelectedMenuList,addImportProjectMenuList,
listLeftRole, listRightRole, addRightRoleList,delRightRoleList,
getFpMenuTree,getSelectedFpMenuList,addFpMenuList} from "@/api/system/projectPermission"
import {
listProjectUser,
listImportProjectUser,
addImportProjectUserList,
delRelProjectUser,
listProjectRole,
delRelProjectRole,
addImportProjectRoleList,
listImportProjectRole,
listMenu,
getMenuTree,
getImportSelectedMenuList,
addImportProjectMenuList,
listLeftRole,
listRightRole,
addRightRoleList,
delRightRoleList,
getFpMenuTree,
getSelectedFpMenuList,
addFpMenuList
} from '@/api/system/projectPermission'
import { ref } from 'vue'
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 activeTab = ref('user')
......@@ -540,10 +670,10 @@ const userTotal = ref(0)
const importProjectUserListTotal = ref(0)
const roleTotal = ref(0)
const importProjectRoleListTotal = ref(0)
const title = ref("")
const importUserTitle = ref("")
const importRoleTitle = ref("")
const importMenuTitle = ref("")
const title = ref('')
const importUserTitle = ref('')
const importRoleTitle = ref('')
const importMenuTitle = ref('')
const importProjectProjectListOpen = ref(false)
const importProjectUserListOpen = ref(false)
const importProjectRoleListOpen = ref(false)
......@@ -555,7 +685,6 @@ const importProjectUserListNames = ref([])
const importProjectRoleListIds = ref([])
const importProjectRoleListNames = ref([])
// 菜单树数据(直接从接口获取树形结构)
const menuTree = ref([])
// 当前项目绑定的选中菜单ID
......@@ -563,7 +692,7 @@ const checkedKeys = ref([])
// 树组件引用
const treeRef = ref(null)
// 添加响应式变量控制严格模式
const isCheckStrictly = ref(false); // 默认关闭严格模式(启用联动)
const isCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置
const defaultProps = {
children: 'children',
......@@ -619,10 +748,17 @@ const data = reactive({
menuName: undefined
}
})
const { queryParams,importProjectProjectListQueryParams, userQueryParams,
importProjectUserListQueryParams,roleQueryParams,importProjectRoleListQueryParams,
menuQueryParams,form, rules } = toRefs(data)
const {
queryParams,
importProjectProjectListQueryParams,
userQueryParams,
importProjectUserListQueryParams,
roleQueryParams,
importProjectRoleListQueryParams,
menuQueryParams,
form,
rules
} = toRefs(data)
//========用户-列表逻辑开始=========
/** 查询项目用户关系列表 */
......@@ -641,7 +777,7 @@ function userHandleQuery() {
}
/** 用户列表-重置按钮操作 */
function userResetQuery() {
proxy.resetForm("userQueryRef")
proxy.resetForm('userQueryRef')
userHandleQuery()
}
/** 删除项目用户关系 */
......@@ -649,16 +785,19 @@ function userHandleDelete(row) {
//项目和用户关系表主键id
const relProjectUserId = row.id
const userName = row.userName
proxy.$modal.confirm('是否确认删除用户账号为"' + userName + '"的项目和用户关系的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除用户账号为"' + userName + '"的项目和用户关系的数据项?')
.then(function () {
return delRelProjectUser(relProjectUserId)
}).then(() => {
})
.then(() => {
getUserList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
//========用户-列表逻辑结束=========
//========用户-导入逻辑开始=========
/** 用户列表-导入-查询导入项目用户关系列表 */
function getImportProjectUserList() {
......@@ -673,7 +812,7 @@ function getImportProjectUserList() {
function handleImportProjectUserList() {
getImportProjectUserList()
importProjectUserListOpen.value = true
importUserTitle.value = "导入用户"
importUserTitle.value = '导入用户'
}
/** 用户列表-导入-搜索按钮操作 */
function importProjectUserListHandleQuery() {
......@@ -682,7 +821,7 @@ function importProjectUserListHandleQuery() {
}
/** 用户列表-导入-重置按钮操作 */
function importProjectUserListResetQuery() {
proxy.resetForm("importProjectUserListQueryRef")
proxy.resetForm('importProjectUserListQueryRef')
importProjectUserListHandleQuery()
}
/** 用户列表-导入-多选框选中数据 */
......@@ -696,22 +835,25 @@ function importProjectUserListSubmitForm() {
const importProjectUserListNameList = importProjectUserListNames.value
const projectBizId = route.query.projectBizId
proxy.$modal.confirm('是否确认导入用户账号为"' + importProjectUserListNameList + '"的数据项?').then(function () {
return addImportProjectUserList(importProjectUserListIdList,projectBizId)
}).then(() => {
proxy.$modal
.confirm('是否确认导入用户账号为"' + importProjectUserListNameList + '"的数据项?')
.then(function () {
return addImportProjectUserList(importProjectUserListIdList, projectBizId)
})
.then(() => {
importProjectUserListOpen.value = false
getUserList()
proxy.$modal.msgSuccess("导入成功")
}).catch(() => {})
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
}
/** 用户列表-导入-取消 */
function importProjectUserListCancel() {
importProjectUserListOpen.value = false
proxy.resetForm("importProjectUserListQueryRef")
proxy.resetForm('importProjectUserListQueryRef')
}
//========用户-导入逻辑结束=========
//========角色-列表逻辑开始=========
/** 查询项目角色关系列表 */
function getRoleList() {
......@@ -729,7 +871,7 @@ function roleHandleQuery() {
}
/** 角色列表-重置按钮操作 */
function roleResetQuery() {
proxy.resetForm("roleQueryRef")
proxy.resetForm('roleQueryRef')
roleHandleQuery()
}
/** 删除项目角色关系 */
......@@ -737,16 +879,19 @@ function roleHandleDelete(row) {
//项目和角色关系表主键id
const relProjectRoleId = row.id
const roleName = row.roleName
proxy.$modal.confirm('是否确认删除角色名称为"' + roleName + '"的项目和角色关系的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除角色名称为"' + roleName + '"的项目和角色关系的数据项?')
.then(function () {
return delRelProjectRole(relProjectRoleId)
}).then(() => {
})
.then(() => {
getRoleList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
//========角色-列表逻辑结束=========
//========角色-导入逻辑开始=========
/** 角色列表-导入-查询导入项目角色关系列表 */
function getImportProjectRoleList() {
......@@ -761,7 +906,7 @@ function getImportProjectRoleList() {
function handleImportProjectRoleList() {
getImportProjectRoleList()
importProjectRoleListOpen.value = true
importRoleTitle.value = "导入角色"
importRoleTitle.value = '导入角色'
}
/** 角色列表-导入-搜索按钮操作 */
function importProjectRoleListHandleQuery() {
......@@ -770,7 +915,7 @@ function importProjectRoleListHandleQuery() {
}
/** 角色列表-导入-重置按钮操作 */
function importProjectRoleListResetQuery() {
proxy.resetForm("importProjectRoleListQueryRef")
proxy.resetForm('importProjectRoleListQueryRef')
importProjectRoleListHandleQuery()
}
/** 角色列表-导入-多选框选中数据 */
......@@ -784,28 +929,31 @@ function importProjectRoleListSubmitForm() {
const importProjectRoleListNameList = importProjectRoleListNames.value
const projectBizId = route.query.projectBizId
proxy.$modal.confirm('是否确认导入角色名称为"' + importProjectRoleListNameList + '"的数据项?').then(function () {
return addImportProjectRoleList(importProjectRoleListIdList,projectBizId)
}).then(() => {
proxy.$modal
.confirm('是否确认导入角色名称为"' + importProjectRoleListNameList + '"的数据项?')
.then(function () {
return addImportProjectRoleList(importProjectRoleListIdList, projectBizId)
})
.then(() => {
importProjectRoleListOpen.value = false
getRoleList()
proxy.$modal.msgSuccess("导入成功")
}).catch(() => {})
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
}
/** 角色列表-导入-取消 */
function importProjectRoleListCancel() {
importProjectRoleListOpen.value = false
proxy.resetForm("importProjectRoleListQueryRef")
proxy.resetForm('importProjectRoleListQueryRef')
}
//========角色-导入逻辑结束=========
//========菜单-列表逻辑结束=========
/** 查询菜单列表 */
function getMenuList() {
menuLoading.value = true
listMenu(menuQueryParams.value).then(response => {
menuList.value = proxy.handleTree(response.data.records, "menuBizId")
menuList.value = proxy.handleTree(response.data.records, 'menuBizId')
menuLoading.value = false
})
}
......@@ -824,18 +972,17 @@ function menuHandleQuery() {
}
/** 菜单列表-重置按钮操作 */
function menuResetQuery() {
proxy.resetForm("menuQueryRef")
proxy.resetForm('menuQueryRef')
menuHandleQuery()
}
//========菜单-列表逻辑结束=========
//========菜单-导入逻辑开始=========
/** 菜单列表-导入菜单 */
function handleImportProjectMenuList() {
loadMenuTree()
importProjectMenuListOpen.value = true
importMenuTitle.value = "导入菜单"
importMenuTitle.value = '导入菜单'
}
// 加载菜单树
const loadMenuTree = async () => {
......@@ -851,32 +998,31 @@ const loadMenuTree = async () => {
// 修改加载选中菜单列表的逻辑
const loadImportSelectedMenuList = async () => {
try {
const res = await getImportSelectedMenuList(route.query.projectBizId);
const targetKeys = res.data || [];
const res = await getImportSelectedMenuList(route.query.projectBizId)
const targetKeys = res.data || []
// 开启严格模式(禁用联动)
isCheckStrictly.value = true;
isCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效)
await nextTick();
await nextTick()
//清空并设置选中状态(此时不会触发联动)
if (treeRef.value) {
treeRef.value.setCheckedKeys([]);
treeRef.value.setCheckedKeys(targetKeys);
treeRef.value.setCheckedKeys([])
treeRef.value.setCheckedKeys(targetKeys)
}
//等待选中状态渲染完成
await nextTick();
await nextTick()
//关闭严格模式(恢复联动)
isCheckStrictly.value = false;
isCheckStrictly.value = false
} catch (error) {
console.error('加载选中的菜单列表失败:', error);
ElMessage.error('加载选中的菜单列表失败');
console.error('加载选中的菜单列表失败:', error)
ElMessage.error('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态
isCheckStrictly.value = false;
isCheckStrictly.value = false
}
}
// 保存
......@@ -897,7 +1043,6 @@ const saveImportSelectedMenuList = async () => {
getMenuList()
importProjectMenuListOpen.value = false
ElMessage.success('更新成功')
} catch (error) {
console.error('更新失败:', error)
ElMessage.error('更新失败')
......@@ -905,12 +1050,10 @@ const saveImportSelectedMenuList = async () => {
}
//========菜单-导入逻辑结束=========
//========分配角色-导入逻辑开始=========
// 弹窗显隐与标题
const assignRoleDialogVisible = ref(false)
const assignRoleTitle = ref("分配角色")
const assignRoleTitle = ref('分配角色')
// 左侧可选角色(查询参数 + 列表数据)
const leftQuery = reactive({
......@@ -925,7 +1068,6 @@ const leftLoading = ref(false)
const leftTotal = ref(0)
const leftSelectedRoles = ref([]) // 左侧选中的角色
// 右侧可选角色(查询参数 + 列表数据)
const rightQuery = reactive({
pageNo: 1,
......@@ -964,11 +1106,13 @@ function getLeftRoleList() {
leftLoading.value = true
listLeftRole({
...leftQuery
}).then(res => {
})
.then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
}).catch(() => {
})
.catch(() => {
leftLoading.value = false
})
}
......@@ -978,11 +1122,13 @@ function getRightRoleList() {
rightLoading.value = true
listRightRole({
...rightQuery
}).then(res => {
})
.then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
}).catch(() => {
})
.catch(() => {
rightLoading.value = false
})
}
......@@ -1005,7 +1151,7 @@ function queryLeftRoleList() {
/** 重置左侧搜索 */
function resetLeftRoleQuery() {
proxy.resetForm("leftQueryRef")
proxy.resetForm('leftQueryRef')
leftQuery.pageNo = 1
leftQuery.roleName = ''
getLeftRoleList()
......@@ -1019,7 +1165,7 @@ function queryRightRoleList() {
/** 重置右侧搜索 */
function resetRightRoleQuery() {
proxy.resetForm("rightQueryRef")
proxy.resetForm('rightQueryRef')
rightQuery.pageNo = 1
rightQuery.roleName = ''
getRightRoleList()
......@@ -1038,15 +1184,19 @@ function moveToRight() {
userBizId: leftQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?').then(function () {
proxy.$modal
.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?')
.then(function () {
return addRightRoleList(data)
}).then(() => {
})
.then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("添加成功")
}).catch(() => {})
proxy.$modal.msgSuccess('添加成功')
})
.catch(() => {})
}
/** 向左箭头:将右侧选中角色移除左侧,删除关系*/
......@@ -1062,15 +1212,19 @@ function moveToLeft() {
userBizId: rightQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?').then(function () {
proxy.$modal
.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?')
.then(function () {
return delRightRoleList(data)
}).then(() => {
})
.then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("移除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('移除成功')
})
.catch(() => {})
}
/** 取消操作 */
function cancelAssignRole() {
......@@ -1078,15 +1232,13 @@ function cancelAssignRole() {
}
//========分配角色-导入逻辑结束=========
//========分配菜单-导入逻辑开始=========
// 菜单树数据(直接从接口获取树形结构)
const fpMenuTree = ref([])
// 树组件引用
const fpMenuRef = ref(null)
// 添加响应式变量控制严格模式
const fpMenuIsCheckStrictly = ref(false); // 默认关闭严格模式(启用联动)
const fpMenuIsCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置
const fpMenuProps = {
children: 'children',
......@@ -1105,7 +1257,7 @@ const getFpMenuTreeParams = reactive({
projectBizId: route.query.projectBizId,
menuName: undefined
})
const fpMenuTitle = ref("")
const fpMenuTitle = ref('')
const fpMenuOpen = ref(false)
/** 分配菜单弹出 */
......@@ -1114,48 +1266,47 @@ function handleFpMenu(row) {
getSelectedFpMenuListParams.roleBizId = row.roleBizId
loadFpMenuTree()
fpMenuOpen.value = true
fpMenuTitle.value = "分配菜单"
fpMenuTitle.value = '分配菜单'
}
// 加载菜单树
const loadFpMenuTree = async () => {
try {
const res = await getFpMenuTree({...getFpMenuTreeParams})
const res = await getFpMenuTree({ ...getFpMenuTreeParams })
fpMenuTree.value = res.data // 直接使用后端返回的树形结构
loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) {
console.error('加载菜单树失败:', error)
proxy.$modal.msgSuccess("菜单加载失败")
proxy.$modal.msgSuccess('菜单加载失败')
}
}
// 修改加载选中菜单列表的逻辑
const loadSelectedFpMenuList = async () => {
try {
const res = await getSelectedFpMenuList({...getSelectedFpMenuListParams});
const targetKeys = res.data || [];
const res = await getSelectedFpMenuList({ ...getSelectedFpMenuListParams })
const targetKeys = res.data || []
// 开启严格模式(禁用联动)
fpMenuIsCheckStrictly.value = true;
fpMenuIsCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效)
await nextTick();
await nextTick()
//清空并设置选中状态(此时不会触发联动)
if (fpMenuRef.value) {
fpMenuRef.value.setCheckedKeys([]);
fpMenuRef.value.setCheckedKeys(targetKeys);
fpMenuRef.value.setCheckedKeys([])
fpMenuRef.value.setCheckedKeys(targetKeys)
}
//等待选中状态渲染完成
await nextTick();
await nextTick()
//关闭严格模式(恢复联动)
fpMenuIsCheckStrictly.value = false;
fpMenuIsCheckStrictly.value = false
} catch (error) {
console.error('加载选中的菜单列表失败:', error);
proxy.$modal.msgSuccess("加载选中的菜单列表失败")
console.error('加载选中的菜单列表失败:', error)
proxy.$modal.msgSuccess('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态
fpMenuIsCheckStrictly.value = false;
fpMenuIsCheckStrictly.value = false
}
}
// 保存
......@@ -1177,30 +1328,26 @@ const saveFpMenuList = async () => {
getRoleList()
fpMenuOpen.value = false
proxy.$modal.msgSuccess("更新成功")
proxy.$modal.msgSuccess('更新成功')
} catch (error) {
console.error('更新失败:', error)
proxy.$modal.msgSuccess("更新失败")
proxy.$modal.msgSuccess('更新失败')
}
}
//========分配菜单-导入逻辑结束=========
const handleTabChange = (tabName) => {
const handleTabChange = tabName => {
console.log('切换到:', tabName)
if (tabName === "user"){
if (tabName === 'user') {
//用户tab获取用户列表数据
getUserList()
}else if (tabName === "role"){
} else if (tabName === 'role') {
//用户tab获取角色列表数据
getRoleList()
}else if (tabName === "menu"){
} else if (tabName === 'menu') {
//用户tab获取菜单树数据
getMenuList()
}
}
getUserList()
......@@ -1283,7 +1430,7 @@ getUserList()
/* 激活态:覆盖父容器的底部边框,实现“凹陷” */
::v-deep .el-tabs__item.is-active::before {
content: "";
content: '';
position: absolute;
bottom: -1px; /* 覆盖父容器的 border-bottom */
left: 0;
......@@ -1309,8 +1456,6 @@ getUserList()
color: #909399;
}
.permission-section {
margin-top: 20px;
}
......@@ -1434,7 +1579,6 @@ getUserList()
background-color: #fff;
}
/* 右侧标题 */
.selected-title {
font-size: 14px;
......
......@@ -336,8 +336,6 @@ function handleDelete(row) {
/** 角色状态修改 */
function handleStatusChange(row) {
console.log('row', row.status)
const newStatus = row.status === 0 ? 0 : 1 // 获取切换后的新状态
const text = newStatus === 0 ? '停用' : '启用' // 根据新状态显示文本
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>
<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-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 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 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 label="状态" prop="status">
<el-select
......@@ -27,7 +51,14 @@
</el-select>
</el-form-item>
<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-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
......@@ -36,10 +67,27 @@
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="tenantList" >
<el-table-column label="租户LOGO" align="center" prop="logoUrl"/>
<el-table v-loading="loading" :data="tenantList">
<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="contactName"/>
<el-table-column label="联系人姓名" align="center" prop="contactName" />
<el-table-column label="联系人电话" align="center" prop="contactPhone" />
<el-table-column label="服务到期时间" align="center" prop="expireTime">
<template #default="scope">
......@@ -54,7 +102,7 @@
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="(val) => handleStatusChange(scope.row, $event)"
@change="val => handleStatusChange(scope.row, $event)"
></el-switch>
</template>
</el-table-column>
......@@ -63,15 +111,32 @@
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</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">
<el-button link type="primary" icon="Edit" @click="handlePermission(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>
<el-button link type="primary" icon="Edit" @click="handlePermission(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>
</el-table-column>
</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>
......@@ -139,15 +204,27 @@
<el-col :span="12">
<el-form-item label="状态">
<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-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="租户LOGO">
<el-input v-model="form.logoUrl" type="textarea" placeholder="请输入内容"></el-input>
<el-form-item label="租户LOGO" prop="logoUrl">
<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-col>
</el-row>
......@@ -159,38 +236,34 @@
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Tenant">
import { getToken } from "@/utils/auth"
import useAppStore from '@/store/modules/app'
import { changeUserStatus, listTenant, resetUserPwd, delUser, getTenant, updateTenant, addTenant,changeTenantStatus } from "@/api/system/tenant"
import {
listTenant,
delUser,
getTenant,
updateTenant,
addTenant,
changeTenantStatus
} from '@/api/system/tenant'
import useUserStore from '@/store/modules/user'
import ImageUpload from '@/components/ImageUpload/index.vue' //图片上传组件
const userStore = useUserStore()
const router = useRouter()
const appStore = useAppStore()
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 open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const title = 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({
form: {},
......@@ -204,20 +277,16 @@ const data = reactive({
status: undefined
},
rules: {
tenantName: [{ required: true, message: "租户名称不能为空", trigger: "blur" }],
contactEmail: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
contactPhone: [{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }]
tenantName: [{ required: true, message: '租户名称不能为空', trigger: 'blur' }],
contactEmail: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],
contactPhone: [
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }
]
}
})
const { queryParams, form, rules } = toRefs(data)
/** 通过条件过滤节点 */
const filterNode = (value, data) => {
if (!value) return true
return data.label.indexOf(value) !== -1
}
/** 查询租户列表 */
function getList() {
loading.value = true
......@@ -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() {
queryParams.value.pageNum = 1
......@@ -256,113 +306,37 @@ function handleQuery() {
/** 重置按钮操作 */
function resetQuery() {
dateRange.value = []
proxy.resetForm("queryRef")
proxy.resetForm('queryRef')
handleQuery()
}
/** 删除按钮操作 */
function handleDelete(row) {
const userIds = row.userId || ids.value
proxy.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?')
.then(function () {
return delUser(userIds)
}).then(() => {
})
.then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
proxy.download("system/user/export", {
...queryParams.value,
},`user_${new Date().getTime()}.xlsx`)
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 租户状态修改 */
function handleStatusChange(row, event) {
let text = row.status === 0 ? "停用" : "启用"
let text = row.status === 0 ? '停用' : '启用'
const tenantName = row.tenantName
proxy.$modal.confirm(`确认要${text}"${tenantName}"租户吗?`)
.then(() => changeTenantStatus({tenantBizId: row.tenantBizId, status: row.status}))
proxy.$modal
.confirm(`确认要${text}"${tenantName}"租户吗?`)
.then(() => changeTenantStatus({ tenantBizId: row.tenantBizId, status: row.status }))
.then(() => proxy.$modal.msgSuccess(`${text}成功`))
.catch(() => {
// 操作取消时恢复原状态
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() {
form.value = {
......@@ -374,12 +348,12 @@ function reset() {
contactEmail: undefined,
industry: undefined,
address: undefined,
status: "0",
status: '0',
expireTime: undefined,
maxProject: undefined,
maxUser: undefined
}
proxy.resetForm("tenantRef")
proxy.resetForm('tenantRef')
}
/** 取消按钮 */
......@@ -392,7 +366,7 @@ function cancel() {
function handleAdd() {
reset()
open.value = true
title.value = "添加租户"
title.value = '添加租户'
}
/** 修改按钮操作 */
......@@ -402,7 +376,7 @@ function handleUpdate(row) {
getTenant(tenantBizId).then(response => {
form.value = response.data
open.value = true
title.value = "修改用户"
title.value = '修改用户'
})
}
......@@ -413,18 +387,20 @@ function handlePermission(row) {
/** 新增或者修改租户提交按钮 */
function submitForm() {
proxy.$refs["tenantRef"].validate(valid => {
proxy.$refs['tenantRef'].validate(valid => {
if (valid) {
if (form.value.tenantBizId != undefined) {
updateTenant(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
proxy.$modal.msgSuccess('修改成功')
open.value = false
reset()
getList()
})
} else {
addTenant(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
proxy.$modal.msgSuccess('新增成功')
open.value = false
reset()
getList()
})
}
......@@ -434,3 +410,91 @@ function submitForm() {
getList()
</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>
<div class="app-container">
<!-- 选项卡组件:增加 custom-tabs 类名 -->
<el-tabs
v-model="activeTab"
type="card"
@tab-change="handleTabChange"
class="custom-tabs"
>
<el-tabs v-model="activeTab" type="card" @tab-change="handleTabChange" class="custom-tabs">
<!-- 项目权限选项卡 -->
<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-input
v-model="queryParams.projectName"
......@@ -22,7 +23,9 @@
<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="handleImportTenantProjectList">导入</el-button>
<el-button type="info" plain icon="Upload" @click="handleImportTenantProjectList"
>导入</el-button
>
</el-form-item>
</el-form>
......@@ -41,7 +44,9 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<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>
</el-table-column>
</el-table>
......@@ -55,8 +60,18 @@
/>
<!-- 项目导入(根据权限从项目池导入进来) -->
<el-dialog :title="title" v-model="importTenantProjectListOpen" width="700px" append-to-body>
<el-form :model="importTenantProjectListQueryParams" ref="importTenantProjectListQueryRef" :inline="true" label-width="68px">
<el-dialog
: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-input
v-model="importTenantProjectListQueryParams.projectName"
......@@ -67,12 +82,18 @@
/>
</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-form-item>
</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 label="项目名称" prop="projectName" />
<el-table-column prop="scope" label="作用域">
......@@ -95,11 +116,16 @@
</div>
</template>
</el-dialog>
</el-tab-pane>
<!-- 用户权限选项卡 -->
<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-input
v-model="userQueryParams.userName"
......@@ -130,7 +156,9 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="userHandleQuery">搜索</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>
......@@ -146,10 +174,19 @@
<dict-tag :options="sys_gender" :value="scope.row.gender" />
</template>
</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">
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)" >分配角色</el-button>
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)" >删除</el-button>
<el-button link type="primary" icon="Edit" @click="openAssignRoleDialog(scope.row)"
>分配角色</el-button
>
<el-button link type="primary" icon="Delete" @click="userHandleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
......@@ -163,8 +200,19 @@
/>
<!-- 用户导入(根据权限从用户池导入进来) -->
<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-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"
......@@ -193,12 +241,18 @@
/>
</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-form-item>
</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 label="账号" prop="userName" />
<el-table-column label="姓名" prop="realName" />
......@@ -237,7 +291,13 @@
<div class="assign-role-container">
<!-- 左侧:可选角色列表(带复选框) -->
<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-input
v-model="leftQuery.roleName"
......@@ -247,7 +307,9 @@
/>
</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-form-item>
</el-form>
......@@ -310,7 +372,13 @@
<div class="right-panel">
<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-input
v-model="rightQuery.roleName"
......@@ -320,7 +388,9 @@
/>
</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-form-item>
</el-form>
......@@ -358,7 +428,6 @@
<!-- 底部统一保存按钮 -->
<template #footer>
<div class="dialog-footer">
<!-- <el-button type="primary" @click="saveAllRoles">确 定</el-button>-->
<el-button @click="cancelAssignRole">取 消</el-button>
</div>
</template>
......@@ -366,7 +435,13 @@
</el-tab-pane>
<!-- 角色权限选项卡 -->
<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-input
v-model="roleQueryParams.roleName"
......@@ -379,7 +454,9 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="roleHandleQuery">搜索</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>
......@@ -396,10 +473,19 @@
<dict-tag :options="sys_role_type" :value="scope.row.roleType" />
</template>
</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">
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)" >分配菜单</el-button>
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)" >删除</el-button>
<el-button link type="primary" icon="Edit" @click="handleFpMenu(scope.row)"
>分配菜单</el-button
>
<el-button link type="primary" icon="Delete" @click="roleHandleDelete(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
......@@ -443,8 +529,19 @@
</el-dialog>
<!-- 角色导入(根据权限从角色池导入进来) -->
<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-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="roleName">
<el-input
v-model="importTenantRoleListQueryParams.roleName"
......@@ -455,12 +552,18 @@
/>
</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-form-item>
</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 label="角色名称" prop="roleName" />
<el-table-column prop="scope" label="作用域">
......@@ -491,7 +594,13 @@
</el-tab-pane>
<!-- 菜单权限选项卡 -->
<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-input
v-model="menuQueryParams.menuName"
......@@ -504,20 +613,20 @@
<el-form-item>
<el-button type="primary" icon="Search" @click="menuHandleQuery">搜索</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>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="info"
plain
icon="Sort"
@click="toggleExpandAll"
>展开/折叠</el-button>
<el-button type="info" plain icon="Sort" @click="toggleExpandAll">展开/折叠</el-button>
</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-table
......@@ -528,7 +637,11 @@
:default-expand-all="isExpandAll"
: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">
<template #default="scope">
<svg-icon :icon-class="scope.row.icon" />
......@@ -546,14 +659,25 @@
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<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>
</el-table-column>
</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">
<el-card header="导入菜单" class="mt-4">
......@@ -582,24 +706,64 @@
</div>
</el-dialog>
</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>
</div>
</template>
<script setup name="TenantPermission">
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 {
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 DeptList from './deptList.vue'
import InsuranceList from './insuranceList.vue'
import { ref } from 'vue'
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 activeTab = ref('project')
......@@ -632,10 +796,10 @@ const userTotal = ref(0)
const importTenantUserListTotal = ref(0)
const roleTotal = ref(0)
const importTenantRoleListTotal = ref(0)
const title = ref("")
const importUserTitle = ref("")
const importRoleTitle = ref("")
const importMenuTitle = ref("")
const title = ref('')
const importUserTitle = ref('')
const importRoleTitle = ref('')
const importMenuTitle = ref('')
const importTenantProjectListOpen = ref(false)
const importTenantUserListOpen = ref(false)
const importTenantRoleListOpen = ref(false)
......@@ -647,13 +811,12 @@ const importTenantUserListNames = ref([])
const importTenantRoleListIds = ref([])
const importTenantRoleListNames = ref([])
// 菜单树数据(直接从接口获取树形结构)
const menuTree = ref([])
// 树组件引用
const treeRef = ref(null)
// 添加响应式变量控制严格模式
const isCheckStrictly = ref(false); // 默认关闭严格模式(启用联动)
const isCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置
const defaultProps = {
children: 'children',
......@@ -709,9 +872,17 @@ const data = reactive({
menuName: undefined
}
})
const { queryParams,importTenantProjectListQueryParams, userQueryParams,
importTenantUserListQueryParams,roleQueryParams,importTenantRoleListQueryParams,
menuQueryParams,form, rules } = toRefs(data)
const {
queryParams,
importTenantProjectListQueryParams,
userQueryParams,
importTenantUserListQueryParams,
roleQueryParams,
importTenantRoleListQueryParams,
menuQueryParams,
form,
rules
} = toRefs(data)
//========项目-列表逻辑=========
/** 查询租户项目关系列表 */
......@@ -730,7 +901,7 @@ function handleQuery() {
}
/** 项目列表-重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
proxy.resetForm('queryRef')
handleQuery()
}
/** 删除租户项目关系 */
......@@ -738,12 +909,16 @@ function handleDelete(row) {
//租户和项目关系表主键id
const relTenantProjectId = row.id
const projectName = row.projectName
proxy.$modal.confirm('是否确认删除项目名称为"' + projectName + '"的租户和项目关系的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除项目名称为"' + projectName + '"的租户和项目关系的数据项?')
.then(function () {
return delRelTenantProject(relTenantProjectId)
}).then(() => {
})
.then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
//========项目-列表结束=========
......@@ -761,7 +936,7 @@ function getImportTenantProjectList() {
function handleImportTenantProjectList() {
getImportTenantProjectList()
importTenantProjectListOpen.value = true
title.value = "导入项目"
title.value = '导入项目'
}
/** 搜索按钮操作 */
function importTenantProjectListHandleQuery() {
......@@ -770,7 +945,7 @@ function importTenantProjectListHandleQuery() {
}
/** 重置按钮操作 */
function importTenantProjectListResetQuery() {
proxy.resetForm("importTenantProjectListQueryRef")
proxy.resetForm('importTenantProjectListQueryRef')
importTenantProjectListHandleQuery()
}
/** 多选框选中数据 */
......@@ -784,22 +959,25 @@ function importTenantProjectListSubmitForm() {
const importTenantProjectListNameList = importTenantProjectListNames.value
const tenantBizId = route.query.tenantBizId
proxy.$modal.confirm('是否确认导入项目名称为"' + importTenantProjectListNameList + '"的数据项?').then(function () {
return addImportTenantProjectList(importTenantProjectListIdList,tenantBizId)
}).then(() => {
proxy.$modal
.confirm('是否确认导入项目名称为"' + importTenantProjectListNameList + '"的数据项?')
.then(function () {
return addImportTenantProjectList(importTenantProjectListIdList, tenantBizId)
})
.then(() => {
importTenantProjectListOpen.value = false
getList()
proxy.$modal.msgSuccess("导入成功")
}).catch(() => {})
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
}
/** 导入项目取消 */
function importTenantProjectListCancel(){
function importTenantProjectListCancel() {
importTenantProjectListOpen.value = false
proxy.resetForm("importTenantProjectListQueryRef")
proxy.resetForm('importTenantProjectListQueryRef')
}
//========项目-导入逻辑结束=========
//========用户-列表逻辑开始=========
/** 查询租户用户关系列表 */
function getUserList() {
......@@ -817,7 +995,7 @@ function userHandleQuery() {
}
/** 用户列表-重置按钮操作 */
function userResetQuery() {
proxy.resetForm("userQueryRef")
proxy.resetForm('userQueryRef')
userHandleQuery()
}
/** 删除租户用户关系 */
......@@ -825,16 +1003,19 @@ function userHandleDelete(row) {
//租户和用户关系表主键id
const relTenantUserId = row.id
const userName = row.userName
proxy.$modal.confirm('是否确认删除用户账号为"' + userName + '"的租户和用户关系的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除用户账号为"' + userName + '"的租户和用户关系的数据项?')
.then(function () {
return delRelTenantUser(relTenantUserId)
}).then(() => {
})
.then(() => {
getUserList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
//========用户-列表逻辑结束=========
//========用户-导入逻辑开始=========
/** 用户列表-导入-查询导入租户用户关系列表 */
function getImportTenantUserList() {
......@@ -849,7 +1030,7 @@ function getImportTenantUserList() {
function handleImportTenantUserList() {
getImportTenantUserList()
importTenantUserListOpen.value = true
importUserTitle.value = "导入用户"
importUserTitle.value = '导入用户'
}
/** 用户列表-导入-搜索按钮操作 */
function importTenantUserListHandleQuery() {
......@@ -858,7 +1039,7 @@ function importTenantUserListHandleQuery() {
}
/** 用户列表-导入-重置按钮操作 */
function importTenantUserListResetQuery() {
proxy.resetForm("importTenantUserListQueryRef")
proxy.resetForm('importTenantUserListQueryRef')
importTenantUserListHandleQuery()
}
/** 用户列表-导入-多选框选中数据 */
......@@ -872,22 +1053,25 @@ function importTenantUserListSubmitForm() {
const importTenantUserListNameList = importTenantUserListNames.value
const tenantBizId = route.query.tenantBizId
proxy.$modal.confirm('是否确认导入用户账号为"' + importTenantUserListNameList + '"的数据项?').then(function () {
return addImportTenantUserList(importTenantUserListIdList,tenantBizId)
}).then(() => {
proxy.$modal
.confirm('是否确认导入用户账号为"' + importTenantUserListNameList + '"的数据项?')
.then(function () {
return addImportTenantUserList(importTenantUserListIdList, tenantBizId)
})
.then(() => {
importTenantUserListOpen.value = false
getUserList()
proxy.$modal.msgSuccess("导入成功")
}).catch(() => {})
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
}
/** 用户列表-导入-取消 */
function importTenantUserListCancel() {
importTenantUserListOpen.value = false
proxy.resetForm("importTenantUserListQueryRef")
proxy.resetForm('importTenantUserListQueryRef')
}
//========用户-导入逻辑结束=========
//========角色-列表逻辑开始=========
/** 查询租户角色关系列表 */
function getRoleList() {
......@@ -905,7 +1089,7 @@ function roleHandleQuery() {
}
/** 角色列表-重置按钮操作 */
function roleResetQuery() {
proxy.resetForm("roleQueryRef")
proxy.resetForm('roleQueryRef')
roleHandleQuery()
}
/** 删除租户角色关系 */
......@@ -913,16 +1097,19 @@ function roleHandleDelete(row) {
//租户和角色关系表主键id
const relTenantRoleId = row.id
const roleName = row.roleName
proxy.$modal.confirm('是否确认删除角色名称为"' + roleName + '"的租户和角色关系的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除角色名称为"' + roleName + '"的租户和角色关系的数据项?')
.then(function () {
return delRelTenantRole(relTenantRoleId)
}).then(() => {
})
.then(() => {
getRoleList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
//========角色-列表逻辑结束=========
//========角色-导入逻辑开始=========
/** 角色列表-导入-查询导入租户角色关系列表 */
function getImportTenantRoleList() {
......@@ -937,7 +1124,7 @@ function getImportTenantRoleList() {
function handleImportTenantRoleList() {
getImportTenantRoleList()
importTenantRoleListOpen.value = true
importRoleTitle.value = "导入角色"
importRoleTitle.value = '导入角色'
}
/** 角色列表-导入-搜索按钮操作 */
function importTenantRoleListHandleQuery() {
......@@ -946,7 +1133,7 @@ function importTenantRoleListHandleQuery() {
}
/** 角色列表-导入-重置按钮操作 */
function importTenantRoleListResetQuery() {
proxy.resetForm("importTenantRoleListQueryRef")
proxy.resetForm('importTenantRoleListQueryRef')
importTenantRoleListHandleQuery()
}
/** 角色列表-导入-多选框选中数据 */
......@@ -960,28 +1147,31 @@ function importTenantRoleListSubmitForm() {
const importTenantRoleListNameList = importTenantRoleListNames.value
const tenantBizId = route.query.tenantBizId
proxy.$modal.confirm('是否确认导入角色名称为"' + importTenantRoleListNameList + '"的数据项?').then(function () {
return addImportTenantRoleList(importTenantRoleListIdList,tenantBizId)
}).then(() => {
proxy.$modal
.confirm('是否确认导入角色名称为"' + importTenantRoleListNameList + '"的数据项?')
.then(function () {
return addImportTenantRoleList(importTenantRoleListIdList, tenantBizId)
})
.then(() => {
importTenantRoleListOpen.value = false
getRoleList()
proxy.$modal.msgSuccess("导入成功")
}).catch(() => {})
proxy.$modal.msgSuccess('导入成功')
})
.catch(() => {})
}
/** 角色列表-导入-取消 */
function importTenantRoleListCancel() {
importTenantRoleListOpen.value = false
proxy.resetForm("importTenantRoleListQueryRef")
proxy.resetForm('importTenantRoleListQueryRef')
}
//========角色-导入逻辑结束=========
//========菜单-列表逻辑结束=========
/** 查询菜单列表 */
function getMenuList() {
menuLoading.value = true
listMenu(menuQueryParams.value).then(response => {
menuList.value = proxy.handleTree(response.data.records, "menuBizId")
menuList.value = proxy.handleTree(response.data.records, 'menuBizId')
menuLoading.value = false
})
}
......@@ -1000,18 +1190,17 @@ function menuHandleQuery() {
}
/** 菜单列表-重置按钮操作 */
function menuResetQuery() {
proxy.resetForm("menuQueryRef")
proxy.resetForm('menuQueryRef')
menuHandleQuery()
}
//========菜单-列表逻辑结束=========
//========菜单-导入逻辑开始=========
/** 菜单列表-导入菜单 */
function handleImportTenantMenuList() {
loadMenuTree()
importTenantMenuListOpen.value = true
importMenuTitle.value = "导入菜单"
importMenuTitle.value = '导入菜单'
}
// 加载菜单树
const loadMenuTree = async () => {
......@@ -1027,32 +1216,31 @@ const loadMenuTree = async () => {
// 修改加载选中菜单列表的逻辑
const loadImportSelectedMenuList = async () => {
try {
const res = await getImportSelectedMenuList(route.query.tenantBizId);
const targetKeys = res.data || [];
const res = await getImportSelectedMenuList(route.query.tenantBizId)
const targetKeys = res.data || []
// 开启严格模式(禁用联动)
isCheckStrictly.value = true;
isCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效)
await nextTick();
await nextTick()
//清空并设置选中状态(此时不会触发联动)
if (treeRef.value) {
treeRef.value.setCheckedKeys([]);
treeRef.value.setCheckedKeys(targetKeys);
treeRef.value.setCheckedKeys([])
treeRef.value.setCheckedKeys(targetKeys)
}
//等待选中状态渲染完成
await nextTick();
await nextTick()
//关闭严格模式(恢复联动)
isCheckStrictly.value = false;
isCheckStrictly.value = false
} catch (error) {
console.error('加载选中的菜单列表失败:', error);
proxy.$modal.msgError('加载选中的菜单列表失败');
console.error('加载选中的菜单列表失败:', error)
proxy.$modal.msgError('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态
isCheckStrictly.value = false;
isCheckStrictly.value = false
}
}
// 保存
......@@ -1073,7 +1261,6 @@ const saveImportSelectedMenuList = async () => {
getMenuList()
importTenantMenuListOpen.value = false
proxy.$modal.msgSuccess('更新成功')
} catch (error) {
console.error('更新失败:', error)
proxy.$modal.msgError('更新失败')
......@@ -1081,14 +1268,10 @@ const saveImportSelectedMenuList = async () => {
}
//========菜单-导入逻辑结束=========
//========分配角色-导入逻辑开始=========
// 弹窗显隐与标题
const assignRoleDialogVisible = ref(false)
const assignRoleTitle = ref("分配角色")
const assignRoleTitle = ref('分配角色')
// 左侧可选角色(查询参数 + 列表数据)
const leftQuery = reactive({
......@@ -1103,7 +1286,6 @@ const leftLoading = ref(false)
const leftTotal = ref(0)
const leftSelectedRoles = ref([]) // 左侧选中的角色
// 右侧可选角色(查询参数 + 列表数据)
const rightQuery = reactive({
pageNo: 1,
......@@ -1142,11 +1324,13 @@ function getLeftRoleList() {
leftLoading.value = true
listLeftRole({
...leftQuery
}).then(res => {
})
.then(res => {
leftRoleList.value = res.data.records
leftTotal.value = res.data.total
leftLoading.value = false
}).catch(() => {
})
.catch(() => {
leftLoading.value = false
})
}
......@@ -1156,11 +1340,13 @@ function getRightRoleList() {
rightLoading.value = true
listRightRole({
...rightQuery
}).then(res => {
})
.then(res => {
rightRoleList.value = res.data.records
rightTotal.value = res.data.total
rightLoading.value = false
}).catch(() => {
})
.catch(() => {
rightLoading.value = false
})
}
......@@ -1183,7 +1369,7 @@ function queryLeftRoleList() {
/** 重置左侧搜索 */
function resetLeftRoleQuery() {
proxy.resetForm("leftQueryRef")
proxy.resetForm('leftQueryRef')
leftQuery.pageNo = 1
leftQuery.roleName = ''
getLeftRoleList()
......@@ -1197,7 +1383,7 @@ function queryRightRoleList() {
/** 重置右侧搜索 */
function resetRightRoleQuery() {
proxy.resetForm("rightQueryRef")
proxy.resetForm('rightQueryRef')
rightQuery.pageNo = 1
rightQuery.roleName = ''
getRightRoleList()
......@@ -1216,15 +1402,19 @@ function moveToRight() {
userBizId: leftQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?').then(function () {
proxy.$modal
.confirm('是否向右移动角色名称为"' + leftRoleNameList + '"的数据项?')
.then(function () {
return addRightRoleList(data)
}).then(() => {
})
.then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("添加成功")
}).catch(() => {})
proxy.$modal.msgSuccess('添加成功')
})
.catch(() => {})
}
/** 向左箭头:将右侧选中角色移除左侧,删除关系*/
......@@ -1240,15 +1430,19 @@ function moveToLeft() {
userBizId: rightQuery.userBizId,
roleBizIdList: roleBizIdList
}
proxy.$modal.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?').then(function () {
proxy.$modal
.confirm('是否向左移除角色名称为"' + rightRoleNameList + '"的数据项?')
.then(function () {
return delRightRoleList(data)
}).then(() => {
})
.then(() => {
// 加载左侧待选角色分页列表(排除已选)
getLeftRoleList()
// 加载右侧已选角色分页列表
getRightRoleList()
proxy.$modal.msgSuccess("移除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('移除成功')
})
.catch(() => {})
}
/** 底部保存按钮:统一提交所有变更 */
......@@ -1263,14 +1457,17 @@ function saveAllRoles() {
const removeIds = oldRoleIds.filter(id => !newRoleIds.includes(id))
// 调用接口统一保存(一次请求)
proxy.$modal.confirm(`确认要分配 ${newRoleIds.length} 个角色吗?`).then(() => {
proxy.$modal
.confirm(`确认要分配 ${newRoleIds.length} 个角色吗?`)
.then(() => {
return assignUserRole({
userId: leftQuery.userId,
addRoleIds: addIds,
removeRoleIds: removeIds
})
}).then(() => {
proxy.$modal.msgSuccess("角色分配成功")
})
.then(() => {
proxy.$modal.msgSuccess('角色分配成功')
assignRoleDialogVisible.value = false
// 可触发父组件刷新用户列表
// emit('refreshUserList')
......@@ -1282,15 +1479,13 @@ function cancelAssignRole() {
}
//========分配角色-导入逻辑结束=========
//========分配菜单-导入逻辑开始=========
// 菜单树数据(直接从接口获取树形结构)
const fpMenuTree = ref([])
// 树组件引用
const fpMenuRef = ref(null)
// 添加响应式变量控制严格模式
const fpMenuIsCheckStrictly = ref(false); // 默认关闭严格模式(启用联动)
const fpMenuIsCheckStrictly = ref(false) // 默认关闭严格模式(启用联动)
// 树形配置
const fpMenuProps = {
children: 'children',
......@@ -1309,7 +1504,7 @@ const getFpMenuTreeParams = reactive({
tenantBizId: route.query.tenantBizId,
menuName: undefined
})
const fpMenuTitle = ref("")
const fpMenuTitle = ref('')
const fpMenuOpen = ref(false)
/** 分配菜单弹出 */
......@@ -1318,12 +1513,12 @@ function handleFpMenu(row) {
getSelectedFpMenuListParams.roleBizId = row.roleBizId
loadFpMenuTree()
fpMenuOpen.value = true
fpMenuTitle.value = "分配菜单"
fpMenuTitle.value = '分配菜单'
}
// 加载菜单树
const loadFpMenuTree = async () => {
try {
const res = await getFpMenuTree({...getFpMenuTreeParams})
const res = await getFpMenuTree({ ...getFpMenuTreeParams })
fpMenuTree.value = res.data // 直接使用后端返回的树形结构
loadSelectedFpMenuList() // 加载选中的菜单列表,更新树勾选
} catch (error) {
......@@ -1334,32 +1529,31 @@ const loadFpMenuTree = async () => {
// 修改加载选中菜单列表的逻辑
const loadSelectedFpMenuList = async () => {
try {
const res = await getSelectedFpMenuList({...getSelectedFpMenuListParams});
const targetKeys = res.data || [];
const res = await getSelectedFpMenuList({ ...getSelectedFpMenuListParams })
const targetKeys = res.data || []
// 开启严格模式(禁用联动)
fpMenuIsCheckStrictly.value = true;
fpMenuIsCheckStrictly.value = true
// 等待DOM更新(确保严格模式生效)
await nextTick();
await nextTick()
//清空并设置选中状态(此时不会触发联动)
if (fpMenuRef.value) {
fpMenuRef.value.setCheckedKeys([]);
fpMenuRef.value.setCheckedKeys(targetKeys);
fpMenuRef.value.setCheckedKeys([])
fpMenuRef.value.setCheckedKeys(targetKeys)
}
//等待选中状态渲染完成
await nextTick();
await nextTick()
//关闭严格模式(恢复联动)
fpMenuIsCheckStrictly.value = false;
fpMenuIsCheckStrictly.value = false
} catch (error) {
console.error('加载选中的菜单列表失败:', error);
proxy.$modal.msgError('加载选中的菜单列表失败');
console.error('加载选中的菜单列表失败:', error)
proxy.$modal.msgError('加载选中的菜单列表失败')
// 异常时也要恢复严格模式状态
fpMenuIsCheckStrictly.value = false;
fpMenuIsCheckStrictly.value = false
}
}
// 保存
......@@ -1382,7 +1576,6 @@ const saveFpMenuList = async () => {
getRoleList()
fpMenuOpen.value = false
proxy.$modal.msgSuccess('更新成功')
} catch (error) {
console.error('更新失败:', error)
proxy.$modal.msgError('更新失败')
......@@ -1390,23 +1583,21 @@ const saveFpMenuList = async () => {
}
//========分配菜单-导入逻辑结束=========
const handleTabChange = (tabName) => {
const handleTabChange = tabName => {
console.log('切换到:', tabName)
if (tabName === "project") {
if (tabName === 'project') {
//项目tab获取项目列表数据
getList()
}else if (tabName === "user"){
} else if (tabName === 'user') {
//用户tab获取用户列表数据
getUserList()
}else if (tabName === "role"){
} else if (tabName === 'role') {
//用户tab获取角色列表数据
getRoleList()
}else if (tabName === "menu"){
} else if (tabName === 'menu') {
//用户tab获取菜单树数据
getMenuList()
}
}
getList()
......@@ -1489,7 +1680,7 @@ getList()
/* 激活态:覆盖父容器的底部边框,实现“凹陷” */
::v-deep .el-tabs__item.is-active::before {
content: "";
content: '';
position: absolute;
bottom: -1px; /* 覆盖父容器的 border-bottom */
left: 0;
......@@ -1515,8 +1706,6 @@ getList()
color: #909399;
}
.permission-section {
margin-top: 20px;
}
......@@ -1640,7 +1829,6 @@ getList()
background-color: #fff;
}
/* 右侧标题 */
.selected-title {
font-size: 14px;
......
......@@ -83,7 +83,7 @@
v-model="scope.row.status"
:active-value="1"
:inactive-value="0"
@change="(val) => handleStatusChange(scope.row, $event)"
@change="val => handleStatusChange(scope.row, $event)"
></el-switch>
</template>
</el-table-column>
......@@ -108,12 +108,21 @@
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200px">
<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>-->
<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>
......@@ -132,7 +141,7 @@
<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-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">
......@@ -147,12 +156,23 @@
<el-form-item label="手机号" prop="mobile">
<el-input v-model="form.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="用户属性">
<el-select v-model="form.attribute" placeholder="请输入">
<el-option
v-for="item in sys_user_attribute"
:key="item.value"
:label="item.label"
:value="Number(item.value)"
/>
</el-select>
</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)">
:value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
......@@ -162,7 +182,8 @@
<el-radio
v-for="dict in sys_status"
:key="Number(dict.value)"
:value="Number(dict.value)">
:value="Number(dict.value)"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
......@@ -173,24 +194,37 @@
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"></el-button>
<el-button type="primary" @click="submitForm"></el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="User">
import { listUser,getUser,updateUser,addUser,changeUserStatus,resetUserPwd } from "@/api/system/user"
import {
listUser,
getUser,
updateUser,
addUser,
changeUserStatus,
resetUserPwd
} from '@/api/system/user'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const router = useRouter()
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 open = ref(false)
......@@ -198,7 +232,7 @@ const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const total = ref(0)
const title = ref("")
const title = ref('')
const data = reactive({
form: {},
......@@ -213,16 +247,26 @@ const data = reactive({
isSuperAdmin: undefined
},
rules: {
userName: [{ required: true, message: "账号不能为空", trigger: "blur" }],
realName: [{ required: true, message: "姓名不能为空", trigger: "blur" }],
nickName: [{ required: true, message: "昵称不能为空", trigger: "blur" }],
email: [{ required: true, message: "邮箱不能为空", trigger: "blur" },{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }],
mobile: [{ 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" }]
},
userName: [{ required: true, message: '账号不能为空', trigger: 'blur' }],
realName: [{ required: true, message: '姓名不能为空', trigger: 'blur' }],
nickName: [{ required: true, message: '昵称不能为空', trigger: 'blur' }],
email: [
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
],
mobile: [
{ 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)
......@@ -244,28 +288,33 @@ function handleQuery() {
/** 重置按钮操作 */
function resetQuery() {
proxy.resetForm("queryRef")
proxy.resetForm('queryRef')
handleQuery()
}
/** 删除按钮操作 */
function handleDelete(row) {
const roleIds = row.roleId || ids.value
proxy.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function () {
proxy.$modal
.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?')
.then(function () {
return delRole(roleIds)
}).then(() => {
})
.then(() => {
getList()
proxy.$modal.msgSuccess("删除成功")
}).catch(() => {})
proxy.$modal.msgSuccess('删除成功')
})
.catch(() => {})
}
/** 用户状态修改 */
function handleStatusChange(row, event) {
debugger
let text = row.status === 0 ? "停用" : "启用"
let text = row.status === 0 ? '停用' : '启用'
const userName = row.userName
proxy.$modal.confirm(`确认要${text}"${userName}"用户吗?`)
.then(() => changeUserStatus({userBizId: row.userBizId, status: row.status}))
proxy.$modal
.confirm(`确认要${text}"${userName}"用户吗?`)
.then(() => changeUserStatus({ userBizId: row.userBizId, status: row.status }))
.then(() => proxy.$modal.msgSuccess(`${text}成功`))
.catch(() => {
// 操作取消时恢复原状态
......@@ -287,14 +336,14 @@ function reset() {
gender: 0,
status: 1
}
proxy.resetForm("userRef")
proxy.resetForm('userRef')
}
/** 添加用户 */
function handleAdd() {
reset()
open.value = true
title.value = "添加用户"
title.value = '添加用户'
}
/** 修改用户弹框 */
......@@ -304,45 +353,48 @@ function handleUpdate(row) {
getUser(userBizId).then(response => {
form.value = response.data
open.value = true
title.value = "修改项目"
title.value = '修改项目'
})
}
/** 重置密码按钮操作 */
function handleResetPwd(row) {
proxy.$prompt('请输入"' + row.userName + '"的新密码', "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
proxy
.$prompt('请输入"' + row.userName + '"的新密码', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
inputPattern: /^.{5,20}$/,
inputErrorMessage: "用户密码长度必须介于 5 和 20 之间",
inputValidator: (value) => {
inputErrorMessage: '用户密码长度必须介于 5 和 20 之间',
inputValidator: value => {
if (/<|>|"|'|\||\\/.test(value)) {
return "不能包含非法字符:< > \" ' \\\ |"
return '不能包含非法字符:< > " \' \\\ |'
}
},
}).then(({ value }) => {
resetUserPwd({userBizId: row.userBizId,password: value}).then(response => {
}
})
.then(({ value }) => {
resetUserPwd({ userBizId: row.userBizId, password: value }).then(response => {
getList()
proxy.$modal.msgSuccess("修改成功,新密码是:" + value)
proxy.$modal.msgSuccess('修改成功,新密码是:' + value)
})
}).catch(() => {})
})
.catch(() => {})
}
/** 添加和修改用户提交按钮 */
function submitForm() {
debugger
proxy.$refs["userRef"].validate(valid => {
proxy.$refs['userRef'].validate(valid => {
console.log('valid', valid)
if (valid) {
if (form.value.userBizId) {
updateUser(form.value).then(response => {
proxy.$modal.msgSuccess("修改成功")
proxy.$modal.msgSuccess('修改成功')
open.value = false
getList()
})
} else {
addUser(form.value).then(response => {
proxy.$modal.msgSuccess("新增成功")
proxy.$modal.msgSuccess('新增成功')
open.value = false
getList()
})
......
<template>
<div class="app-card" @click="handleClick">
<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>
<div class="app-name">{{ appName }}</div>
......@@ -11,6 +11,8 @@
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { getVisitPermission } from '@/api/common'
import { getToken } from '@/utils/auth'
const props = defineProps({
project: {
......@@ -23,6 +25,10 @@ const router = useRouter()
const appName = computed(() => props.project.projectName)
const handleClick = () => {
console.log('点击了应用卡片', props.project)
getVisitPermission(props.project.projectBizId).then(response => {
if (response.code === 200) {
// 有权限访问项目,进行跳转
// 记录最近使用的应用
const recentApps = JSON.parse(localStorage.getItem('recentApps') || '[]')
const newRecent = [
......@@ -33,8 +39,11 @@ const handleClick = () => {
// 根据 isIn 字段决定跳转逻辑
if (props.project.isIn === 0 && props.project.projectUrl) {
let newUrl = `${props.project.projectUrl}?projectBizId=${
props.project.projectBizId
}&tenantBizId=${props.project.tenantBizId}&token=${getToken()}`
// 外部跳转
window.open(props.project.projectUrl, '_blank')
window.open(newUrl, '_blank')
} else {
// 内部跳转
router.push({
......@@ -45,6 +54,12 @@ const handleClick = () => {
}
})
}
} else {
// 没访问权限提示信息
proxy.$modal.msgError(response.msg)
}
})
return
}
</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