fix:1,优化投注记录

main
YuanJian 2025-09-16 09:37:40 +08:00
parent 609cc7b1b8
commit 3416e62a1b
32 changed files with 2380 additions and 2236 deletions

BIN
dist.rar

Binary file not shown.

View File

@ -8,6 +8,22 @@ export function listRole(query) {
params: query
})
}
// 获取角色选择框列表
export function optionselect(query) {
return request({
url: '/system/role/optionselect',
method: 'get',
params: query
})
}
// 获取菜单角色
export function getMenu(roleId) {
return request({
url: '/system/role/getMenu/' + roleId,
method: 'get'
})
}
// 查询角色详细
export function getRole(roleId) {
@ -97,7 +113,15 @@ export function authUserCancelAll(data) {
return request({
url: '/system/role/authUser/cancelAll',
method: 'put',
params: data
data: data
})
}
// 批量清除关联账号
export function authRolecancelAll(data) {
return request({
url: '/system/role/authRole/cancelAll',
method: 'put',
data: data
})
}
@ -106,7 +130,7 @@ export function authUserSelectAll(data) {
return request({
url: '/system/role/authUser/selectAll',
method: 'put',
params: data
data: data
})
}

View File

@ -43,13 +43,36 @@ export function delUser(userId) {
method: 'delete'
})
}
//删除账户/批量删除
export function deleteUserSystem(data) {
return request({
url: '/system/user/delete',
method: 'delete',
data: data
})
}
//冻结账户
export function changeStatus(data) {
return request({
url: '/system/user/changeStatus',
method: 'put',
data: data
})
}
//清空角色
export function resetRole(data) {
return request({
url: '/system/user/resetRole',
method: 'put',
data: data
})
}
// 用户密码重置
export function resetUserPwd(userId, password,codeGoogle) {
export function resetUserPwd(userId,inputGoogleCode) {
const data = {
userId,
password,
codeGoogle
inputGoogleCode
}
return request({
url: '/system/user/resetPwd',
@ -58,6 +81,20 @@ export function resetUserPwd(userId, password,codeGoogle) {
})
}
// 用户重置谷歌验证码
export function resetGoogle(userId,inputGoogleCode) {
const data = {
userId,
inputGoogleCode
}
return request({
url: '/system/user/resetGoogle',
method: 'put',
data: data
})
}
// 用户状态修改
export function changeUserStatus(userId, status) {
const data = {
@ -89,10 +126,11 @@ export function updateUserProfile(data) {
}
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
export function updateUserPwd(oldPassword, newPassword,codeGoogle) {
const data = {
oldPassword,
newPassword
newPassword,
codeGoogle
}
return request({
url: '/system/user/profile/updatePwd',
@ -101,6 +139,15 @@ export function updateUserPwd(oldPassword, newPassword) {
})
}
// 修改密码
export function updateUserPassword(data) {
return request({
url: '/system/user/profile/change/password',
method: 'put',
data: data
})
}
// 用户头像上传
export function uploadAvatar(data) {
return request({
@ -136,11 +183,10 @@ export function deptTreeSelect() {
})
}
// 修改密码
export function updateUserPassword(data) {
// 用户名称检查
export function getSystemUserCheck(userName) {
return request({
url: '/resetPwd',
method: 'post',
data: data
url: '/system/user/check/' + userName,
method: 'get'
})
}

View File

@ -0,0 +1,68 @@
<template>
<!-- 表格批量操作组件 -->
<div class="table-op">
<el-checkbox v-model="isAllSelection" @change="allSelectionChange" style="transform: translateY(3px);">{{
t('全选当前页')
}}</el-checkbox>
<el-select v-model="batchOpType" :disabled="selectionData.length === 0" :placeholder="t('批量操作')"
style="width: 150px; margin-left: 20px;" @change="opTypeChange">
<el-option v-for="item in opTypeOptions" :label="t(item.label)" :value="item.value" :disabled="item.disabled" />
</el-select>
<span class="selection-tips" v-if="selectionData.length > 0">{{ t('')
}}<b>{{ selectionData.length }}</b>{{ t('条数据') }}</span>
</div>
</template>
<script setup>
const props = defineProps({
selectionData: { //
type: Array,
default: []
},
opTypeOptions: { //
type: Array,
default: []
}
});
const isAllSelection = defineModel('isAllSelection', { type: Boolean, default: false }); //
const batchOpType = defineModel('batchOpType', { type: String, default: '' }); //
const emit = defineEmits(['allSelectionChange', 'opTypeChange']);
//
const allSelectionChange = () => {
emit('allSelectionChange');
}
//
const opTypeChange = () => {
emit('opTypeChange');
}
const reset = () => {
batchOpType.value = null;
emit('selectionDataNu');
}
defineExpose({
reset
})
</script>
<style scoped lang='scss'>
.table-op {
position: absolute;
left: 5px;
bottom: 10px;
z-index: 100;
.selection-tips {
font-size: 14px;
padding-left: 10px;
color: #666666;
b {
color: #F56C6C;
padding: 0 2px;
}
}
}
</style>

View File

@ -27,17 +27,19 @@
/>
</el-menu>
</el-scrollbar> -->
<div class="avatar-container">
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon>
<div class="avatar-container" style="margin-right: 10px;" >
<el-dropdown @command="handleCommand" style="" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper" style="display: flex;align-items: center;justify-content: center;color: #fff;font-size: 14px;">
<!-- <img :src="userStore.avatar" class="user-avatar" />
<el-icon><caret-bottom /></el-icon> -->
{{ nickName }}
</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 @click="handleClick">{{ t("") }}</el-dropdown-item>
<el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<span>布局设置</span>
</el-dropdown-item>
@ -48,6 +50,61 @@
</template>
</el-dropdown>
</div>
<el-dialog :title="t('账号设置')" align-center v-model="dialogVisible" width="700" append-to-body>
<el-scrollbar max-height="600px">
<el-form ref="userRef" :model="formData" :rules="rules" label-width="100px">
<el-form-item label="后台账号" prop="roleName">
{{ formData.userName }}
</el-form-item>
<el-form-item label="后台昵称" prop="nickName">
<el-input v-model="formData.nickName"
:placeholder="t('仅支持中英文和数字为1-20个字符')" maxlength="20" />
</el-form-item>
<el-form-item label="登录密码">
<el-row style="width: 100%;">
<el-col :span="12">
{{ t('已设置') }}
</el-col>
<el-col :span="12" style="display: flex;justify-content: right;">
<el-button type="primary" @click="updatePassword"></el-button>
</el-col>
</el-row>
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<div class="dialog-footer" style="display: flex;justify-content: center;">
<el-button @click="closeDialog"> </el-button>
<el-button type="primary" @click="submitForm"></el-button>
</div>
</template>
</el-dialog>
<!-- 修改密码 -->
<el-dialog :title="t('修改密码')" align-center v-model="passwordDialogVisible" width="630" append-to-body>
<el-scrollbar max-height="600px">
<el-form ref="passwordRef" :model="passwordFormData" :rules="rulesPassword" label-width="100px">
<el-form-item label="旧密码" prop="oldPassword">
<el-input v-model="passwordFormData.oldPassword" placeholder="请输入旧密码" type="password" show-password />
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="passwordFormData.newPassword" placeholder="请输入新密码" type="password" show-password />
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="passwordFormData.confirmPassword" placeholder="请确认新密码" type="password" show-password/>
</el-form-item>
<el-form-item label="谷歌验证码" prop="codeGoogle">
<el-input v-model="passwordFormData.codeGoogle" maxlength="6" placeholder="请输入谷歌验证码"/>
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<div class="dialog-footer" style="display: flex;justify-content: center;">
<el-button @click="closePasswordDialog"> </el-button>
<el-button type="primary" @click="submitPasswordForm"></el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@ -60,19 +117,49 @@ import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import useUserStore from '@/store/modules/user'
import { getLocalStorage } from "@/utils/auth";
import { updateUserProfile,getUserProfile,updateUserPwd } from "@/api/system/user";
const route = useRoute();
const appStore = useAppStore()
const userStore = useUserStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const { proxy } = getCurrentInstance()
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
const showLogo = computed(() => settingsStore.sidebarLogo);
const sideTheme = computed(() => settingsStore.sideTheme);
const theme = computed(() => settingsStore.theme);
const windowWidth = ref(window.innerWidth)
const nickName = ref(getLocalStorage('userInfo')?.nickName);
const dialogVisible = ref(false);
const rules = ref({
});
const formData = ref({
});
const oldForm = shallowRef({ });
const passwordDialogVisible = ref(false);
const passwordFormData = ref({
oldPassword: undefined,
newPassword: undefined,
confirmPassword: undefined,
codeGoogle: undefined
});
const equalToPassword = (rule, value, callback) => {
if (passwordFormData.value.newPassword !== value) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
};
const rulesPassword = ref({
oldPassword: [{ required: true, message: "旧密码不能为空", trigger: "change" }],
newPassword: [{ required: true, message: "新密码不能为空", trigger: "change" }, { min: 6, max: 20, message: "长度在 6 到 20 个字符", trigger: "change" }, { pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "change" }],
confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "change" }, { required: true, validator: equalToPassword, trigger: "change" }],
codeGoogle: [{ required: true, message: "谷歌验证码不能为空", trigger: "change" }]
});
function updateWidth() {
windowWidth.value = window.innerWidth
@ -125,6 +212,51 @@ function logout() {
})
}).catch(() => { });
}
const handleClick = () => {
getUserProfile().then(response => {
formData.value = response.data;
dialogVisible.value = true;
nextTick(() => {
oldForm.value = JSON.stringify(formData.value);
})
});
}
const closeDialog = () => {
dialogVisible.value = false;
}
//
const updatePassword = () => {
passwordDialogVisible.value = true;
}
const submitForm = () => {
proxy.$refs.userRef.validate(valid => {
if (valid) {
if (JSON.stringify(formData.value) != oldForm.value) {
updateUserProfile(formData.value).then(response => {
proxy.$modal.msgSuccess("修改成功");
nickName.value = formData.value.nickName;
dialogVisible.value = false;
});
}else{
dialogVisible.value = false;
}
}
});
}
const closePasswordDialog = () => {
passwordDialogVisible.value = false;
}
const submitPasswordForm = () => {
proxy.$refs.passwordRef.validate(valid => {
if (valid) {
updateUserPwd(passwordFormData.value.oldPassword, passwordFormData.value.newPassword,passwordFormData.value.codeGoogle).then(response => {
proxy.$modal.msgSuccess("修改成功");
passwordDialogVisible.value = false;
});
}
});
}
</script>
<style lang="scss" scoped>

View File

@ -111,7 +111,8 @@
</el-input-number>
</el-form-item>
<el-form-item :label="t('谷歌验证码')" prop="googleCode">
<el-input v-model="form.googleCode" style="width: 260px;" :placeholder="t('请输入谷歌验证码')" />
<el-input v-model="form.googleCode" :maxlength="6" style="width: 260px;" :placeholder="t('请输入谷歌验证码')" />
</el-form-item>
<!-- <div class="label-scoreRatio">
<span>{{ t('平台比例') }}</span>
@ -162,7 +163,7 @@
</el-input-number>
</el-form-item>
<el-form-item :label="t('谷歌验证码')" prop="googleCode">
<el-input v-model="form.googleCode" style="width: 260px;" :placeholder="t('请输入谷歌验证码')" />
<el-input v-model="form.googleCode" :maxlength="6" style="width: 260px;" :placeholder="t('请输入谷歌验证码')" />
</el-form-item>
<!-- <el-form-item :label="t('商户模式')" prop="tenantType">
<el-radio-group v-model="form.tenantType" :disabled="openView">

View File

@ -73,7 +73,7 @@
{{ t(' (万法定货币=1万通用额度)') }}
</el-form-item>
<el-form-item :label="t('谷歌验证码')" prop="googleCode">
<el-input v-model="form.googleCode" style="width: 260px;" :placeholder="t('请输入谷歌验证码')" />
<el-input v-model="form.googleCode" :maxlength="6" style="width: 260px;" :placeholder="t('请输入谷歌验证码')" />
</el-form-item>
<div class="label-scoreRatio">
<span>{{ t('平台比例') }}</span>

View File

@ -91,6 +91,12 @@
<el-table-column :label="t('结算时间')" align="center" min-width="160" prop="settlementTime" :show-overflow-tooltip="true">
<template #default="scope">{{ parseTime(scope.row.settlementTime) }}</template>
</el-table-column>
<el-table-column :label="t('创建时间')" align="center" min-width="160" prop="ffcreateAt" :show-overflow-tooltip="true">
<template #default="scope">{{ parseTime(scope.row.ffCreateAt) }}</template>
</el-table-column>
<el-table-column :label="t('更新时间')" align="center" min-width="160" prop="ffupdateAt" :show-overflow-tooltip="true">
<template #default="scope">{{ parseTime(scope.row.ffUpdateAt) }}</template>
</el-table-column>
<!-- <el-table-column :label="t('操作')" align="center" width="200" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="handleView(scope.row)" v-hasPermi="['agent:tenant:view']">{{ t('') }}</el-button>

View File

@ -83,7 +83,7 @@
<el-input v-model="userInfos.walletAddress" disabled auto-complete="off" placeholder="请输入钱包地址" />
</el-form-item>
<el-form-item :label="t('谷歌验证码')" prop="googleCode">
<el-input v-model="form.googleCode" :placeholder="t('请输入谷歌验证码')" />
<el-input v-model="form.googleCode" :maxlength="6" :placeholder="t('请输入谷歌验证码')" />
</el-form-item>
</el-form>
<template #footer>

View File

@ -91,7 +91,7 @@
<el-dialog v-model="goodDialogVisible" style="margin-top: 45vh !important;" :title="t('温馨提示')" width="418" :before-close="handleClose">
<div style="width: 90%;margin-bottom: 20px;">{{ t('请用手机打开客服端Google身份验证器,输入验证码') }}</div>
<div class="">
<el-input v-model="gooleCode" style="width: 90%" :placeholder="t('验证码只能为数字')" />
<el-input v-model="gooleCode" :maxlength="6" style="width: 90%" :placeholder="t('验证码只能为数字')" />
</div>
<template #footer>
<div class="dialog-footer">
@ -111,7 +111,7 @@
</div>
<el-form ref="authenticatorRef" :model="authenticatorForm" :rules="authenticatorRules">
<el-form-item label="谷歌验证码" prop="authenticatorCode">
<el-input v-model="authenticatorForm.authenticatorCode" :digit="2" style="width: 100%;height: 36px;" :placeholder="t('验证码只能为数字')" />
<el-input v-model="authenticatorForm.authenticatorCode" :maxlength="6" :digit="2" style="width: 100%;height: 36px;" :placeholder="t('验证码只能为数字')" />
</el-form-item>
</el-form>
<div class="" style="margin-top: 20px;margin-bottom: 20px;">

View File

@ -111,7 +111,7 @@
{{ t('如需扣余额,则输入负数,例如:-10.50') }}
</div>
<el-form-item :label="t('谷歌验证码')" prop="googleCode">
<el-input v-model="formAdjustment.googleCode" :placeholder="t('请输入谷歌验证码')" />
<el-input v-model="formAdjustment.googleCode" :maxlength="6" :placeholder="t('请输入谷歌验证码')" />
</el-form-item>
<el-form-item :label="t('备注')" prop="remark">
<el-input type="textarea" rows="4" v-model="formAdjustment.remark" :placeholder="t('请输入备注')" />

View File

@ -28,7 +28,7 @@
</div>
</el-form-item>
<el-form-item :label="t('谷歌验证码')" prop="googleCode">
<el-input v-model="formAdjustment.googleCode" :placeholder="t('请输入谷歌验证码')" />
<el-input v-model="formAdjustment.googleCode" :maxlength="6" :placeholder="t('请输入谷歌验证码')" />
</el-form-item>
<el-form-item :label="t('备注')" prop="remark">
<el-input type="textarea" rows="4" v-model="formAdjustment.remark" :placeholder="t('请输入备注')" />

View File

@ -296,7 +296,12 @@ currencySelectArr.value = res.map(item => {
/** 查询列表 */
function getList() {
loading.value = true;
superTenantList(queryParams.value).then(response => {
let queryParams={
pageNum: 1,
pageSize: 10,
tenantKey: "",
}
superTenantList(queryParams).then(response => {
agentList.value = response.rows;
total.value = response.total;
loading.value = false;

View File

@ -3,7 +3,7 @@
<table-search-card :leftSpan="20" :model="queryParams" @getList="getList" @handleQuery="handleQuery" @resetQuery="resetQuery">
<template #left>
<table-search-date v-model:dateRange="dateRange" v-model:operateTimeType="operateTimeType"></table-search-date>
<el-form-item :label="t('商户账号')" prop="tenantKey">
<!-- <el-form-item :label="t('商户账号')" prop="tenantKey">
<el-input
v-model="queryParams.tenantKey"
:placeholder="t('请输入商户账号')"
@ -11,6 +11,40 @@
style="width: 200px"
@keyup.enter="handleQuery"
/>
</el-form-item> -->
<el-form-item :label="t('商户账号')" prop="tenantKey">
<el-select
v-model="queryParams.tenantKey"
filterable
clearable
reserve-keyword
placeholder="请输入租户Key搜索"
:remote-method="loadOptions"
:loading="loadingSelect"
style="width: 240px"
>
<!-- 正常列表 -->
<el-option
v-for="item in agentListSelect"
:key="item.tenantKey"
:label="item.tenantKey"
:value="item.tenantKey"
/>
<!-- 加载更多按钮 -->
<el-option
v-if="hasMore && agentListSelect.length > 0"
disabled
value=""
>
<div
style="color:#409EFF;cursor:pointer;width:100%;"
@click.stop="loadMore"
>
{{ loadingMore ? "加载中..." : "加载更多" }}
</div>
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="t('玩家账号')" prop="memberAccount">
<el-input
@ -43,9 +77,9 @@
<CustomSelect v-if="showLoding3" v-model="queryParams.platformCode" :options="quotaplatformCodeArr" filterable placeholder="请选择平台" style="width: 200px" />
</el-form-item>
<el-form-item :label="t('操作类型')" prop="operationType">
<el-select v-if="operationTypeOption.length > 0" v-model="queryParams.operationType" clearable style="width:220px;" :placeholder="t('请选择')">
<el-select v-model="queryParams.operationType" clearable style="width:220px;" :placeholder="t('请选择')">
<el-option
v-for="dict in operationTypeOption"
v-for="dict in operationTypes"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -236,7 +270,7 @@
</template>
<script setup name="Agent">
import { superTenantQuotaflow,superCommonPlatSelect,superTenantQuotaAudit,getSuperCommonOperationType } from "@/api/super/tenant";
import { superTenantQuotaflow,superCommonPlatSelect,superTenantQuotaAudit,getSuperCommonOperationType,superTenantList } from "@/api/super/tenant";
import Crontab from '@/components/Crontab'
import { getLocalStorage } from "@/utils/auth";
import CopyIcon from '@/components/CopyIcon'
@ -297,6 +331,18 @@ const dd = String(today.getDate()).padStart(2, '0');
}
});
//
const operationTypes = ref([
{ label: proxy.t('商户转入转出'), value: 1 },
{ label: proxy.t('日用额度结算'), value: 2 },
{ label: proxy.t('信誉额度充值'), value: 3 },
{ label: proxy.t('后台人工操作'), value: 4 },
{ label: proxy.t('商户订单充值'), value: 5 },
{ label: proxy.t('转入恢复'), value: 6 },
{ label: proxy.t('转出恢复'), value: 7 },
]);
const { queryParams, form, rules } = toRefs(data);
const quotaTypeArr = ref([
{ label: proxy.t('可用额度'), value: "BALANCE" },
@ -461,9 +507,52 @@ const handleAudit = (row) => {
});
});
};
const agentListSelect = ref([])
const loadingSelect = ref(false)
const loadingMore = ref(false)
const hasMore = ref(true)
const queryParamSelect = ref({
pageNum: 1,
pageSize: 10,
tenantKey: ""
})
//
const loadOptions = async (query) => {
queryParamSelect.value.pageNum = 1
queryParamSelect.value.tenantKey = query
loadingSelect.value = true
try {
const res = await superTenantList(queryParamSelect.value)
agentListSelect.value = res.rows || []
hasMore.value = agentListSelect.value.length < (res.total || 0)
} finally {
loadingSelect.value = false
}
}
//
const loadMore = async () => {
if (!hasMore.value) return
queryParamSelect.value.pageNum++
loadingMore.value = true
try {
const res = await superTenantList(queryParamSelect.value)
agentListSelect.value = [...agentListSelect.value, ...(res.rows || [])]
hasMore.value = agentListSelect.value.length < (res.total || 0)
} finally {
loadingMore.value = false
}
}
//
onMounted(() => {
getList();
loadOptions('');
getsuperCommonCurrencySelect();
getSuperCommonOperationTypes();
getsuperCommonPlatformTypeSelect();

View File

@ -118,7 +118,7 @@
loading.value = true;
await passwordForm.value.validate();
let objForm = {
password:form.value.newPassword
newPassword:form.value.newPassword
}
// API
updateUserPassword(objForm).then(response => {

View File

@ -29,7 +29,7 @@
<el-table-column label="权限字符" align="center" prop="roleKey" />
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
<span>{{ formatTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>

View File

@ -26,7 +26,7 @@ onActivated(() => {
//
if (Object.keys(routeParams).length) {
activeName.value = routeParams.activeName || 'auditManage';
activeName.value = routeParams.activeName || 'userList';
memberAccount.value = routeParams.memberAccount || '';
}

View File

@ -35,7 +35,7 @@
</li>
<li class="list-group-item">
<svg-icon icon-class="date" />创建日期
<div class="pull-right">{{ state.user.createTime }}</div>
<div class="pull-right">{{ formatTime(state.user.createTime) }}</div>
</li>
</ul>
</div>

View File

@ -1,7 +1,7 @@
<template>
<div class="user-info-head" @click="editCropper()">
<img :src="options.img" title="点击上传头像" class="img-circle img-lg" />
<el-dialog :title="title" align-center v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-dialog :title="title" v-model="open" width="800px" append-to-body @opened="modalOpened" @close="closeDialog">
<el-row>
<el-col :xs="24" :md="12" :style="{ height: '350px' }">
<vue-cropper
@ -63,17 +63,19 @@ import "vue-cropper/dist/index.css";
import { VueCropper } from "vue-cropper";
import { uploadAvatar } from "@/api/system/user";
import useUserStore from "@/store/modules/user";
import { getLocalStorage } from "@/utils/auth";
const userStore = useUserStore();
const { proxy } = getCurrentInstance();
const fileHost = getLocalStorage('fileUrl') || ''; // host
const open = ref(false);
const visible = ref(false);
const title = ref("修改头像");
//
const options = reactive({
img: userStore.avatar, //
img: fileHost + userStore.avatar, //
autoCrop: true, //
autoCropWidth: 200, //
autoCropHeight: 200, //
@ -133,7 +135,7 @@ function uploadImg() {
formData.append("avatarfile", data, options.filename);
uploadAvatar(formData).then(response => {
open.value = false;
options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl;
options.img = response.imgUrl;
userStore.avatar = options.img;
proxy.$modal.msgSuccess("修改成功");
visible.value = false;
@ -148,7 +150,7 @@ function realTime(data) {
/** 关闭窗口 */
function closeDialog() {
options.img = userStore.avatar;
options.img = fileHost + userStore.avatar;
options.visible = false;
}
</script>

View File

@ -1,221 +0,0 @@
# 权限树状表格组件使用说明
## 概述
这个权限树状表格组件实现了类似表格的树形权限管理界面,左侧显示树形菜单结构,右侧显示对应的权限复选框,完全符合您截图中的设计需求。
## 文件结构
```
src/views/system/user/role/
├── render.vue # 核心权限树状表格组件
├── permission-dialog.vue # 权限选择对话框组件
├── permission-example.vue # 完整使用示例
└── README.md # 使用说明
```
## 核心功能
### 1. 树状表格展示
- 表格样式的树形结构展示
- 支持多级树形结构
- 可展开/收起节点
- 支持节点复选框选择(含半选状态)
### 2. 权限复选框管理
- 每个节点可以配置独立的权限项
- 权限项以复选框形式展示在右侧
- 支持权限项的选中状态管理
- 支持热门权限标记
### 3. 表格式布局
- 左侧:菜单名称列(支持树形缩进)
- 右侧:权限操作列(权限复选框)
- 表格头部标题
- 行分隔线和悬停效果
## 数据结构
```javascript
const treeData = [
{
id: 1, // 节点唯一标识
label: '开站管理', // 节点显示名称
children: [ // 子节点(可选)
{
id: 11,
label: '待审核',
perms: [] // 权限列表(可选)
},
{
id: 12,
label: '待付款',
perms: [ // 权限项配置
{
key: 'pay',
label: '付款',
checked: false
}
]
},
{
id: 15,
label: '已上线',
perms: [
{
key: 'add_brand',
label: '新增子品牌',
checked: false,
isHot: true // 热门权限标记
},
{
key: 'edit',
label: '修改',
checked: false
}
]
}
]
}
]
```
## 使用方法
### 1. 基础使用
```vue
<template>
<PermissionTreeTable ref="treeTableRef" />
</template>
<script setup>
import { ref } from 'vue'
import PermissionTreeTable from './render.vue'
const treeTableRef = ref()
// 获取选中的数据
const getSelectedData = () => {
if (treeTableRef.value) {
return treeTableRef.value.getCheckedData()
}
}
</script>
```
### 2. 对话框形式使用
```vue
<template>
<PermissionDialog
v-model="dialogVisible"
:role-id="currentRoleId"
@confirm="handlePermissionConfirm"
/>
</template>
<script setup>
import PermissionDialog from './permission-dialog.vue'
const dialogVisible = ref(false)
const currentRoleId = ref(1)
const handlePermissionConfirm = (data) => {
console.log('选中的权限:', data)
}
</script>
```
## 样式说明
### 1. 表格式布局
- 使用 CSS Grid 布局实现表格效果
- 左列:菜单名称(固定宽度 300px
- 右列:权限操作(自适应宽度)
- 表格头部和内容分离
### 2. 主要样式类名
- `.permission-tree-table`: 组件容器
- `.table-header`: 表格头部
- `.table-content`: 表格内容区域
- `.table-row`: 每行的容器
- `.row-left`: 左侧菜单名称区域
- `.row-right`: 右侧权限操作区域
- `.expand-icon`: 展开/收起图标
- `.hot-tag`: 热门权限标记
### 3. 核心样式
```css
.permission-tree-table {
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
}
.table-header {
display: grid;
grid-template-columns: 300px 1fr;
background: #f5f7fa;
border-bottom: 1px solid #ebeef5;
}
.table-row {
display: grid;
grid-template-columns: 300px 1fr;
border-bottom: 1px solid #ebeef5;
}
.table-row:hover {
background-color: #f5f7fa;
}
```
## API 说明
### Props
- `modelValue`: 对话框显示状态(仅对话框组件)
- `roleId`: 角色ID可选
### Events
- `confirm`: 确认选择权限时触发,返回选中的权限数据
### Methods
- `getCheckedData()`: 获取当前选中的数据
- 返回格式:
```javascript
{
checkedNodes: [ // 选中的节点
{ id: 1, label: '开站管理' }
],
checkedPermissions: { // 选中的权限
"12": [
{ key: 'pay', label: '付款' }
]
}
}
```
## 扩展功能
### 1. 权限标记
可以为特定权限添加标记(如"热门"标记):
```javascript
const isHot = perm === '新增子品牌'
// 在渲染时添加标记
h('span', { class: 'hot-tag' }, '热')
```
### 2. 权限分组
可以通过树形结构对权限进行分组管理。
### 3. 权限搜索
可以添加搜索功能来快速定位特定权限。
## 注意事项
1. 确保每个节点的 `id` 唯一
2. `perms` 数组为空时不会显示权限复选框
3. 权限状态通过 `permissionState` 响应式对象管理
4. 使用 `:deep()` 选择器来修改 Element Plus 组件的默认样式

View File

@ -70,7 +70,7 @@
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
<span>{{ formatTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
@ -91,7 +91,7 @@
</div>
</template>
<script setup name="User">
<script setup name="AuthUser">
import selectUser from "./selectUser";
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
@ -108,7 +108,7 @@ const userIds = ref([]);
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
pageSize: 20,
roleId: route.params.roleId,
userName: undefined,
phonenumber: undefined,

View File

@ -1,188 +1,163 @@
<template>
<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.roleName"
placeholder="请输入角色名称"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="权限字符" prop="roleKey">
<el-input
v-model="queryParams.roleKey"
placeholder="请输入权限字符"
clearable
style="width: 240px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="角色状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in sys_normal_disable"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</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-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="Plus"
@click="handleAdd"
v-hasPermi="['system:role:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="Edit"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:role:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="Delete"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:role:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['system:role:export']"
>导出</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<table-search-card :model="queryParams" @getList="getList" @handleQuery="handleQuery" @resetQuery="resetQuery">
<template #left>
<select-input-form ref="selectInputFormRef" :queryParamsList="queryParamsList" :queryParams="queryParams"
@handleQuery="handleQuery">
</select-input-form>
</template>
<template #right>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:role:add']"></el-button>
</template>
</table-search-card>
<el-alert :title="t('提示: 删除权限后台账号数为0时才能删除权限')" style="margin-bottom: 10px;" :closable="false" type="warning"
show-icon />
<!-- 表格数据 -->
<el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template #default="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
<el-table v-loading="loading" :data="roleList" ref="tableRef" class="c-table-main" @select="tableSelect"
@select-all="tableSelect" stripe border>
<el-table-column type="selection" width="55" :selectable="rowSelectable" align="center" />
<el-table-column label="权限名称" prop="roleName" min-width="150" align="center" >
<template #default="{ row }">
<span>{{ row.roleName }} <span v-if="row.defaultStatus == 0">()</span></span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
<!-- <el-table-column label="权限类型" prop="roleName" min-width="110" align="center" /> -->
<el-table-column label="权限适用范围" prop="roleScope" min-width="150" align="center">
<template #default="{ row }">
{{ row.roleScope || '--' }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<el-table-column label="用户列表" prop="roleSort" min-width="200">
<template #default="{ row }">
<div v-if="row.sysUsers.length > 0">
<span v-for="(item, index) in row.sysUsers.slice(0, 5)" :key="item.userId" @click="handleUserListClick(row)" style="color: rgb(64, 158, 255);cursor: pointer;">
{{ item.userName }}
<span v-if="index < Math.min(5, row.sysUsers.length) - 1">,</span>
</span>
<span v-if="row.sysUsers.length > 5"> ...</span>
</div>
<div v-else>--</div>
</template>
</el-table-column>
<el-table-column label="权限数" prop="menuCount" align="center" min-width="80">
<template #default="{ row }">
{{ row.menuCount || '--' }}
</template>
</el-table-column>
<el-table-column label="备注" prop="remark" min-width="130">
<template #default="{ row }">
{{ row.remark || '--' }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" min-width="200" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:role:remove']"></el-button>
</el-tooltip>
<el-tooltip content="数据权限" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
<el-tooltip content="分配用户" placement="top" v-if="scope.row.roleId !== 1">
<el-button link type="primary" icon="User" @click="handleAuthUser(scope.row)" v-hasPermi="['system:role:edit']"></el-button>
</el-tooltip>
<el-button link type="primary" @click="handleDetail(scope.row)" v-hasPermi="['system:role:edit']">{{ t('')
}}</el-button>
<el-button link type="primary" v-if="scope.row.defaultStatus == 1" @click="handleUpdate(scope.row)"
v-hasPermi="['system:role:edit']">{{ t('修改') }}</el-button>
<el-button link type="primary" @click="handleManage(scope.row)" v-hasPermi="['system:role:edit']">{{ t('')
}}</el-button>
<el-button link type="primary" v-if="scope.row.defaultStatus == 1" @click="handleDelete(scope.row)"
v-hasPermi="['system:role:remove']">{{ t('删除') }}</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" />
<!-- 批量操作 -->
<table-batch-operate v-if="total > 0" v-model:batchOpType="batchOpType"
v-model:isAllSelection="isAllSelection" @allSelectionChange="allSelectionChange" @opTypeChange="opTypeChange"
:selectionData="selectionData" :opTypeOptions="opTypeOptions"></table-batch-operate>
<!-- 添加或修改角色配置对话框 -->
<el-dialog :title="title" align-center v-model="open" width="860px" append-to-body>
<el-scrollbar max-height="600px">
<el-scrollbar max-height="800px">
<el-form ref="roleRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
<el-input v-model="form.roleName" :disabled="modifyStatus == 'detail' || modifyStatus == 'Manage'"
placeholder="请输入角色名称" maxlength="20" show-word-limit />
</el-form-item>
<el-form-item prop="roleKey">
<template #label>
<span>
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
权限字符
</span>
</template>
<el-input v-model="form.roleKey" placeholder="请输入权限字符" />
<el-form-item label="权限范围">
<el-select disabled v-model="form.dataScope" style="width: 350px;" @change="dataScopeSelectChange">
<el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="角色顺序" prop="roleSort">
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
<el-form-item v-if="modifyStatus == 'add'" label="复制权限">
<el-select v-model="form.copyRoleId" style="width: 350px;" clearable @change="changeMenu">
<el-option v-for="item in roleOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="角色等级" prop="roleLevel">
<NumberInput v-model="form.roleLevel" :placeholder="t('数字越高级别越高')" style="width: 200px;"></NumberInput>
<el-form-item label="备注">
<el-input v-model="form.remark" :disabled="modifyStatus == 'detail' || modifyStatus == 'Manage'"
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_normal_disable"
:key="dict.value"
:value="dict.value"
>{{ dict.label }}</el-radio>
</el-radio-group>
<el-form-item v-if="modifyStatus == 'Manage'" label="添加账号">
<el-row style="width: 100%;">
<el-col :span="16">
<div style="width: 100%;">
<checkbox-select v-if="showLoding" collapse-tags collapse-tags-tooltip style="width: 100%;" v-model="accountLinking"
:options="accountOptions" :placeholder="t('请选择账号进行权限关联')"></checkbox-select>
</div>
</el-col>
<el-col :span="8" style="display: flex;justify-content: right;">
<div style="margin-left: 10px;">
<el-button type="primary" @click="handleAddAccount">{{ t('') }}</el-button>
</div>
</el-col>
</el-row>
<el-row v-if="sysUsersArr.length > 0" style="width: 100%;margin-top: 20px;">
<el-col :span="20">
<div style="width: 100%;">
<el-input v-model="userNameText" style="width: 100%" :placeholder="t('输入关键字进行用户列表过滤,支持空格隔开的多关键字匹配')" @input="handleFilter" />
</div>
</el-col>
<el-col :span="4" style="display: flex;justify-content: right;">
<div style="margin-left: 10px;">
<el-button type="primary" @click="handleRemoveAccount">{{ t('') }}</el-button>
</div>
</el-col>
</el-row>
<div class="accounts-about-scroll-container" v-if="sysUsersArr.length > 0">
<el-tag v-for="(item,index) in form.sysUsers" :key="item.userId" closable type="info" style="margin-right: 10px;" @close="handleClose(item)">
{{ item.userName }}
</el-tag>
</div>
</el-form-item>
<el-form-item label="菜单权限">
<!-- <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">/</el-checkbox> -->
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">/</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')"></el-checkbox>
<div style="display: flex;">
<div style="font-size: 14px;font-weight: 600;">{{ t('权限设置') }}</div>
<div style="margin-left: 10px;">{{ t('权限数') }}{{ countTree }}</div>
<div @click="handleCheckedTreeExpand($event, 'menu')"
style="cursor: pointer;color: rgb(64, 158, 255);margin-left: 10px;">
<span v-if="!defaultExpandAll">{{ t('') }}</span>
<span v-if="defaultExpandAll">{{ t('') }}</span>
</div>
</div>
<div style="width: 100%;margin-top: 10px;">
<el-input v-model="filterText" style="width: 100%" :placeholder="t('输入关键字进行权限过滤,支持空格隔开的多关键字匹配')" />
</div>
<div style="margin-top: 10px;">
<el-checkbox v-model="menuNodeAll" :disabled="modifyStatus == 'Manage' || modifyStatus == 'detail'"
@change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" :disabled="modifyStatus == 'Manage' || modifyStatus == 'detail'"
@change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox>
<div class="table">
<div class="menu-list">
<el-tree ref="menuRef" :data="menuOptions" default-expand-all :expand-on-click-node='false' check-on-click-node show-checkbox node-key="id"
:check-strictly="!form.menuCheckStrictly" :props="{ label: 'label', children: 'children' }"></el-tree>
<el-tree ref="menuRef" :data="menuOptions" v-if="defaultExpandShow" :default-expand-all="defaultExpandAll"
:expand-on-click-node='false' check-on-click-node show-checkbox node-key="id"
:check-strictly="!form.menuCheckStrictly"
:props="{ label: 'label', children: 'children', disabled: (data, node) => modifyStatus == 'Manage' || modifyStatus == 'detail', }"
:filter-node-method="filterNode"></el-tree>
</div>
</div>
</div>
<!-- <el-form-item label="菜单权限">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">/</el-checkbox>
<!-- <el-tree
<el-tree
class="tree-border"
:data="menuOptions"
show-checkbox
@ -192,21 +167,47 @@
empty-text="加载中,请稍候"
:render-content="renderContent"
:props="{ label: 'label', children: 'children' }"
></el-tree> -->
></el-tree>
</el-form-item> -->
</el-form>
</el-scrollbar>
<template #footer>
<div class="dialog-footer" style="display: flex;justify-content: center;">
<el-button v-if="modifyStatus == 'detail' || modifyStatus == 'Manage'" @click="cancel"> </el-button>
<el-button v-if="modifyStatus == 'add' || modifyStatus == 'edit'" type="primary" @click="submitForm">
</el-button>
<el-button v-if="modifyStatus == 'add' || modifyStatus == 'edit'" @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<el-dialog :title="t('用户列表')" align-center v-model="openUserList" width="1050px" append-to-body>
<el-scrollbar max-height="800px">
<el-form ref="roleRef" label-width="100px">
<el-form-item label="权限名称">
{{ formUserList.roleName }}<span v-if="formUserList.defaultStatus == 0">()</span>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
<el-form-item label="用户列表">
<el-row v-if="sysUsersArr.length > 0" style="width: 100%;margin-top: 20px;">
<el-col :span="24">
<div style="width: 100%;">
<el-input v-model="userListText" style="width: 100%" :placeholder="t('输入关键字进行用户列表过滤,支持空格隔开的多关键字匹配')" @input="handleUserListFilter" />
</div>
</el-col>
</el-row>
<div class="accounts-about-scroll-container" v-if="sysUsersArr.length > 0">
<el-tag v-for="(item,index) in formUserList.sysUsers" :key="item.userId" type="info" style="margin-right: 10px;" >
{{ item.userName }}
</el-tag>
</div>
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
<div class="dialog-footer" style="display: flex;justify-content: center;">
<el-button @click="cancelUserList"> </el-button>
</div>
</template>
</el-dialog>
<!-- 分配角色数据权限对话框 -->
<el-dialog :title="title" align-center v-model="openDataScope" width="500px" append-to-body>
<el-form :model="form" label-width="80px">
@ -217,30 +218,19 @@
<el-input v-model="form.roleKey" :disabled="true" />
</el-form-item>
<el-form-item label="权限范围">
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<el-option
v-for="item in dataScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
<el-select @change="dataScopeSelectChange">
<el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="数据权限" v-show="form.dataScope == 2">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">/</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">/</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')"></el-checkbox>
<el-tree
class="tree-border"
:data="deptOptions"
show-checkbox
default-expand-all
ref="deptRef"
node-key="id"
:check-strictly="!form.deptCheckStrictly"
empty-text="加载中,请稍候"
:props="{ label: 'label', children: 'children' }"
></el-tree>
<el-checkbox v-model="form.deptCheckStrictly"
@change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox>
<el-tree class="tree-border" :data="deptOptions" show-checkbox default-expand-all ref="deptRef" node-key="id"
:check-strictly="!form.deptCheckStrictly" empty-text="加载中,请稍候"
:props="{ label: 'label', children: 'children' }"></el-tree>
</el-form-item>
</el-form>
<template #footer>
@ -253,14 +243,18 @@
</template>
<script setup name="User">
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role";
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect, optionselect, getMenu, unallocatedUserList,authUserSelectAll,authUserCancelAll,authUserCancel,authRolecancelAll } from "@/api/system/role";
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu";
import NumberInput from "@/components/NumberInput";
import Render from "./render.vue";
import TableBatchOperate from '@/components/TableBatchOperate'; //
import CheckboxSelect from "@/components/CheckboxSelect"; // /
import { formatTime } from '@/utils/ruoyi';
import SelectInputForm from '@/components/SelectInputForm';
import { nextTick, ref } from "vue";
const router = useRouter();
const { proxy } = getCurrentInstance();
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
import { getLocalStorage } from "@/utils/auth";
const roleList = ref([]);
const open = ref(false);
const loading = ref(true);
@ -280,6 +274,23 @@ const deptOptions = ref([]);
const openDataScope = ref(false);
const menuRef = ref(null);
const deptRef = ref(null);
const filterText = ref('');
const modifyStatus = ref('add');
const accountLinking = ref([]);
const accountOptions = ref([]);
const sysUsersArr = ref([]);
const formUserList = ref({});
const oldForm = shallowRef({ });
const queryParamsList = ref([{
label: proxy.t('角色名称'),
value: 'roleName',
}, {
label: proxy.t('后台账户'),
value: 'account',
}, {
label: proxy.t('后台呢称'),
value: 'nickName',
}])
/** 数据范围选项*/
const dataScopeOptions = ref([
@ -293,7 +304,8 @@ const data = reactive({
form: {},
queryParams: {
pageNum: 1,
pageSize: 10,
pageSize: 20,
searchType: 'roleName',
roleName: undefined,
roleKey: undefined,
status: undefined
@ -307,7 +319,67 @@ const data = reactive({
});
const { queryParams, form, rules } = toRefs(data);
//
const tableRef = ref(),
isAllSelection = ref(false), //
selectionData = ref([]), //
batchOpType = ref(''); //
//
const opTypeOptions = ref([
{ label: proxy.t('批量清除关联账户'), value: 'update' },
{ label: proxy.t('批量删除'), value: 'ignore' },
]);
//
const tableSelect = (val) => {
selectionData.value = val;
//
if (val.length == roleList.value.length) {
isAllSelection.value = true;
} else {
isAllSelection.value = false;
}
}
//
const allSelectionChange = () => {
tableRef.value.toggleAllSelection();
}
//
const opTypeChange = () => {
if (batchOpType.value === 'ignore') {
const params = selectionData.value.map(i => i.roleId); // id
proxy.$modal.confirm(proxy.t('确认批量删除已选的' + params.length + '条记录吗?')).then(() => {
loading.value = true;
delRole(params).then(res => {
loading.value = false;
batchOpType.value = '';
proxy.$modal.msgSuccess(proxy.t('操作成功!'));
handleQuery();
}).catch(() => {
batchOpType.value = '';
loading.value = false;
});
}).catch(() => {
batchOpType.value = '';
});
}else if (batchOpType.value === 'update') {
const params = selectionData.value.map(i => i.roleId); // id
proxy.$modal.confirm(proxy.t(`确认清除这些权限的关联账号(共${params.length}个)?`)).then(() => {
loading.value = true;
authRolecancelAll({roleIds: params}).then(res => {
loading.value = false;
batchOpType.value = '';
proxy.$modal.msgSuccess(proxy.t('操作成功!'));
handleQuery();
}).catch(() => {
batchOpType.value = '';
loading.value = false;
});
}).catch(() => {
batchOpType.value = '';
});
}
}
/** 查询角色列表 */
function getList() {
loading.value = true;
@ -318,6 +390,31 @@ function getList() {
});
}
// el-tree filter
watch(filterText, (val) => {
menuRef.value && menuRef.value.filter(val);
});
const rowSelectable = (row, index) => {
return row.defaultStatus == 0 ? false:true;
}
const openUserList = ref(false);
//
const handleUserListClick = (row) => {
openUserList.value = true;
sysUsersArr.value = row.sysUsers;
formUserList.value = row;
};
const cancelUserList = () => {
openUserList.value = false;
}
//
const filterNode = (value, data) => {
if (!value) return true;
//
const keywords = value.split(" ");
return keywords.every((kw) => data.label.includes(kw));
};
/** 搜索按钮操作 */
function handleQuery() {
queryParams.value.pageNum = 1;
@ -339,7 +436,7 @@ function handleDelete(row) {
}).then(() => {
getList();
proxy.$modal.msgSuccess("删除成功");
}).catch(() => {});
}).catch(() => { });
}
/** 导出按钮操作 */
@ -387,14 +484,115 @@ function handleCommand(command, row) {
function handleAuthUser(row) {
router.push("/system/role-auth/user/" + row.roleId);
}
const countTree = ref(0);
/** 查询菜单树结构 */
function getMenuTreeselect() {
menuTreeselect().then(response => {
countTree.value = response.count;
menuOptions.value = response.data;
});
}
const showLoding = ref(true);
/** 获取用户列表 */
const getunallocatedUserList = (roleId) => {
unallocatedUserList({roleId: roleId }).then(response => {
accountOptions.value = response.data.map(item => {
return {
...item,
label: item.userName,
value: item.userId
}
});
showLoding.value = false;
nextTick(() => {
showLoding.value = true;
})
});
}
//
const handleAddAccount = () => {
if (accountLinking.value.length == 0){
proxy.$modal.msgError(proxy.t('请选择要关联的账户'));
return;
}
proxy.$modal.confirm('请确认此操作,是否继续?').then(function () {
let obj = {
roleId:form.value.roleId,
userIds:accountLinking.value
}
return authUserSelectAll(obj);
}).then(() => {
getRole(form.value.roleId).then(response => {
form.value = response.data;
sysUsersArr.value = response.data.sysUsers;
});
accountLinking.value = [];
getunallocatedUserList(form.value.roleId);
handleQuery();
proxy.$modal.msgSuccess("关联成功");
}).catch(() => { });
}
const userNameText = ref('');
//2
const handleFilter = () => {
if (!userNameText.value.trim()) return form.value.sysUsers = sysUsersArr.value
const keywords = userNameText.value.trim().split(/\s+/) //
form.value.sysUsers = sysUsersArr.value.filter(user =>
keywords.every(k => user.userName.includes(k))
)
}
const userListText = ref('');
//
const handleUserListFilter = () => {
if (!userListText.value.trim()) return formUserList.value.sysUsers = sysUsersArr.value
const keywords = userListText.value.trim().split(/\s+/) //
formUserList.value.sysUsers = sysUsersArr.value.filter(user =>
keywords.every(k => user.userName.includes(k))
)
}
//
const handleRemoveAccount = () => {
if (form.value.sysUsers.length == 0){
proxy.$modal.msgError("请选择要移出的账户");
return
}
proxy.$modal.confirm('请确认此操作,是否继续?').then(function () {
let userIds = form.value.sysUsers.map(item => item.userId);
let obj = {
roleId:form.value.roleId,
userIds:userIds
}
return authUserCancelAll(obj);
}).then(() => {
getRole(form.value.roleId).then(response => {
form.value = response.data;
sysUsersArr.value = response.data.sysUsers;
});
handleQuery();
getunallocatedUserList(form.value.roleId);
proxy.$modal.msgSuccess("移出成功");
}).catch(() => { });
}
//
const handleClose = (row) => {
proxy.$modal.confirm('请确认此操作,是否继续?').then(function () {
let userIds = form.value.sysUsers.map(item => item.userId);
let obj = {
roleId:form.value.roleId,
userId:row.userId
}
return authUserCancel(obj);
}).then(() => {
getRole(form.value.roleId).then(response => {
form.value = response.data;
sysUsersArr.value = response.data.sysUsers;
});
handleQuery();
getunallocatedUserList(form.value.roleId);
proxy.$modal.msgSuccess("移出成功");
}).catch(() => { });
}
/** 所有部门节点数据 */
function getDeptAllCheckedKeys() {
//
@ -428,15 +626,94 @@ function reset() {
};
proxy.resetForm("roleRef");
}
const roleOptions = ref([]);
/** 添加角色 */
function handleAdd() {
reset();
getMenuTreeselect();
open.value = true;
title.value = "添加角色";
title.value = "新增角色";
modifyStatus.value = 'add';
form.value.dataScope = getLocalStorage('userInfo')?.nickName;
optionselect({}).then(response => {
roleOptions.value = response.data.map(item => {
return {
...item,
label: item.roleName,
value: item.roleId
}
});
})
}
//
const handleDetail = (row) => {
reset();
const roleId = row.roleId || ids.value;
const roleMenu = getRoleMenuTreeselect(roleId);
getRole(roleId).then(response => {
form.value = response.data;
form.value.roleSort = Number(form.value.roleSort);
open.value = true;
nextTick(() => {
roleMenu.then((res) => {
let checkedKeys = res.checkedKeys;
checkedKeys.forEach((v) => {
nextTick(() => {
menuRef.value.setChecked(v, true, false);
});
});
});
});
form.value.dataScope = response.data.roleScope;
modifyStatus.value = 'detail';
title.value = "角色详情";
});
}
//
const handleManage = (row) => {
reset();
const roleId = row.roleId || ids.value;
const roleMenu = getRoleMenuTreeselect(roleId);
getunallocatedUserList(roleId);
getRole(roleId).then(response => {
form.value = response.data;
sysUsersArr.value = response.data.sysUsers;
form.value.roleSort = Number(form.value.roleSort);
open.value = true;
nextTick(() => {
roleMenu.then((res) => {
let checkedKeys = res.checkedKeys;
checkedKeys.forEach((v) => {
nextTick(() => {
menuRef.value.setChecked(v, true, false);
});
});
});
});
form.value.dataScope = response.data.roleScope;
modifyStatus.value = 'Manage';
title.value = "角色管理";
form.value.roleId = row.roleId;
});
}
//
const changeMenu = (roleId) => {
if (roleId) {
getMenu(roleId).then(response => {
let checkedKeys = response.data;
checkedKeys.forEach((v) => {
nextTick(() => {
menuRef.value.setChecked(v, true, false);
});
});
});
} else {
menuRef.value.setCheckedKeys([]);
}
}
/** 修改角色 */
function handleUpdate(row) {
reset();
@ -456,6 +733,13 @@ function handleUpdate(row) {
});
});
});
setTimeout(() => {
form.value.menuIds = getMenuAllCheckedKeys();
oldForm.value = JSON.stringify(form.value);
}, 600);
form.value.dataScope = response.data.roleScope;
modifyStatus.value = 'edit';
title.value = "修改角色";
});
}
@ -475,14 +759,25 @@ function getDeptTree(roleId) {
return response;
});
}
const defaultExpandAll = ref(false);
const defaultExpandShow = ref(true);
/** 树权限(展开/折叠)*/
function handleCheckedTreeExpand(value, type) {
if (type == "menu") {
let treeList = menuOptions.value;
for (let i = 0; i < treeList.length; i++) {
menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
// let treeList = menuOptions.value;
// for (let i = 0; i < treeList.length; i++) {
// menuRef.value.store.nodesMap[treeList[i].id].expanded = value;
// }
if (!defaultExpandAll.value) {
defaultExpandAll.value = true;
} else {
defaultExpandAll.value = false;
}
defaultExpandShow.value = false;
nextTick(() => {
defaultExpandShow.value = true;
});
} else if (type == "dept") {
let treeList = deptOptions.value;
for (let i = 0; i < treeList.length; i++) {
@ -525,14 +820,32 @@ function submitForm() {
if (valid) {
if (form.value.roleId != undefined) {
form.value.menuIds = getMenuAllCheckedKeys();
updateRole(form.value).then(response => {
if (JSON.stringify(form.value) != oldForm.value) {
let params = {
menuIds: form.value.menuIds,
roleName: form.value.roleName,
remark: form.value.remark,
roleSort: form.value.roleSort,
roleId: form.value.roleId,
};
updateRole(params).then(response => {
proxy.$modal.msgSuccess("修改成功");
open.value = false;
getList();
});
}else{
open.value = false;
}
} else {
form.value.menuIds = getMenuAllCheckedKeys();
addRole(form.value).then(response => {
let params = {
menuIds: form.value.menuIds,
roleName: form.value.roleName,
remark: form.value.remark,
roleSort: form.value.roleSort,
};
addRole(params).then(response => {
proxy.$modal.msgSuccess("新增成功");
open.value = false;
getList();
@ -592,8 +905,10 @@ function cancelDataScope() {
openDataScope.value = false;
reset();
}
getList();
//
onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
.custom-node-row {
@ -622,6 +937,7 @@ getList();
width: 240px;
color: #aaa;
}
.table {
// padding: 20px;
// min-height: 88vh;
@ -629,39 +945,51 @@ getList();
width: 100%;
// margin: 20px;
}
.menu-list {
::v-deep .el-tree {
border-top: 1px solid #ebeef5;
border-left: 1px solid #ebeef5;
border-right: 1px solid #ebeef5;
.el-tree-node__expand-icon {
// display: none !important;
}
.el-tree-node {
&.is-expanded,
&.is-current,
&.is-focusable {
background-color: transparent !important;
}
.el-tree-node__content {
background-color: transparent !important;
&:hover {
background-color: transparent !important;
}
}
}
> .el-tree-node {
>.el-tree-node {
display: flex;
align-items: center;
border-bottom: 1px solid #ebeef5;
position: relative;
> .el-tree-node__content {
padding-top: 10px;
padding-bottom: 10px;
>.el-tree-node__content {
width: 300px;
height: 100%;
padding-left: 20px !important;
}
.el-tree-node__children {
position: relative;
&::after {
content: ' ';
display: block;
@ -672,6 +1000,7 @@ getList();
left: 0px;
top: 0px;
}
.el-tree-node {
// width: 350px;
padding: 10px 0px;
@ -683,4 +1012,16 @@ getList();
}
}
}
.accounts-about-scroll-container {
align-items: flex-start;
content-visibility: auto;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
margin-top: 10px;
max-height: 100px;
overflow: auto;
width: 100%;
}
</style>

View File

@ -1,78 +0,0 @@
<template>
<el-dialog
v-model="dialogVisible"
align-center
title="新增权限"
width="800px"
:before-close="handleClose"
>
<div class="permission-dialog">
<PermissionTreeTable ref="treeTableRef" />
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCancel"></el-button>
<el-button type="primary" @click="handleConfirm"></el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, computed } from 'vue'
import PermissionTreeTable from './render.vue'
const props = defineProps({
modelValue: {
type: Boolean,
default: false
},
roleId: {
type: [String, Number],
default: null
}
})
const emit = defineEmits(['update:modelValue', 'confirm'])
const treeTableRef = ref()
const dialogVisible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
//
const getSelectedPermissions = () => {
if (treeTableRef.value) {
return treeTableRef.value.getCheckedData()
}
return { checkedNodes: [], checkedPermissions: {} }
}
const handleClose = () => {
dialogVisible.value = false
}
const handleCancel = () => {
dialogVisible.value = false
}
const handleConfirm = () => {
const selectedData = getSelectedPermissions()
emit('confirm', selectedData)
dialogVisible.value = false
}
</script>
<style scoped>
.permission-dialog {
max-height: 500px;
overflow-y: auto;
}
.dialog-footer {
text-align: right;
}
</style>

View File

@ -1,195 +0,0 @@
<template>
<div class="permission-example">
<div class="header">
<h2>权限树使用示例</h2>
<el-button type="primary" @click="showPermissionDialog">
新增权限
</el-button>
</div>
<!-- 权限对话框 -->
<PermissionDialog
v-model="dialogVisible"
:role-id="currentRoleId"
@confirm="handlePermissionConfirm"
/>
<!-- 直接展示权限树状表格 -->
<div class="tree-container">
<h3>权限树状表格展示</h3>
<PermissionTreeTable ref="treeTableRef" />
<div class="action-buttons">
<el-button @click="getSelectedData"></el-button>
<el-button type="primary" @click="savePermissions"></el-button>
</div>
</div>
<!-- 已选权限展示 -->
<div v-if="selectedData" class="selected-permissions">
<h3>已选择的权限</h3>
<div class="permission-summary">
<div class="section">
<h4>选中的节点</h4>
<div class="node-list">
<el-tag
v-for="node in selectedData.checkedNodes"
:key="node.id"
size="small"
class="node-tag"
>
{{ node.label }}
</el-tag>
</div>
</div>
<div class="section">
<h4>选中的权限</h4>
<div v-for="(perms, nodeId) in selectedData.checkedPermissions" :key="nodeId" class="node-permissions">
<strong>节点 {{ nodeId }}:</strong>
<el-tag
v-for="perm in perms"
:key="perm.key"
size="small"
type="success"
class="perm-tag"
>
{{ perm.label }}
</el-tag>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import PermissionDialog from './permission-dialog.vue'
import PermissionTreeTable from './render.vue'
const dialogVisible = ref(false)
const currentRoleId = ref(1)
const selectedData = ref(null)
const treeTableRef = ref()
const showPermissionDialog = () => {
dialogVisible.value = true
}
const handlePermissionConfirm = (data) => {
console.log('确认选择的权限:', data)
selectedData.value = data
// API
// await saveRolePermissions(currentRoleId.value, data)
}
const getSelectedData = () => {
if (treeTableRef.value) {
const data = treeTableRef.value.getCheckedData()
selectedData.value = data
console.log('当前选中的数据:', data)
}
}
const savePermissions = async () => {
const data = getSelectedData()
if (data) {
try {
// API
// await saveRolePermissions(currentRoleId.value, data)
console.log('保存权限成功:', data)
//
// ElMessage.success('')
} catch (error) {
console.error('保存权限失败:', error)
// ElMessage.error('')
}
}
}
</script>
<style scoped>
.permission-example {
padding: 20px;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.tree-container {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #e4e7ed;
border-radius: 4px;
}
.action-buttons {
margin-top: 20px;
text-align: right;
}
.action-buttons .el-button {
margin-left: 10px;
}
.selected-permissions {
margin-top: 20px;
padding: 20px;
background: #f9f9f9;
border-radius: 4px;
}
.permission-summary {
margin-top: 15px;
}
.section {
margin-bottom: 20px;
}
.section h4 {
margin: 0 0 10px 0;
color: #303133;
font-size: 14px;
font-weight: 500;
}
.node-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.node-permissions {
margin-bottom: 15px;
padding: 10px;
background: white;
border-radius: 4px;
border: 1px solid #e4e7ed;
}
.node-permissions strong {
display: block;
margin-bottom: 8px;
color: #303133;
font-size: 13px;
}
.node-tag,
.perm-tag {
margin-right: 8px;
margin-bottom: 4px;
}
.node-tag {
background-color: #409eff;
border-color: #409eff;
}
</style>

View File

@ -1,588 +0,0 @@
<template>
<div class="table">
<!-- default-expand-all 默认展开全部数据 -->
<!-- expand-on-click-node 只有点击箭头才会收缩节点 -->
<!-- check-on-click-node 点击文本选中 -->
<!-- show-checkbox 复选框 -->
<div class="menu-list">
<el-tree ref="tree" :data="list" default-expand-all :expand-on-click-node='false' check-on-click-node show-checkbox node-key="menuId" :props="defaultProps"></el-tree>
</div>
<el-button @click="getCheckedKeys"> key </el-button>
</div>
</template>
<script>
export default {
data () {
return {
list: [
{
"menuId": 1,
"menuName": "首页(首页)",
"menuCode": "首页",
"parentId": 0,
"orderNum": 0,
"path": "/page",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "iconfont icon-shouye",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
"children": [
{
"menuId": 2,
"menuName": "首页(首页)",
"menuCode": "首页",
"parentId": 1,
"orderNum": 1,
"path": "/welcome",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
}
]
},
{
"menuId": 3,
"menuName": "随访数据查询(随访数据查询)",
"menuCode": "随访数据查询",
"parentId": 0,
"orderNum": 1,
"path": "/followUpDataQuery",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "iconfont icon-shengwuxuejiance",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
"children": [
{
"menuId": 4,
"menuName": "随访数据查询(随访数据查询)",
"menuCode": "随访数据查询",
"parentId": 3,
"orderNum": 1,
"path": "index",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
},
{
"menuId": 5,
"menuName": "随访详情(随访详情)",
"menuCode": "随访详情",
"parentId": 3,
"orderNum": 2,
"path": "detail",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
}
]
},
{
"menuId": 21,
"menuName": "此单名称(11)",
"menuCode": "11",
"parentId": 0,
"orderNum": 1,
"path": "请问请问饿```",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "321",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
// "children": null
},
{
"menuId": 6,
"menuName": "调查数据查询(调查数据查询)",
"menuCode": "调查数据查询",
"parentId": 0,
"orderNum": 2,
"path": "/surveyDataQuery",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "iconfont icon-shaichaxinxichaxun",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
"children": [
{
"menuId": 7,
"menuName": "调查数据查询(调查数据查询)",
"menuCode": "调查数据查询",
"parentId": 6,
"orderNum": 1,
"path": "index",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
},
{
"menuId": 8,
"menuName": "新建问卷(新建问卷)",
"menuCode": "新建问卷",
"parentId": 6,
"orderNum": 2,
"path": "evaluationreport",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
},
{
"menuId": 9,
"menuName": "随访详情(随访详情)",
"menuCode": "随访详情",
"parentId": 6,
"orderNum": 3,
"path": "evaluationlook",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
}
]
},
{
"menuId": 10,
"menuName": "任务配置(任务配置)",
"menuCode": "任务配置",
"parentId": 0,
"orderNum": 3,
"path": "/taskSet",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "iconfont icon-renwuguanli",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
"children": [
{
"menuId": 11,
"menuName": "任务配置(任务配置)",
"menuCode": "任务配置",
"parentId": 10,
"orderNum": 1,
"path": "index",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
}
]
},
{
"menuId": 12,
"menuName": "数据统计(数据统计)",
"menuCode": "数据统计",
"parentId": 0,
"orderNum": 4,
"path": "/dataStatistics",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "iconfont icon-shujutongji",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
"children": [
{
"menuId": 13,
"menuName": "数据统计(数据统计)",
"menuCode": "数据统计",
"parentId": 12,
"orderNum": 1,
"path": "index",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
}
]
},
{
"menuId": 14,
"menuName": "数据导出(数据导出)",
"menuCode": "数据导出",
"parentId": 0,
"orderNum": 5,
"path": "/dataExport",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "iconfont icon-suifangrenwuguanli",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
"children": [
{
"menuId": 15,
"menuName": "数据导出(数据导出)",
"menuCode": "数据导出",
"parentId": 14,
"orderNum": 1,
"path": "index",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
}
]
},
{
"menuId": 16,
"menuName": "系统管理(系统管理)",
"menuCode": "系统管理",
"parentId": 0,
"orderNum": 6,
"path": "/systemManage",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 0,
"visible": 1,
"perms": null,
"icon": "iconfont icon-xitongguanli",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 1,
"children": [
{
"menuId": 17,
"menuName": "用户管理(用户管理)",
"menuCode": "用户管理",
"parentId": 16,
"orderNum": 1,
"path": "usersManage",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
},
{
"menuId": 18,
"menuName": "角色管理(角色管理)",
"menuCode": "角色管理",
"parentId": 16,
"orderNum": 2,
"path": "roleManage",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
},
{
"menuId": 19,
"menuName": "权限配置(权限配置)",
"menuCode": "权限配置",
"parentId": 16,
"orderNum": 3,
"path": "permissionSetting",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
},
{
"menuId": 20,
"menuName": "菜单管理(菜单管理)",
"menuCode": "菜单管理",
"parentId": 16,
"orderNum": 4,
"path": "menuManage",
"component": null,
"query": null,
"isFrame": 0,
"isCache": 0,
"menuType": 1,
"visible": 1,
"perms": null,
"icon": "",
"status": 1,
"remark": null,
"roleId": null,
"selected": true,
"level": 2,
"children": null
}
]
}
],
defaultProps: {
children: 'children', //
label: 'menuName' //
}
}
},
mounted () {
//
this.setCheckedKeys()
},
methods: {
//
getCheckedKeys () {
// key
const childMenu = this.$refs.tree.getCheckedKeys()
// key
const partMenu = this.$refs.tree.getHalfCheckedKeys()
//
const menu = partMenu.concat(childMenu)
console.log(menu)
},
//
setCheckedKeys () {
//
// default-expand-all使,
// const nodesMap = this.$refs.tree.store.nodesMap
// const keys = Object.keys(nodesMap)
// keys.forEach(key => {
// nodesMap[key].expanded = true
// })
//
this.$nextTick(() => {
this.$refs.tree.setCheckedKeys([3])
})
},
}
}
</script>
<style scoped lang="scss">
.table {
padding: 20px;
min-height: 88vh;
background: #fff;
margin: 20px;
}
.menu-list {
::v-deep .el-tree {
border-top: 1px solid #ebeef5;
border-left: 1px solid #ebeef5;
border-right: 1px solid #ebeef5;
.el-tree-node__expand-icon {
display: none !important;
}
.el-tree-node {
&.is-expanded,
&.is-current,
&.is-focusable {
background-color: transparent !important;
}
.el-tree-node__content {
background-color: transparent !important;
&:hover {
background-color: transparent !important;
}
}
}
> .el-tree-node {
display: flex;
align-items: center;
border-bottom: 1px solid #ebeef5;
position: relative;
> .el-tree-node__content {
width: 300px;
height: 100%;
padding-left: 20px !important;
}
.el-tree-node__children {
position: relative;
&::after {
content: ' ';
display: block;
width: 1px;
height: 100%;
border-left: 1px solid #ebeef5;
position: absolute;
left: 0px;
top: 0px;
}
.el-tree-node {
width: 350px;
padding: 10px 0px;
}
}
}
}
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<!-- 授权用户 -->
<el-dialog title="选择用户" align-center v-model="visible" width="800px" top="5vh" append-to-body>
<el-dialog title="选择用户" v-model="visible" width="800px" top="5vh" append-to-body>
<el-form :model="queryParams" ref="queryRef" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input
@ -39,7 +39,7 @@
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
<span>{{ formatTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
@ -60,7 +60,7 @@
</el-dialog>
</template>
<script setup name="User">
<script setup name="SelectUser">
import { authUserSelectAll, unallocatedUserList } from "@/api/system/role";
const props = defineProps({
@ -79,7 +79,7 @@ const userIds = ref([]);
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
pageSize: 20,
roleId: undefined,
userName: undefined,
phonenumber: undefined

View File

@ -1,132 +0,0 @@
<template>
<div class="test-page">
<el-card>
<template #header>
<div class="card-header">
<span>权限树状表格测试页面</span>
</div>
</template>
<!-- 权限树状表格 -->
<PermissionTreeTable ref="treeTableRef" />
<!-- 操作按钮 -->
<div class="actions">
<el-button @click="getSelectedData"></el-button>
<el-button type="primary" @click="showDialog"></el-button>
<el-button type="success" @click="setDefaultData"></el-button>
<el-button type="warning" @click="clearAll"></el-button>
</div>
<!-- 结果展示 -->
<div v-if="selectedData" class="result">
<h3>选中结果</h3>
<el-alert
title="数据获取成功"
type="success"
:closable="false"
show-icon
/>
<pre>{{ JSON.stringify(selectedData, null, 2) }}</pre>
</div>
</el-card>
<!-- 对话框测试 -->
<PermissionDialog
v-model="dialogVisible"
@confirm="handleDialogConfirm"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import PermissionTreeTable from './render.vue'
import PermissionDialog from './permission-dialog.vue'
const treeTableRef = ref()
const dialogVisible = ref(false)
const selectedData = ref(null)
//
const getSelectedData = () => {
if (treeTableRef.value) {
const data = treeTableRef.value.getCheckedData()
selectedData.value = data
console.log('选中的数据:', data)
}
}
//
const showDialog = () => {
dialogVisible.value = true
}
//
const handleDialogConfirm = (data) => {
selectedData.value = data
console.log('对话框确认的数据:', data)
}
//
const setDefaultData = () => {
//
console.log('设置默认选中数据')
//
// 使props
}
//
const clearAll = () => {
//
console.log('清空所有选中')
selectedData.value = null
}
</script>
<style scoped>
.test-page {
padding: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.actions {
margin: 20px 0;
text-align: center;
}
.actions .el-button {
margin: 0 5px;
}
.result {
margin-top: 20px;
padding: 20px;
background: #f9f9f9;
border-radius: 4px;
}
.result h3 {
margin-top: 0;
color: #303133;
}
.result pre {
background: #fff;
padding: 15px;
border-radius: 4px;
border: 1px solid #e4e7ed;
overflow-x: auto;
font-size: 12px;
line-height: 1.5;
}
.el-alert {
margin-bottom: 15px;
}
</style>

View File

@ -0,0 +1,122 @@
<template>
<!-- 账户详情 -->
<el-dialog :title="t('账户详情')" align-center v-model="showDialog" width="700px" append-to-body
class="el-dialog">
<el-scrollbar max-height="700px">
<el-descriptions :column="1" border class="c-descriptions">
<el-descriptions-item :label="t('所属部门')" label-width="150">
{{ modifyDate.dept?.deptName||'--' }}
</el-descriptions-item>
<el-descriptions-item :label="t('后台账号')" label-width="150" >
{{ modifyDate.userName }}
</el-descriptions-item>
<el-descriptions-item :label="t('后台昵称')" label-width="150">
{{ modifyDate.nickName }}
</el-descriptions-item>
<el-descriptions-item :label="t('权限名称')" label-width="150">
{{ modifyDate.roleName }}
</el-descriptions-item>
<el-descriptions-item :label="t('创建人')" label-width="150">
{{ modifyDate.updateBy||modifyDate.createBy }}
</el-descriptions-item>
<el-descriptions-item :label="t('创建时间')" label-width="150">
{{ formatTime(modifyDate.createTime) }}
</el-descriptions-item>
<el-descriptions-item :label="t('最后登录方式')" label-width="150">
{{ t('密码登录') }}
</el-descriptions-item>
<el-descriptions-item :label="t('最后登录时间')" label-width="150">
{{ formatTime(modifyDate.loginDate) || '--' }}
</el-descriptions-item>
<el-descriptions-item :label="t('最后登出时间')" label-width="150">
{{ formatTime(modifyDate.exitDate) || '--' }}
</el-descriptions-item>
<el-descriptions-item :label="t('最后登入IP')" label-width="150">
{{ modifyDate.loginIp || '--' }}
</el-descriptions-item>
</el-descriptions>
</el-scrollbar>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeDialog">{{ t(' ') }}</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { nextTick, ref } from "vue";
import { formatTime } from '@/utils/ruoyi';
const { proxy } = getCurrentInstance()
const emits = defineEmits(['submit', 'update:show']) //
const props = defineProps({ //
show: {
type: Boolean,
default: false
},
modifyDate: {
type: Object,
default: {}
}
})
const showDialog = computed({//
get() {
return props.show
},
set(value) {
emits('update:show', value)
}
})
const formAll = reactive({ //
id: '',
rewardAmount:'',
auditRate:'',
remark:'',
password:''
})
//
nextTick(() => {
formAll.id = props.modifyDate.id;
})
//
onMounted(() => {
});
//
const closeDialog = () => {
showDialog.value = false
}
</script>
<style scope lang="scss">
.icon-box {
display: flex;
align-items: center;
gap: 20px;
}
.disable-click {
pointer-events: none;
}
.clt-item {
.label-box {
vertical-align: middle;
width: 150px;
text-align: right;
padding-right: 12px;
word-break: break-all;
line-height: 20px;
box-sizing: border-box;
}
:deep(.el-form-item__error) {
padding-left: 150px;
}
.item-right {
width: calc(100% - 150px);
}
}
</style>

View File

@ -0,0 +1,101 @@
<template>
<!-- Google验证码 -->
<el-dialog v-model="showDialog" align-center width="500px" append-to-body
class="el-dialog">
<template #title>
<div class="title" style="width: 100%;text-align: center;">Google验证码</div>
</template>
<el-form :model="formGoogle" :rules="rulesGoogle" ref="googleRef" label-width="130px">
<el-form-item label="Google验证码" prop="googleCode">
<el-input v-model="formGoogle.googleCode" placeholder="请输入您的Google验证码" maxlength="6" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer" style="display: flex;justify-content: center;">
<el-button @click="showDialog = false">取消</el-button>
<el-button type="primary" @click="submitGoogle">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { nextTick, ref } from "vue";
const { proxy } = getCurrentInstance()
const emits = defineEmits(['submit', 'update:show']) //
const props = defineProps({ //
show: {
type: Boolean,
default: false
},
modifyDate: {
type: Object,
default: {}
}
})
const showDialog = computed({//
get() {
return props.show
},
set(value) {
emits('update:show', value)
}
})
const rulesGoogle = reactive({
googleCode: [{ required: true, message: "请输入谷歌验证码", trigger: "change" }]
});
const formGoogle = reactive({
googleCode: ''
});
//
nextTick(() => {
})
const submitGoogle = () => {
proxy.$refs["googleRef"].validate(valid => {
if (valid) {
emits('submit', formGoogle.googleCode)
}
})
}
//
onMounted(() => {
});
//
const closeDialog = () => {
showDialog.value = false
}
</script>
<style scope lang="scss">
.icon-box {
display: flex;
align-items: center;
gap: 20px;
}
.disable-click {
pointer-events: none;
}
.clt-item {
.label-box {
vertical-align: middle;
width: 150px;
text-align: right;
padding-right: 12px;
word-break: break-all;
line-height: 20px;
box-sizing: border-box;
}
:deep(.el-form-item__error) {
padding-left: 150px;
}
.item-right {
width: calc(100% - 150px);
}
}
</style>

View File

@ -0,0 +1,44 @@
<template>
<div>
<div style="margin-bottom: 10px;">
<span>批量数量</span>
<b>{{ batchCount }}</b>
</div>
<el-form :model="form">
<el-form-item label="操作类型">
<el-select v-model="form.actionType" placeholder="请选择操作类型" style="width: 300px">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { reactive } from "vue";
const props = defineProps({
batchCount: {
type: Number,
required: true
},
options: {
type: Array,
default: () => []
}
});
const form = reactive({
actionType: ""
});
//
defineExpose({ form });
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long