项目初始化。

This commit is contained in:
tianyongbao
2026-02-04 17:14:16 +08:00
parent f6a89c632d
commit 016996b5d5
715 changed files with 142166 additions and 71 deletions

View File

@@ -0,0 +1,524 @@
<template>
<view class="container" style="paddingBottom:1rpx;">
<u-navbar
leftIconSize="40rpx"
leftIconColor="#333333"
:title="title"
>
</u-navbar>
<view class="section">
<view class="section-title">{{ title }}</view>
<view class="form-view">
<u--form labelPosition="left" :model="form" :rules="rules" ref="uForm" label-width="auto"
:labelStyle="{ color: '#333333', fontSize: '30rpx' }">
<u-form-item label="角色名称" prop="roleName" required>
<u--input v-model="form.roleName" placeholder="请输入角色名称" maxlength="30"
inputAlign="right" border="none"></u--input>
</u-form-item>
<u-form-item label="权限字符" prop="roleKey" required>
<u--input v-model="form.roleKey" placeholder="请输入权限字符" maxlength="100"
inputAlign="right" border="none"></u--input>
</u-form-item>
<u-form-item label="显示顺序" required>
<u-number-box v-model="form.roleSort" :min="0" :max="999" inputAlign="right"></u-number-box>
</u-form-item>
<u-form-item label="状态" prop="statusName" @click="showStatusPicker = true">
<u--input v-model="statusName" disabled disabledColor="#ffffff" placeholder="请选择状态"
inputAlign="right" border="none"></u--input>
<template #right>
<u-icon name="arrow-down"></u-icon>
</template>
</u-form-item>
<u-form-item label="备注" prop="remark" labelPosition="top">
<u--textarea v-model="form.remark" placeholder="请输入备注" border="none" autoHeight inputAlign="right" count
maxlength="500" style="padding:18rpx 0;"></u--textarea>
</u-form-item>
<u-form-item label="菜单权限" @click="openMenuPicker">
<u--input :value="menuPermissionText" disabled disabledColor="#ffffff" placeholder="请选择菜单权限"
inputAlign="right" border="none"></u--input>
<template #right>
<u-icon name="arrow-down"></u-icon>
</template>
</u-form-item>
<!-- 已选菜单标签展示 -->
<view v-if="selectedMenuNames.length > 0" class="selected-menus">
<view class="selected-title">已选择的菜单权限</view>
<view class="menu-tags">
<view v-for="name in selectedMenuNames" :key="name" class="menu-tag">
{{ name }}
</view>
</view>
</view>
</u--form>
<view class="form-btn">
<u-button type="primary" text="提交" @click="submit"></u-button>
</view>
</view>
</view>
<!-- 状态选择器 -->
<u-picker itemHeight="88" :show="showStatusPicker" :columns="statusList" keyName="dictLabel"
@cancel="showStatusPicker = false" @confirm="handleStatusConfirm"></u-picker>
<!-- 菜单权限选择弹窗 -->
<u-popup :show="showMenuPicker" mode="bottom" :round="20" @close="showMenuPicker = false">
<view class="menu-popup">
<view class="popup-header">
<text class="header-title">选择菜单权限</text>
<text class="header-count">已选择 {{ selectedMenuCount }} </text>
</view>
<scroll-view class="popup-content" scroll-y>
<view class="menu-tree">
<u-checkbox-group v-model="tempSelectedMenuIds">
<view v-for="menu in menuOptions" :key="menu.id" class="menu-group">
<!-- 一级菜单 -->
<view class="level-1-wrapper">
<u-checkbox
:name="menu.id"
:label="menu.label"
shape="square"
activeColor="#667eea"
labelSize="32rpx"
></u-checkbox>
</view>
<!-- 二级菜单 -->
<view v-if="menu.children && menu.children.length > 0" class="level-2-list">
<view v-for="child in menu.children" :key="child.id" class="level-2-item">
<u-checkbox
:name="child.id"
:label="child.label"
shape="square"
activeColor="#667eea"
labelSize="28rpx"
></u-checkbox>
<!-- 三级菜单 -->
<view v-if="child.children && child.children.length > 0" class="level-3-list">
<view v-for="btn in child.children" :key="btn.id" class="level-3-item">
<u-checkbox
:name="btn.id"
:label="btn.label"
shape="square"
activeColor="#667eea"
labelSize="26rpx"
></u-checkbox>
</view>
</view>
</view>
</view>
</view>
</u-checkbox-group>
</view>
</scroll-view>
<view class="popup-footer">
<view class="footer-btn cancel-btn" @click="cancelMenuSelect">取消</view>
<view class="footer-btn confirm-btn" @click="confirmMenuSelect">确定</view>
</view>
</view>
</u-popup>
</view>
</template>
<script setup>
import { ref, reactive, getCurrentInstance, computed } from 'vue'
import { getRole, addRole, updateRole } from '@/api/system/role'
import { treeselect, roleMenuTreeselect } from '@/api/system/menu'
import { getDicts } from "@/api/system/dict/data"
import { onLoad } from "@dcloudio/uni-app"
const { proxy } = getCurrentInstance()
const title = ref('添加角色')
const statusList = ref([])
const showStatusPicker = ref(false)
const statusName = ref('正常')
const menuOptions = ref([])
const selectedMenuIds = ref([])
const showMenuPicker = ref(false)
const tempSelectedMenuIds = ref([])
const form = reactive({
roleId: undefined,
roleName: undefined,
roleKey: undefined,
roleSort: 0,
status: '0',
menuIds: [],
remark: undefined
})
const selectedMenuCount = computed(() => {
return tempSelectedMenuIds.value.length
})
const menuPermissionText = computed(() => {
const count = selectedMenuIds.value.length
return count > 0 ? `已选择 ${count} 个菜单` : ''
})
const selectedMenuNames = computed(() => {
const names = []
const getNames = (menus) => {
menus.forEach(menu => {
if (selectedMenuIds.value.includes(menu.id)) {
names.push(menu.label)
}
if (menu.children && menu.children.length > 0) {
getNames(menu.children)
}
})
}
getNames(menuOptions.value)
return names
})
const rules = {
roleName: [
{ required: true, message: '角色名称不能为空', trigger: ['blur', 'change'] }
],
roleKey: [
{ required: true, message: '权限字符不能为空', trigger: ['blur', 'change'] }
],
roleSort: [
{ required: true, message: '显示顺序不能为空', trigger: ['blur', 'change'] }
]
}
onLoad((option) => {
getDicts('sys_normal_disable').then(res => {
statusList.value = [res.data]
})
// 获取菜单树
getMenuTree()
if (option.id) {
title.value = '修改角色'
getDetail(option.id)
} else {
// 新增时默认显示状态
setTimeout(() => {
updateStatusName()
}, 100)
}
})
// 获取菜单树
function getMenuTree() {
treeselect().then(res => {
menuOptions.value = res.data || []
})
}
// 获取详情
function getDetail(id) {
getRole(id).then(res => {
Object.assign(form, res.data)
updateStatusName()
})
// 获取角色的菜单权限
roleMenuTreeselect(id).then(res => {
menuOptions.value = res.menus || []
// 设置已选中的菜单
if (res.checkedKeys && res.checkedKeys.length > 0) {
selectedMenuIds.value = res.checkedKeys
tempSelectedMenuIds.value = [...res.checkedKeys]
}
})
}
// 更新状态名称显示
function updateStatusName() {
if (statusList.value.length > 0 && statusList.value[0].length > 0) {
const item = statusList.value[0].find(v => v.dictValue === form.status)
statusName.value = item ? item.dictLabel : ''
}
}
// 状态选择确认
function handleStatusConfirm(e) {
form.status = e.value[0].dictValue
statusName.value = e.value[0].dictLabel
showStatusPicker.value = false
}
// 打开菜单选择器
function openMenuPicker() {
// 将已选择的菜单ID复制到临时变量
tempSelectedMenuIds.value = [...selectedMenuIds.value]
showMenuPicker.value = true
}
// 取消菜单选择
function cancelMenuSelect() {
showMenuPicker.value = false
}
// 确认菜单选择
function confirmMenuSelect() {
selectedMenuIds.value = [...tempSelectedMenuIds.value]
showMenuPicker.value = false
}
// 提交
function submit() {
// 设置菜单权限
form.menuIds = selectedMenuIds.value
proxy.$refs['uForm'].validate().then(() => {
if (form.roleId) {
updateRole(form).then(() => {
proxy.$modal.msgSuccess('修改成功')
setTimeout(() => {
uni.navigateTo({ url: `/pages_mine/pages/system/role/list` })
}, 1500)
})
} else {
addRole(form).then(() => {
proxy.$modal.msgSuccess('新增成功')
setTimeout(() => {
uni.navigateTo({ url: `/pages_mine/pages/system/role/list` })
}, 1500)
})
}
}).catch(() => {
proxy.$modal.msgError('请填写完整信息')
})
}
</script>
<style lang="scss" scoped>
.container {
background-color: #f5f5f5;
}
.section {
margin: 24rpx;
padding: 0;
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
.section-title {
padding: 16rpx 24rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #ffffff;
line-height: 1.2;
font-size: 28rpx;
font-weight: 600;
display: flex;
align-items: center;
&::before {
content: '';
width: 6rpx;
height: 28rpx;
background: #ffffff;
border-radius: 3rpx;
margin-right: 12rpx;
}
}
.form-view {
padding: 24rpx;
.form-btn {
padding-top: 16rpx;
}
.selected-menus {
margin-top: 16rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 12rpx;
.selected-title {
font-size: 26rpx;
color: #666;
margin-bottom: 12rpx;
}
.menu-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
.menu-tag {
padding: 8rpx 16rpx;
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
border: 1rpx solid #667eea;
border-radius: 24rpx;
font-size: 24rpx;
color: #667eea;
}
}
}
}
}
.menu-popup {
background: #fff;
max-height: 80vh;
display: flex;
flex-direction: column;
.popup-header {
padding: 32rpx 24rpx 24rpx;
border-bottom: 1rpx solid #f0f0f0;
.header-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 8rpx;
display: block;
}
.header-count {
font-size: 24rpx;
color: #667eea;
display: block;
}
}
.popup-content {
flex: 1;
max-height: 60vh;
.menu-tree {
padding: 24rpx;
.menu-group {
margin-bottom: 32rpx;
width: 100%;
// 一级菜单
.level-1-wrapper {
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
border-radius: 12rpx;
padding: 16rpx 20rpx;
border-left: 4rpx solid #667eea;
margin-bottom: 16rpx;
width: 100%;
}
// 二级菜单列表
.level-2-list {
padding-left: 0;
width: 100%;
.level-2-item {
margin-bottom: 8rpx;
padding-left: 40rpx;
position: relative;
width: 100%;
&::before {
content: '';
position: absolute;
left: 20rpx;
top: 18rpx;
width: 6rpx;
height: 6rpx;
background: #667eea;
border-radius: 50%;
}
// 三级菜单列表
.level-3-list {
margin-top: 8rpx;
padding-left: 0;
width: 100%;
.level-3-item {
margin-bottom: 8rpx;
padding: 8rpx 12rpx 8rpx 60rpx;
position: relative;
background: #fafbfc;
border-radius: 8rpx;
width: 100%;
&::before {
content: '';
position: absolute;
left: 40rpx;
top: 50%;
transform: translateY(-50%);
width: 4rpx;
height: 4rpx;
background: #c0c4cc;
border-radius: 50%;
}
}
}
}
}
}
}
}
.popup-footer {
padding: 24rpx;
border-top: 1rpx solid #f0f0f0;
display: flex;
gap: 24rpx;
.footer-btn {
flex: 1;
height: 88rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 500;
transition: all 0.3s;
}
.cancel-btn {
background: #f5f7fa;
color: #909399;
&:active {
background: #e8eaed;
}
}
.confirm-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #ffffff;
box-shadow: 0 4rpx 12rpx rgba(102, 126, 234, 0.3);
&:active {
opacity: 0.8;
}
}
}
}
</style>
<style lang="scss">
.form-btn .u-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
border: none !important;
border-radius: 24rpx !important;
height: 80rpx !important;
box-shadow: 0 4rpx 16rpx rgba(102, 126, 234, 0.4) !important;
}
.form-btn .u-button__text {
font-size: 30rpx !important;
font-weight: 500 !important;
letter-spacing: 2rpx !important;
}
</style>