sc
This commit is contained in:
42
src/api/account/index.js
Normal file
42
src/api/account/index.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
// 添加增删改查接口
|
||||||
|
export const addAccount = async data => {
|
||||||
|
return request({
|
||||||
|
url: '/applet/xunjia/account/create',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteAccount = async id => {
|
||||||
|
return request({
|
||||||
|
url: '/applet/xunjia/account/delete',
|
||||||
|
method: 'delete',
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateAccount = async data => {
|
||||||
|
return request({
|
||||||
|
url: '/applet/xunjia/account/update',
|
||||||
|
method: 'put',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAccountPage = async params => {
|
||||||
|
return request({
|
||||||
|
url: '/applet/xunjia/account/page',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAccountInfo = async id => {
|
||||||
|
return request({
|
||||||
|
url: '/applet/xunjia/account/get',
|
||||||
|
method: 'get',
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
};
|
||||||
167
src/components/tree-select-item.vue
Normal file
167
src/components/tree-select-item.vue
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<view class="submenu-item">
|
||||||
|
<view class="submenu-item-content" @click.stop="handleMenuClick">
|
||||||
|
<view
|
||||||
|
class="custom-checkbox"
|
||||||
|
:class="{ 'checked': isMenuChecked }"
|
||||||
|
></view>
|
||||||
|
<view class="submenu-name">{{ menu[nameField] }}</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="hasChildren" class="menu-arrow" :class="{ 'expanded': isExpanded }" @click.stop="handleToggleMenu">{{ isExpanded ? '▼' : '▶' }}</view>
|
||||||
|
<view v-else class="menu-arrow-placeholder"></view>
|
||||||
|
</view>
|
||||||
|
<view v-if="hasChildren && isExpanded" class="submenu-list">
|
||||||
|
<TreeSelectItem
|
||||||
|
v-for="submenu in menu.children"
|
||||||
|
:key="submenu[idField]"
|
||||||
|
:menu="submenu"
|
||||||
|
:id-field="idField"
|
||||||
|
:name-field="nameField"
|
||||||
|
:selected-ids="selectedIds"
|
||||||
|
@update:selected-ids="$emit('update:selected-ids', $event)"
|
||||||
|
:expanded-menus="expandedMenus"
|
||||||
|
@toggle-menu="$emit('toggle-menu', $event)"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'TreeSelectItem'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from "vue"
|
||||||
|
import TreeSelectItem from './tree-select-item.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
menu: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
idField: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
nameField: {
|
||||||
|
type: String,
|
||||||
|
default: 'name'
|
||||||
|
},
|
||||||
|
selectedIds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
expandedMenus: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:selected-ids', 'toggle-menu'])
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const hasChildren = computed(() => {
|
||||||
|
return props.menu.children && props.menu.children.length > 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const isMenuChecked = computed(() => {
|
||||||
|
return props.selectedIds.includes(props.menu[props.idField])
|
||||||
|
})
|
||||||
|
|
||||||
|
const isExpanded = computed(() => {
|
||||||
|
return props.expandedMenus.includes(props.menu[props.idField])
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理菜单点击
|
||||||
|
function handleMenuClick() {
|
||||||
|
const menuId = props.menu[props.idField]
|
||||||
|
const checked = !isMenuChecked.value
|
||||||
|
emit('update:selected-ids', { menuId, checked })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理菜单展开/折叠
|
||||||
|
function handleToggleMenu() {
|
||||||
|
const menuId = props.menu[props.idField]
|
||||||
|
emit('toggle-menu', menuId)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 自定义checkbox样式 */
|
||||||
|
.custom-checkbox {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border: 2rpx solid #dcdfe6;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked {
|
||||||
|
border-color: #409eff;
|
||||||
|
background-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 10rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border: 2rpx solid #ffffff;
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
transform: translate(-50%, -60%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #606266;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #909399;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow.expanded {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow-placeholder {
|
||||||
|
width: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-list {
|
||||||
|
margin-left: 40rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
327
src/components/tree-select.vue
Normal file
327
src/components/tree-select.vue
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tree-select">
|
||||||
|
<!-- 递归渲染菜单树 -->
|
||||||
|
<template v-for="menu in treeData" :key="getMenuId(menu)">
|
||||||
|
<view class="menu-item">
|
||||||
|
<view class="menu-header" @click="toggleMenu(getMenuId(menu))">
|
||||||
|
<view
|
||||||
|
class="custom-checkbox"
|
||||||
|
:class="{ 'checked': isMenuChecked(getMenuId(menu)) }"
|
||||||
|
@click.stop="onMenuChange(getMenuId(menu), !isMenuChecked(getMenuId(menu)))">
|
||||||
|
</view>
|
||||||
|
<view class="menu-name">{{ getMenuName(menu) }}</view>
|
||||||
|
<view v-if="hasChildren(menu)" class="menu-arrow" :class="{ 'expanded': expandedMenus.includes(getMenuId(menu)) }">{{ expandedMenus.includes(getMenuId(menu)) ? '▼' : '▶' }}</view>
|
||||||
|
<view v-else class="menu-arrow-placeholder"></view>
|
||||||
|
</view>
|
||||||
|
<view v-if="hasChildren(menu) && expandedMenus.includes(getMenuId(menu))" class="submenu-list">
|
||||||
|
<!-- 递归渲染子菜单 -->
|
||||||
|
<TreeSelectItem
|
||||||
|
v-for="submenu in menu.children"
|
||||||
|
:key="getMenuId(submenu)"
|
||||||
|
:menu="submenu"
|
||||||
|
:id-field="idField"
|
||||||
|
:name-field="nameField"
|
||||||
|
:selected-ids="modelValue"
|
||||||
|
@update:selected-ids="handleSubMenuChange"
|
||||||
|
:expanded-menus="expandedMenus"
|
||||||
|
@toggle-menu="toggleMenu"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import TreeSelectItem from './tree-select-item.vue'
|
||||||
|
import { ref, computed, watch } from "vue"
|
||||||
|
|
||||||
|
// 组件props
|
||||||
|
const props = defineProps({
|
||||||
|
// 树数据
|
||||||
|
treeData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// id字段名
|
||||||
|
idField: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
// name字段名
|
||||||
|
nameField: {
|
||||||
|
type: String,
|
||||||
|
default: 'name'
|
||||||
|
},
|
||||||
|
// 选中字段(双向绑定)
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 事件
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
// 展开的菜单
|
||||||
|
const expandedMenus = ref([])
|
||||||
|
|
||||||
|
// 获取菜单ID
|
||||||
|
function getMenuId(menu) {
|
||||||
|
return menu[props.idField]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取菜单名称
|
||||||
|
function getMenuName(menu) {
|
||||||
|
return menu[props.nameField]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查菜单是否有子菜单
|
||||||
|
function hasChildren(menu) {
|
||||||
|
return menu.children && menu.children.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查菜单是否被选中
|
||||||
|
function isMenuChecked(menuId) {
|
||||||
|
return props.modelValue.includes(menuId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换菜单展开状态
|
||||||
|
function toggleMenu(menuId) {
|
||||||
|
const index = expandedMenus.value.indexOf(menuId)
|
||||||
|
if (index > -1) {
|
||||||
|
expandedMenus.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
expandedMenus.value.push(menuId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 菜单权限变更
|
||||||
|
function onMenuChange(menuId, checked) {
|
||||||
|
// 创建新的选中数组
|
||||||
|
const newSelected = [...props.modelValue]
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
// 选中当前菜单
|
||||||
|
if (!newSelected.includes(menuId)) {
|
||||||
|
newSelected.push(menuId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到当前菜单
|
||||||
|
const menu = findMenuById(menuId, props.treeData)
|
||||||
|
|
||||||
|
// 如果是上级菜单,选中所有子菜单
|
||||||
|
if (menu && hasChildren(menu)) {
|
||||||
|
menu.children.forEach(child => {
|
||||||
|
const childId = getMenuId(child)
|
||||||
|
if (!newSelected.includes(childId)) {
|
||||||
|
newSelected.push(childId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并选中上级菜单
|
||||||
|
checkParentMenu(menuId, newSelected)
|
||||||
|
} else {
|
||||||
|
// 取消选中当前菜单
|
||||||
|
const index = newSelected.indexOf(menuId)
|
||||||
|
if (index > -1) {
|
||||||
|
newSelected.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到当前菜单
|
||||||
|
const menu = findMenuById(menuId, props.treeData)
|
||||||
|
|
||||||
|
// 如果是上级菜单,取消选中所有子菜单
|
||||||
|
if (menu && hasChildren(menu)) {
|
||||||
|
menu.children.forEach(child => {
|
||||||
|
const childId = getMenuId(child)
|
||||||
|
const childIndex = newSelected.indexOf(childId)
|
||||||
|
if (childIndex > -1) {
|
||||||
|
newSelected.splice(childIndex, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并取消选中上级菜单
|
||||||
|
uncheckParentMenu(menuId, newSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 触发更新事件
|
||||||
|
emit('update:modelValue', newSelected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID查找菜单
|
||||||
|
function findMenuById(menuId, menus) {
|
||||||
|
for (const menu of menus) {
|
||||||
|
if (getMenuId(menu) === menuId) {
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
if (hasChildren(menu)) {
|
||||||
|
const found = findMenuById(menuId, menu.children)
|
||||||
|
if (found) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并选中上级菜单
|
||||||
|
function checkParentMenu(menuId, selected) {
|
||||||
|
// 找到当前菜单的父菜单
|
||||||
|
const parentMenu = findParentMenu(menuId, props.treeData)
|
||||||
|
if (parentMenu) {
|
||||||
|
const parentId = getMenuId(parentMenu)
|
||||||
|
if (!selected.includes(parentId)) {
|
||||||
|
selected.push(parentId)
|
||||||
|
}
|
||||||
|
// 递归检查更上级的菜单
|
||||||
|
checkParentMenu(parentId, selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查并取消选中上级菜单
|
||||||
|
function uncheckParentMenu(menuId, selected) {
|
||||||
|
// 找到当前菜单的父菜单
|
||||||
|
const parentMenu = findParentMenu(menuId, props.treeData)
|
||||||
|
if (parentMenu) {
|
||||||
|
const parentId = getMenuId(parentMenu)
|
||||||
|
// 检查父菜单的所有子菜单是否都未选中
|
||||||
|
const allChildrenUnchecked = parentMenu.children.every(child => {
|
||||||
|
return !selected.includes(getMenuId(child))
|
||||||
|
})
|
||||||
|
|
||||||
|
if (allChildrenUnchecked) {
|
||||||
|
const parentIndex = selected.indexOf(parentId)
|
||||||
|
if (parentIndex > -1) {
|
||||||
|
selected.splice(parentIndex, 1)
|
||||||
|
// 递归检查更上级的菜单
|
||||||
|
uncheckParentMenu(parentId, selected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找父菜单
|
||||||
|
function findParentMenu(menuId, menus) {
|
||||||
|
for (const menu of menus) {
|
||||||
|
if (hasChildren(menu)) {
|
||||||
|
if (menu.children.some(child => getMenuId(child) === menuId)) {
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
const found = findParentMenu(menuId, menu.children)
|
||||||
|
if (found) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理子菜单变更
|
||||||
|
function handleSubMenuChange(data) {
|
||||||
|
onMenuChange(data.menuId, data.checked)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* 自定义checkbox样式 */
|
||||||
|
.custom-checkbox {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border: 2rpx solid #dcdfe6;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked {
|
||||||
|
border-color: #409eff;
|
||||||
|
background-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 10rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border: 2rpx solid #ffffff;
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
transform: translate(-50%, -60%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 菜单列表 */
|
||||||
|
.menu-list {
|
||||||
|
space-y: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16rpx;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #303133;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow.expanded {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow-placeholder {
|
||||||
|
width: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-list {
|
||||||
|
margin-left: 40rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-item-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #606266;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -102,6 +102,12 @@
|
|||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/account/accountForm",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/student/list",
|
"path": "pages/student/list",
|
||||||
"style": {
|
"style": {
|
||||||
|
|||||||
341
src/pages/account/accountForm.vue
Normal file
341
src/pages/account/accountForm.vue
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
<template>
|
||||||
|
<view class="account-form-container">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<view class="page-header">
|
||||||
|
<view class="header-left" @click="goBack">
|
||||||
|
<view class="back-icon">←</view>
|
||||||
|
</view>
|
||||||
|
<view class="header-title">{{ isEdit ? '编辑账号' : '添加账号' }}</view>
|
||||||
|
<view class="header-right" @click="submitForm">
|
||||||
|
<view class="save-btn">保存</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 表单内容 -->
|
||||||
|
<view class="form-content">
|
||||||
|
<!-- 基本信息 -->
|
||||||
|
<view class="form-section">
|
||||||
|
<view class="section-title">基本信息</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="form-label">姓名</view>
|
||||||
|
<view class="form-control">
|
||||||
|
<input
|
||||||
|
v-model="form.nickname"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="请输入姓名"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="form-label">手机号</view>
|
||||||
|
<view class="form-control">
|
||||||
|
<input
|
||||||
|
v-model="form.mobile"
|
||||||
|
class="form-input"
|
||||||
|
maxlength="11"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 菜单权限 -->
|
||||||
|
<view class="form-section">
|
||||||
|
<view class="section-title">菜单权限</view>
|
||||||
|
<view class="menu-list">
|
||||||
|
<!-- 使用tree-select组件 -->
|
||||||
|
<tree-select
|
||||||
|
:tree-data="menuList"
|
||||||
|
id-field="id"
|
||||||
|
name-field="name"
|
||||||
|
v-model="form.menuIds"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 数据权限 -->
|
||||||
|
<view class="form-section">
|
||||||
|
<view class="section-title">数据权限</view>
|
||||||
|
<view class="data-scope-list">
|
||||||
|
<radio-group v-model="form.dataScope">
|
||||||
|
<view
|
||||||
|
v-for="scope in dataScopeOptions"
|
||||||
|
:key="scope.value"
|
||||||
|
class="data-scope-item"
|
||||||
|
>
|
||||||
|
<radio
|
||||||
|
:value="scope.value"
|
||||||
|
:checked="form.dataScope == scope.value"
|
||||||
|
/>
|
||||||
|
<view class="data-scope-label">
|
||||||
|
<view class="scope-name">{{ scope.label }}</view>
|
||||||
|
<view class="scope-desc">{{ scope.desc }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</radio-group>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted } from "vue"
|
||||||
|
import { addAccount, updateAccount, getAccountInfo } from "@/api/account/index.js"
|
||||||
|
import { useUserStore }from "@/store/modules/user.js"
|
||||||
|
import treeSelect from "@/components/tree-select.vue"
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 路由参数
|
||||||
|
const id = ref('');
|
||||||
|
const isEdit = computed(() => !!id.value);
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const form = ref({
|
||||||
|
nickname: '',
|
||||||
|
mobile: '',
|
||||||
|
menuIds: [],
|
||||||
|
dataScope: '1' // 默认全部数据
|
||||||
|
});
|
||||||
|
|
||||||
|
// 菜单列表
|
||||||
|
const menuList = computed(() => userStore.userMenus);
|
||||||
|
|
||||||
|
// 数据权限选项
|
||||||
|
const dataScopeOptions = [
|
||||||
|
{ value: '1', label: '全部数据', desc: '可以查看和管理所有数据' },
|
||||||
|
{ value: '5', label: '个人数据', desc: '只能查看和管理自己及下级数据' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 页面加载时获取参数
|
||||||
|
onMounted(() => {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const currentPage = pages[pages.length - 1];
|
||||||
|
id.value = currentPage.options.id || '';
|
||||||
|
|
||||||
|
if (isEdit.value) {
|
||||||
|
// 编辑模式,加载账号数据
|
||||||
|
loadAccountData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 加载账号数据
|
||||||
|
function loadAccountData() {
|
||||||
|
getAccountInfo(id.value).then(res => {
|
||||||
|
if (res.code == '0000') {
|
||||||
|
form.value = res.data;
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: res.msg || '加载账号数据失败', icon: 'none' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 提交表单
|
||||||
|
function submitForm() {
|
||||||
|
// 表单验证
|
||||||
|
if (!form.value.nickname) {
|
||||||
|
uni.showToast({ title: '请输入姓名', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!form.value.mobile || form.value.mobile.length !== 11) {
|
||||||
|
uni.showToast({ title: '请输入有效的手机号', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实际项目中应调用接口提交表单
|
||||||
|
if (isEdit.value) {
|
||||||
|
updateAccount(form.value).then(res => {
|
||||||
|
if (res.code == '0000') {
|
||||||
|
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||||
|
setTimeout(() => {
|
||||||
|
goBack();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: res.msg || '保存失败', icon: 'none' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
addAccount(form.value).then(res => {
|
||||||
|
if (res.code == '0000') {
|
||||||
|
uni.showToast({ title: '添加成功', icon: 'success' });
|
||||||
|
setTimeout(() => {
|
||||||
|
goBack();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: res.msg || '添加失败', icon: 'none' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
function goBack() {
|
||||||
|
uni.navigateBack({ delta: 1 });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
min-height: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
.account-form-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面头部 */
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 120rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
width: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-icon {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #303133;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
width: 100rpx;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #409eff;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单内容 */
|
||||||
|
.form-content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单 section */
|
||||||
|
.form-section {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单 item */
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
width: 120rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #606266;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border: 1rpx solid #dcdfe6;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 选择器 */
|
||||||
|
.picker {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* 数据权限 */
|
||||||
|
.data-scope-list {
|
||||||
|
space-y: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-scope-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 16rpx;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-scope-label {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scope-name {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scope-desc {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="header-title">账号创建与管控</view>
|
<view class="header-title">账号创建与管控</view>
|
||||||
<view class="header-right" @click="addAccount">
|
<view class="header-right" @click="addAccount">
|
||||||
<view class="add-btn">+ 添加</view>
|
<view class="add-btn">添加</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -15,25 +15,23 @@
|
|||||||
<view class="filter-section">
|
<view class="filter-section">
|
||||||
<view class="filter-row">
|
<view class="filter-row">
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
<view class="filter-label">账号状态</view>
|
<view class="filter-label">姓名</view>
|
||||||
<view class="filter-control">
|
<view class="filter-control">
|
||||||
<picker
|
<input
|
||||||
:range="statusOptions"
|
v-model="nickname"
|
||||||
:value="statusIndex"
|
class="search-input"
|
||||||
@change="onStatusChange"
|
placeholder="请输入姓名"
|
||||||
class="picker"
|
@input="onSearch"
|
||||||
>
|
/>
|
||||||
<view class="picker-text">{{ statusOptions[statusIndex] }}</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="filter-item">
|
<view class="filter-item">
|
||||||
<view class="filter-label">搜索</view>
|
<view class="filter-label">手机号</view>
|
||||||
<view class="filter-control">
|
<view class="filter-control">
|
||||||
<input
|
<input
|
||||||
v-model="searchKeyword"
|
v-model="mobile"
|
||||||
class="search-input"
|
class="search-input"
|
||||||
placeholder="请输入账号名称或手机号"
|
placeholder="请输入手机号"
|
||||||
@input="onSearch"
|
@input="onSearch"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
@@ -49,11 +47,11 @@
|
|||||||
class="account-item"
|
class="account-item"
|
||||||
>
|
>
|
||||||
<view class="account-info">
|
<view class="account-info">
|
||||||
<view class="account-name">{{ account.name }}</view>
|
<view class="account-name">{{ account.nickname }}</view>
|
||||||
<view class="account-meta">
|
<view class="account-meta">
|
||||||
<view class="meta-item">{{ account.phone }}</view>
|
<view class="meta-item">{{ account.mobile }}</view>
|
||||||
<view class="meta-item">{{ account.role }}</view>
|
<view class="meta-item">{{ account.isDistributor ? '分销员' : '' }}</view>
|
||||||
<view class="meta-item status-{{ account.status }}">{{ account.statusText }}</view>
|
<view class="meta-item" :class="`status-${account.status}`">{{ ['启用', '禁用'][account.status] }}</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="account-actions">
|
<view class="account-actions">
|
||||||
@@ -61,11 +59,10 @@
|
|||||||
编辑
|
编辑
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="action-btn"
|
class="action-btn delete-btn"
|
||||||
:class="account.status === 'active' ? 'disable-btn' : 'enable-btn'"
|
@click="handleDelete(account.id)"
|
||||||
@click="toggleAccountStatus(account.id, account.status)"
|
|
||||||
>
|
>
|
||||||
{{ account.status === 'active' ? '冻结' : '启用' }}
|
删除
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -76,52 +73,57 @@
|
|||||||
<view class="empty-icon">👥</view>
|
<view class="empty-icon">👥</view>
|
||||||
<view class="empty-text">暂无账号数据</view>
|
<view class="empty-text">暂无账号数据</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 分页信息 -->
|
||||||
|
<view class="pagination-info" v-if="accountList.length > 0">
|
||||||
|
<view class="info-text">共 {{ total }} 条记录</view>
|
||||||
|
<view class="page-controls">
|
||||||
|
<view class="page-btn" :disabled="pageNo <= 1" @click="changePage(pageNo - 1)">上一页</view>
|
||||||
|
<view class="page-info">{{ pageNo }} / {{ totalPages }}</view>
|
||||||
|
<view class="page-btn" :disabled="pageNo >= totalPages" @click="changePage(pageNo + 1)">下一页</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from "vue"
|
import { ref, onMounted, computed } from "vue"
|
||||||
|
import { getAccountPage, deleteAccount } from '@/api/account'
|
||||||
|
|
||||||
// 状态选项
|
// 筛选条件
|
||||||
const statusOptions = ['全部', '启用', '冻结']
|
const nickname = ref('')
|
||||||
const statusIndex = ref(0)
|
const mobile = ref('')
|
||||||
|
|
||||||
// 搜索关键词
|
// 分页参数
|
||||||
const searchKeyword = ref('')
|
const pageNo = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const total = ref(0)
|
||||||
|
|
||||||
|
// 计算总页数
|
||||||
|
const totalPages = computed(() => {
|
||||||
|
return Math.ceil(total.value / pageSize.value)
|
||||||
|
})
|
||||||
|
|
||||||
// 账号列表
|
// 账号列表
|
||||||
const accountList = ref([
|
const accountList = ref([])
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: '张教练',
|
|
||||||
phone: '138****1234',
|
|
||||||
role: '分销员',
|
|
||||||
status: 'active',
|
|
||||||
statusText: '启用'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: '李教练',
|
|
||||||
phone: '139****5678',
|
|
||||||
role: '分销员',
|
|
||||||
status: 'active',
|
|
||||||
statusText: '启用'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: '王教练',
|
|
||||||
phone: '137****9012',
|
|
||||||
role: '分销员',
|
|
||||||
status: 'inactive',
|
|
||||||
statusText: '冻结'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 实际项目中应从接口获取账号列表
|
// 实际项目中应从接口获取账号列表
|
||||||
// loadAccountList()
|
loadAccountList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const loadAccountList = async () => {
|
||||||
|
const params = {
|
||||||
|
nickname: nickname.value,
|
||||||
|
mobile: mobile.value,
|
||||||
|
pageNo: pageNo.value,
|
||||||
|
pageSize: pageSize.value
|
||||||
|
}
|
||||||
|
const { data } = await getAccountPage(params)
|
||||||
|
accountList.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
}
|
||||||
|
|
||||||
// 返回上一页
|
// 返回上一页
|
||||||
function goBack() {
|
function goBack() {
|
||||||
uni.navigateBack({ delta: 1 })
|
uni.navigateBack({ delta: 1 })
|
||||||
@@ -130,55 +132,59 @@
|
|||||||
// 添加账号
|
// 添加账号
|
||||||
function addAccount() {
|
function addAccount() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/account/add'
|
url: '/pages/account/accountForm'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑账号
|
// 编辑账号
|
||||||
function editAccount(accountId) {
|
function editAccount(accountId) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/account/edit?id=${accountId}`
|
url: `/pages/account/accountForm?id=${accountId}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换账号状态
|
// 删除账号
|
||||||
function toggleAccountStatus(accountId, currentStatus) {
|
function handleDelete(accountId) {
|
||||||
const newStatus = currentStatus === 'active' ? 'inactive' : 'active'
|
|
||||||
const newStatusText = newStatus === 'active' ? '启用' : '冻结'
|
|
||||||
|
|
||||||
uni.showModal({
|
uni.showModal({
|
||||||
title: '确认操作',
|
title: '确认删除',
|
||||||
content: `确定要${newStatusText}该账号吗?`,
|
content: '确定要删除该账号吗?',
|
||||||
success: function(res) {
|
success: async function(res) {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
// 实际项目中应调用接口切换账号状态
|
try {
|
||||||
const account = accountList.value.find(item => item.id === accountId)
|
await deleteAccount(accountId)
|
||||||
if (account) {
|
uni.showToast({
|
||||||
account.status = newStatus
|
title: '删除成功',
|
||||||
account.statusText = newStatusText
|
icon: 'success'
|
||||||
|
})
|
||||||
|
// 删除成功后刷新账号列表
|
||||||
|
loadAccountList()
|
||||||
|
} catch (error) {
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '删除失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
uni.showToast({
|
|
||||||
title: `账号已${newStatusText}`,
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 状态变更
|
|
||||||
function onStatusChange(e) {
|
|
||||||
const value = e.detail.value
|
|
||||||
statusIndex.value = value
|
|
||||||
// 实际项目中应根据状态筛选账号列表
|
|
||||||
// filterAccountList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索
|
// 搜索
|
||||||
function onSearch() {
|
function onSearch() {
|
||||||
|
// 重置页码
|
||||||
|
pageNo.value = 1
|
||||||
// 实际项目中应根据关键词搜索账号列表
|
// 实际项目中应根据关键词搜索账号列表
|
||||||
// searchAccountList()
|
// searchAccountList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换页码
|
||||||
|
function changePage(newPage) {
|
||||||
|
if (newPage >= 1 && newPage <= totalPages.value) {
|
||||||
|
pageNo.value = newPage
|
||||||
|
// 实际项目中应根据新页码加载账号列表
|
||||||
|
// loadAccountList()
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -325,6 +331,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-item {
|
.meta-item {
|
||||||
@@ -332,11 +339,11 @@
|
|||||||
color: #606266;
|
color: #606266;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-active {
|
.status-0 {
|
||||||
color: #67c23a;
|
color: #67c23a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-inactive {
|
.status-1 {
|
||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,16 +365,11 @@
|
|||||||
color: #409eff;
|
color: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disable-btn {
|
.delete-btn {
|
||||||
background-color: #fef0f0;
|
background-color: #fef0f0;
|
||||||
color: #f56c6c;
|
color: #f56c6c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enable-btn {
|
|
||||||
background-color: #f0f9eb;
|
|
||||||
color: #67c23a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 空状态 */
|
/* 空状态 */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -388,61 +390,56 @@
|
|||||||
color: #909399;
|
color: #909399;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 平板和大屏响应式 */
|
/* 分页信息样式 */
|
||||||
@media screen and (min-width: 768px) {
|
.pagination-info {
|
||||||
.account-manage-container {
|
display: flex;
|
||||||
max-width: 1000px;
|
justify-content: space-between;
|
||||||
margin: 0 auto;
|
align-items: center;
|
||||||
width: 100%;
|
padding: 16rpx 32rpx;
|
||||||
}
|
background-color: #fff;
|
||||||
|
border-top: 1rpx solid #e4e7ed;
|
||||||
.filter-section {
|
margin-top: 16rpx;
|
||||||
margin: 0 32rpx 24rpx;
|
}
|
||||||
padding: 32rpx;
|
|
||||||
}
|
.info-text {
|
||||||
|
font-size: 20rpx;
|
||||||
.account-list {
|
color: #606266;
|
||||||
padding: 0 32rpx;
|
}
|
||||||
}
|
|
||||||
|
.page-controls {
|
||||||
.account-item {
|
display: flex;
|
||||||
margin-bottom: 24rpx;
|
align-items: center;
|
||||||
padding: 32rpx;
|
gap: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-row {
|
.page-btn {
|
||||||
gap: 32rpx;
|
padding: 6rpx 16rpx;
|
||||||
}
|
border: 1rpx solid #dcdfe6;
|
||||||
|
border-radius: 4rpx;
|
||||||
.filter-label {
|
font-size: 20rpx;
|
||||||
font-size: 26rpx;
|
color: #606266;
|
||||||
}
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
.picker-text,
|
}
|
||||||
.search-input {
|
|
||||||
font-size: 26rpx;
|
.page-btn:hover {
|
||||||
}
|
border-color: #409eff;
|
||||||
|
color: #409eff;
|
||||||
.filter-control {
|
}
|
||||||
padding: 20rpx;
|
|
||||||
}
|
.page-btn[disabled] {
|
||||||
|
opacity: 0.5;
|
||||||
.account-name {
|
cursor: not-allowed;
|
||||||
font-size: 32rpx;
|
}
|
||||||
}
|
|
||||||
|
.page-btn[disabled]:hover {
|
||||||
.meta-item {
|
border-color: #dcdfe6;
|
||||||
font-size: 22rpx;
|
color: #606266;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.page-info {
|
||||||
font-size: 22rpx;
|
font-size: 20rpx;
|
||||||
padding: 12rpx 24rpx;
|
color: #303133;
|
||||||
}
|
|
||||||
|
|
||||||
.add-btn {
|
|
||||||
font-size: 28rpx;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 大屏设备响应式 */
|
/* 大屏设备响应式 */
|
||||||
|
|||||||
@@ -91,15 +91,11 @@
|
|||||||
<view class="vip-list">
|
<view class="vip-list">
|
||||||
<label v-for="vip in vipList" :key="vip.memberId" class="vip-item checkbox-item"
|
<label v-for="vip in vipList" :key="vip.memberId" class="vip-item checkbox-item"
|
||||||
@click="onVipChange(idx, vip.memberId)">
|
@click="onVipChange(idx, vip.memberId)">
|
||||||
<checkbox
|
<view class="custom-checkbox" :class="{ 'checked': rule.selectedVips && rule.selectedVips.includes(vip.memberId), 'disabled': isVipSelectedInOtherRules(idx, vip.memberId) }"></view>
|
||||||
:value="vip.memberId"
|
|
||||||
:checked="rule.selectedVips.includes(vip.memberId)"
|
|
||||||
:disabled="isVipSelectedInOtherRules(idx, vip.memberId)"
|
|
||||||
/>
|
|
||||||
<view class="checkbox-label" :class="{ 'disabled': isVipSelectedInOtherRules(idx, vip.memberId) }">
|
<view class="checkbox-label" :class="{ 'disabled': isVipSelectedInOtherRules(idx, vip.memberId) }">
|
||||||
<text>{{ vip.carName || '' }}</text>
|
<view v-if="vip.carName" class="car-tag">{{ vip.carName }}</view>
|
||||||
<text>{{ vip.memberName || '' }}</text>
|
<text>{{ vip.memberName || '' }}</text>
|
||||||
<text>{{ vip.discount ? ' ' + vip.discount : '' }}</text>
|
<text v-if="vip.discount" class="discount-text">¥{{ vip.discount }}</text>
|
||||||
</view>
|
</view>
|
||||||
</label>
|
</label>
|
||||||
</view>
|
</view>
|
||||||
@@ -227,17 +223,22 @@ const vipList = ref([])
|
|||||||
// VIP选择变更(确保会员类型不重复)
|
// VIP选择变更(确保会员类型不重复)
|
||||||
function onVipChange(idx, vipId) {
|
function onVipChange(idx, vipId) {
|
||||||
if (profitRules.value[idx]) {
|
if (profitRules.value[idx]) {
|
||||||
const vipIndex = profitRules.value[idx].selectedVips.indexOf(vipId)
|
const rule = profitRules.value[idx]
|
||||||
|
// 确保selectedVips存在
|
||||||
|
if (!rule.selectedVips) {
|
||||||
|
rule.selectedVips = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const vipIndex = rule.selectedVips.indexOf(vipId)
|
||||||
if (vipIndex > -1) {
|
if (vipIndex > -1) {
|
||||||
// 取消选择
|
// 取消选择
|
||||||
profitRules.value[idx].selectedVips.splice(vipIndex, 1)
|
rule.selectedVips.splice(vipIndex, 1)
|
||||||
console.log('取消选择',idx+1, profitRules.value[idx].selectedVips.length)
|
|
||||||
} else {
|
} else {
|
||||||
// 检查是否在其他消费分润规则中已选中
|
// 检查是否在其他消费分润规则中已选中
|
||||||
const isDuplicate = isVipSelectedInOtherRules(idx, vipId)
|
const isDuplicate = isVipSelectedInOtherRules(idx, vipId)
|
||||||
|
|
||||||
if (!isDuplicate) {
|
if (!isDuplicate) {
|
||||||
profitRules.value[idx].selectedVips.push(vipId)
|
rule.selectedVips.push(vipId)
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '该会员类型已在其他规则中选中',
|
title: '该会员类型已在其他规则中选中',
|
||||||
@@ -307,7 +308,6 @@ const vipList = ref([])
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-right {
|
.header-right {
|
||||||
width: 60rpx;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,24 +540,75 @@ const vipList = ref([])
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义checkbox样式 */
|
||||||
|
.custom-checkbox {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border: 2rpx solid #dcdfe6;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked {
|
||||||
|
border-color: #409eff;
|
||||||
|
background-color: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.checked::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 12rpx;
|
||||||
|
height: 20rpx;
|
||||||
|
border: 3rpx solid #ffffff;
|
||||||
|
border-top: none;
|
||||||
|
border-left: none;
|
||||||
|
transform: translate(-50%, -60%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.disabled {
|
||||||
|
border-color: #dcdfe6;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-checkbox.disabled.checked {
|
||||||
|
border-color: #dcdfe6;
|
||||||
|
background-color: #dcdfe6;
|
||||||
|
}
|
||||||
|
.checkbox-label {
|
||||||
|
margin: 0 10rpx;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
/* 禁用状态样式 */
|
/* 禁用状态样式 */
|
||||||
.checkbox-label.disabled {
|
.checkbox-label.disabled {
|
||||||
color: #c0c4cc !important;
|
color: #c0c4cc !important;
|
||||||
opacity: 0.5 !important;
|
opacity: 0.5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 禁用的checkbox样式 */
|
/* 车辆标签样式 */
|
||||||
checkbox[disabled] {
|
.car-tag {
|
||||||
opacity: 0.5 !important;
|
display: inline-block;
|
||||||
|
padding: 2rpx 8rpx;
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
color: #409eff;
|
||||||
|
font-size: 16rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
margin-right: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkbox[disabled] .uni-checkbox-input {
|
/* 折扣价格样式 */
|
||||||
border-color: #dcdfe6 !important;
|
.discount-text {
|
||||||
background-color: #f5f7fa !important;
|
color: #67c23a !important;
|
||||||
}
|
font-weight: 500;
|
||||||
|
|
||||||
checkbox[disabled] .uni-checkbox-input.uni-checkbox-input-checked {
|
|
||||||
border-color: #dcdfe6 !important;
|
|
||||||
background-color: #dcdfe6 !important;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="checkPermi(['work:distribution:profitRule'])" class="feature-card" @click="goToProfitRule">
|
<!-- <view v-if="checkPermi(['work:distribution:profitRule'])" class="feature-card" @click="goToProfitRule">
|
||||||
<view class="feature-icon user-icon">
|
<view class="feature-icon user-icon">
|
||||||
<view class="icon-text">💰</view>
|
<view class="icon-text">💰</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<view class="feature-title">分润规则配置</view>
|
<view class="feature-title">分润规则配置</view>
|
||||||
<view class="feature-desc">配置扫码注册、购买会员分润比例</view>
|
<view class="feature-desc">配置扫码注册、购买会员分润比例</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<view v-if="checkPermi(['work:distribution:profitRecord'])" class="feature-card" @click="goToDistributionData">
|
<view v-if="checkPermi(['work:distribution:profitRecord'])" class="feature-card" @click="goToDistributionData">
|
||||||
<view class="feature-icon dept-icon">
|
<view class="feature-icon dept-icon">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
const avatar = ref(storage.get(constant.avatar));
|
const avatar = ref(storage.get(constant.avatar));
|
||||||
const roles = ref(storage.get(constant.roles));
|
const roles = ref(storage.get(constant.roles));
|
||||||
const permissions = ref(storage.get(constant.permissions));
|
const permissions = ref(storage.get(constant.permissions));
|
||||||
|
const userMenus = ref([]);
|
||||||
|
|
||||||
const SET_TOKEN = val => {
|
const SET_TOKEN = val => {
|
||||||
token.value = val;
|
token.value = val;
|
||||||
@@ -77,6 +78,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
const userid = isEmpty(user) || isEmpty(user.id) ? '' : user.id;
|
const userid = isEmpty(user) || isEmpty(user.id) ? '' : user.id;
|
||||||
const username = isEmpty(user) || isEmpty(user.nickname) ? '' : user.nickname;
|
const username = isEmpty(user) || isEmpty(user.nickname) ? '' : user.nickname;
|
||||||
const role = isEmpty(user) || isEmpty(user.currentRole) ? '' : user.currentRole;
|
const role = isEmpty(user) || isEmpty(user.currentRole) ? '' : user.currentRole;
|
||||||
|
userMenus.value = res.data.menus || [];
|
||||||
if (res.data.roles && res.data.roles.length > 0) {
|
if (res.data.roles && res.data.roles.length > 0) {
|
||||||
SET_ROLES(res.data.roles);
|
SET_ROLES(res.data.roles);
|
||||||
SET_PERMISSIONS(res.data.permissions);
|
SET_PERMISSIONS(res.data.permissions);
|
||||||
@@ -121,6 +123,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
currentRole,
|
currentRole,
|
||||||
avatar,
|
avatar,
|
||||||
roles,
|
roles,
|
||||||
|
userMenus,
|
||||||
permissions,
|
permissions,
|
||||||
SET_AVATAR,
|
SET_AVATAR,
|
||||||
login: loginAction,
|
login: loginAction,
|
||||||
|
|||||||
Reference in New Issue
Block a user