新增角色模板优化店铺装修

This commit is contained in:
gyq
2025-12-09 17:34:06 +08:00
parent 9f4cec00ee
commit cb13753928
9 changed files with 520 additions and 94 deletions

View File

@@ -0,0 +1,55 @@
import request from "@/utils/request";
const baseURL = "/account";
/**
* 角色模板 添加/编辑
* @param {*} data
* @returns
*/
export function roleTemplateAdd(data) {
return request({
url: `${baseURL}/admin/roleTemplate`,
method: data.id ? "put" : "post",
data,
});
}
/**
* 角色模板 列表
* @param {*} params
* @returns
*/
export function roleTemplateList(params) {
return request({
url: `${baseURL}/admin/roleTemplate/list`,
method: "get",
params,
});
}
/**
* 角色模板 删除
* @param {*} id
* @returns
*/
export function roleTemplateDel(id) {
return request({
url: `${baseURL}/admin/roleTemplate?id=${id}`,
method: "DELETE"
});
}
/**
* 角色模板 根据模板保存角色
* @param {*} data
* @returns
*/
export function saveByTemplate(data) {
return request({
url: `${baseURL}/admin/role/saveByTemplate`,
method: "post",
data,
});
}

View File

@@ -15,11 +15,11 @@
<el-icon><zoom-in /></el-icon>
</span>
<!-- 删除 -->
<!-- <span @click="handleRemove(file.url!)">
<span @click="handleRemove(file.url!)">
<el-icon>
<Delete />
</el-icon>
</span> -->
</span>
</span>
</div>
</template>
@@ -87,14 +87,20 @@ const fileList = ref<UploadUserFile[]>([]);
* 删除图片
*/
function handleRemove(imageUrl: string) {
FileAPI.delete(imageUrl).then(() => {
const index = modelValue.value.indexOf(imageUrl);
if (index !== -1) {
// 直接修改数组避免触发整体更新
modelValue.value.splice(index, 1);
fileList.value.splice(index, 1); // 同步更新 fileList
}
});
// FileAPI.delete(imageUrl).then(() => {
// const index = modelValue.value.indexOf(imageUrl);
// if (index !== -1) {
// // 直接修改数组避免触发整体更新
// modelValue.value.splice(index, 1);
// fileList.value.splice(index, 1); // 同步更新 fileList
// }
// });
const index = modelValue.value.indexOf(imageUrl);
if (index !== -1) {
// 直接修改数组避免触发整体更新
modelValue.value.splice(index, 1);
fileList.value.splice(index, 1); // 同步更新 fileList
}
}
function handleProgress(event: any, file: any, fileList: any) {
// console.log("handleProgress", evt, file, fileList);

View File

@@ -123,6 +123,15 @@ export const useUserStore = defineStore("user", () => {
clearToken();
usePermissionStoreHook().resetRouter();
useDictStoreHook().clearDictionaryCache();
let loginType = localStorage.getItem('loginType')
localStorage.clear()
localStorage.setItem('loginType', loginType)
window.location.reload()
resolve();
});
}

View File

@@ -0,0 +1,175 @@
<template>
<el-dialog title="选择角色模板" width="677px" v-model="visible">
<div class="content">
<div class="title">选择模板快速创建好角色</div>
<div class="role_template">
<div class="item" v-for="item in list" :key="item.id" @click="selectRoleHandle(item)">
<div class="header">
<div class="title">
{{ item.name }}
</div>
<el-radio v-model="item.cehcked" :value="true" readonly size="large"></el-radio>
</div>
<div class="role_list">
<div class="rl_t">包含角色</div>
<div class="rl_r">
<div class="row" v-for="val in item.children" :key="val.id">{{ val.name }}</div>
</div>
</div>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<div class="btn">
<el-button @click="visible = false" size="large" style="width: 100%;">自己创建</el-button>
</div>
<div class="btn">
<el-button type="primary" size="large" @click="submitHandle" :loading="loading" style="width: 100%;">
</el-button>
</div>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref } from 'vue'
import { roleTemplateList, saveByTemplate } from '@/api/account/roleTemplate'
const visible = ref(false)
const loading = ref(false)
const list = ref([])
// 获取角色模板列表
async function roleTemplateListAjax() {
try {
const res = await roleTemplateList()
res.forEach(el => {
el.cehcked = false
});
list.value = res
} catch (error) {
console.log(error);
}
}
function show() {
visible.value = true
roleTemplateListAjax()
}
function selectRoleHandle(item) {
list.value.forEach(item => item.cehcked = false)
item.cehcked = true
}
// 提交
const emits = defineEmits(['success'])
async function submitHandle() {
try {
let item = list.value.find(item => item.cehcked)
if (item === undefined) {
ElNotification({
title: '注意',
message: '请选择角色模板',
type: 'error'
})
return
}
loading.value = true
await saveByTemplate({
id: item.id
})
emits('success')
visible.value = false
} catch (error) {
console.log(error);
}
setTimeout(() => {
loading.value = false
}, 500);
}
defineExpose({
show
})
onMounted(() => {
roleTemplateListAjax()
})
</script>
<style scoped lang="scss">
.content {
padding: 28px 0;
.title {
font-size: 16px;
color: #333;
}
.role_template {
padding: 14px 0 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-column-gap: 14px;
grid-row-gap: 14px;
.item {
border: 1px solid #ececec;
border-radius: 8px;
padding: 14px 18px;
transition: all .3s ease-in-out;
&:hover {
cursor: pointer;
border-color: var(--el-color-primary);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
.title {
font-size: 16px;
font-weight: bold;
color: #333;
}
}
.role_list {
display: flex;
div {
flex: 1;
}
.rl_t {
color: #333;
}
.rl_r {
.row {
display: flex;
justify-content: flex-end;
color: #666;
}
}
}
}
}
}
.dialog-footer {
display: flex;
gap: 14px;
.btn {
flex: 1;
}
}
</style>

View File

@@ -101,7 +101,7 @@
</el-form-item>
</div>
<div class="flex-x-between">
<el-input v-model="permKeywords" clearable class="w-[150px]" placeholder="菜单菜单名称">
<el-input v-model="permKeywords" clearable class="w-[150px]" placeholder="菜单名称">
<template #prefix>
<Search />
</template>
@@ -156,6 +156,7 @@
</div>
</template>
</el-drawer>
<roleTemplateDialog ref="roleTemplateDialogRef" @success="handleQuery" />
</div>
</template>
@@ -170,6 +171,9 @@ const shopUser = useUserStore();
import menuSelect from "./components/menus.vue";
import RoleApi, { SysRole, addRequest, getListRequest } from "@/api/account/role";
import MenuAPI, { type RouteVO, CashMenu } from "@/api/account/menu";
import roleTemplateDialog from "./components/roleTemplateDialog.vue";
const roleTemplateDialogRef = ref(null)
const queryFormRef = ref();
const addRequestRef = ref();
@@ -241,6 +245,10 @@ function handleQuery() {
.then((data) => {
roleList.value = data.records;
total.value = data.totalRow;
if (data.records.length == 0) {
roleTemplateDialogRef?.value.show()
}
})
.finally(() => {
loading.value = false;

View File

@@ -6,10 +6,11 @@
<el-input placeholder="请输入" :maxlength="30" v-model="form.name"></el-input>
</el-form-item>
<el-form-item label="排序值">
<el-input placeholder="请输入" :maxlength="20" v-model="form.sort"></el-input>
<el-input placeholder="请输入" :maxlength="20" v-model="form.sort"
@input="e => form.sort = filterNumberInput(e, 0)"></el-input>
</el-form-item>
<el-form-item label="启用状态">
<el-switch v-model="form.status" :active-value="1" :inactive-value="0"></el-switch>
<el-switch v-model="form.isEnable" :active-value="1" :inactive-value="0"></el-switch>
</el-form-item>
</el-form>
<template #footer>
@@ -28,6 +29,8 @@
<script setup>
import { ref } from 'vue'
import { filterNumberInput } from '@/utils'
import { roleTemplateAdd } from "@/api/account/roleTemplate";
// 1添加模板 2添加类型
const type = ref(1)
@@ -36,13 +39,18 @@ const typeObj = ref({
2: '角色'
})
// 1添加 2编辑
const addType = ref(1)
const visible = ref(false)
const formRef = ref(null)
const loading = ref(false)
const form = ref({
name: '',
sort: '',
status: 1
sort: 0,
isEnable: 1,
roleId: '',
pid: ''
})
const rules = ref({
name: [
@@ -58,18 +66,32 @@ function resetForm() {
form.value = {
name: '',
sort: '',
status: 1
isEnable: 1
}
formRef.value.resetFields()
}
// 提交
const emits = defineEmits(['success'])
function submitHandle() {
formRef.value.validate(async vaild => {
try {
if (vaild) {
loading.value = true
// const res = await abc()
if (form.value.sort === '') {
form.value.sort = 0
}
await roleTemplateAdd(form.value)
ElNotification({
title: '注意',
message: '添加成功',
type: 'success'
})
emits('success')
visible.value = false
}
} catch (error) {
console.log(error);
@@ -80,11 +102,23 @@ function submitHandle() {
})
}
function show(t, obj) {
// t=1模板 2角色 obj=row数据 At=添加还是编辑
function show(t = 1, obj = {}, At = 1) {
visible.value = true
type.value = t
if (obj && obj.id) {
form.value = { ...obj }
if (At == 1) {
if (form.value.pid === null) {
form.value.pid = form.value.id
form.value.id = ''
form.value.name = ''
form.value.sort = 0
form.value.isEnable = 1
form.value.roleId = ''
}
}
console.log('form.value===', form.value);
}
}

View File

@@ -1,29 +1,47 @@
<template>
<div class="container">
<div class="gyq_container">
<div class="content">
<div class="row">
<el-button type="primary" @click="addTempateDialogRef.show(1)">添加</el-button>
</div>
<div class="row mt14">
<el-table :data="tableData.list" v-loading="tableData.loading" stripe border>
<el-table-column label="ID"></el-table-column>
<el-table-column label="模版名称"></el-table-column>
<el-table-column label="启用状态"></el-table-column>
<el-table-column label="排序值"></el-table-column>
<el-table-column label="创建时间"></el-table-column>
<el-table-column label="操作人"></el-table-column>
<el-table-column label="操作">
<el-table :data="tableData.list" v-loading="tableData.loading" stripe border row-key="id"
:tree-props="{ children: 'children' }" default-expand-all style="width: 100%;">
<el-table-column label="ID" prop="id" width="80"></el-table-column>
<el-table-column label="模版名称" prop="name"></el-table-column>
<el-table-column label="启用状态" prop="isEnable" width="120">
<template v-slot="scope">
<el-button link type="primary">添加角色</el-button>
<el-button link type="primary">分配权限</el-button>
<el-button link type="danger">删除</el-button>
<el-switch v-model="scope.row.isEnable" :active-value="1" :inactive-value="0"
@change="isEnableChange($event, scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column label="排序值" prop="sort"></el-table-column>
<el-table-column label="创建时间" prop="createTime"></el-table-column>
<!-- <el-table-column label="操作人" prop=""></el-table-column> -->
<el-table-column label="操作" width="250">
<template v-slot="scope">
<el-button link type="primary" @click="showMenusHandle(scope.row)"
v-if="scope.row.pid !== null">分配权限</el-button>
<el-button link type="primary" @click="addTempateDialogRef.show(2, scope.row)" v-else>添加角色</el-button>
<el-button link type="primary" @click="addTempateDialogRef.show(2, scope.row, 2)"
v-if="scope.row.pid">编辑</el-button>
<el-button link type="primary" @click="addTempateDialogRef.show(1, scope.row, 2)" v-else>编辑</el-button>
<el-popconfirm title="确认要删除吗?" @confirm="deleteHandle(scope.row)">
<template #reference>
<el-button type="danger" link>删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
</div>
<!-- <div class="row mt14">
<el-pagination v-model:current-page="tableData.page" v-model:page-size="tableData.size"
:page-sizes="[10, 30, 50, 100]" background layout="total" :total="tableData.total" />
</div> -->
</div>
<!-- 添加模板/角色 -->
<addTempateDialog ref="addTempateDialogRef" />
<addTempateDialog ref="addTempateDialogRef" @success="roleTemplateListAjax" />
<!-- 分配菜单弹窗 -->
<el-drawer v-model="assignPermDialogVisible" size="500">
<template #header>
@@ -42,7 +60,7 @@
</el-form-item>
</div>
<div class="flex-x-between">
<el-input v-model="permKeywords" clearable class="w-[150px]" placeholder="菜单菜单名称">
<el-input v-model="permKeywords" clearable class="w-[150px]" placeholder="菜单名称">
<template #prefix>
<Search />
</template>
@@ -104,6 +122,8 @@
import { ref, reactive } from 'vue'
import addTempateDialog from './components/addTempateDialog.vue';
import MenuAPI from "@/api/account/menu";
import RoleApi from "@/api/account/role";
import { roleTemplateList, roleTemplateDel, roleTemplateAdd } from '@/api/account/roleTemplate'
const addTempateDialogRef = ref(null)
@@ -111,11 +131,25 @@ const tableData = reactive({
loading: false,
page: 1,
size: 10,
total: 0,
list: []
})
function showMenusHandle(row) {
checkedRole.value = { ...row }
getMenuIds(row.roleId)
assignPermDialogVisible.value = true
}
const assignPermDialogVisible = ref(false)
// 弹窗
const dialog = reactive({
title: "",
visible: false,
row: {},
});
const checkedRole = ref({})
const platformType = ref(0);
@@ -192,6 +226,9 @@ const rules = reactive({
// 回显角色已拥有的菜单
function getMenuIds(roleId) {
console.log('回显角色已拥有的菜单===', roleId);
roleId = roleId ? roleId : dialog.row.id;
if (!roleId) {
return;
@@ -237,43 +274,107 @@ watch(
}
);
// 分配菜单菜单提交
function handleAssignPermSubmit() {
const roleId = checkedRole.value.id;
const name = checkedRole.value.name;
if (roleId) {
const checkedMenuIds = permTreeRef
.value.getCheckedNodes(false, true)
.map((node) => node.value);
const loading = ref(false)
const permTreeRef = ref(null)
loading.value = true;
RoleApi.update(roleId, {
name,
adminMenuIdList: checkedMenuIds,
cashMenuIdList: cashMenuIdList.value.filter((id) =>
casher_windows_menus.value.find((v) => v.menuId == id)
),
})
.then(() => {
ElMessage.success("分配菜单成功");
assignPermDialogVisible.value = false;
handleResetQuery();
// 分配菜单菜单提交
async function handleAssignPermSubmit() {
try {
const roleId = checkedRole.value.roleId;
const name = checkedRole.value.name;
if (roleId) {
const checkedMenuIds = permTreeRef
.value.getCheckedNodes(false, true)
.map((node) => node.value);
loading.value = true;
RoleApi.update(roleId, {
name,
adminMenuIdList: checkedMenuIds,
cashMenuIdList: cashMenuIdList.value.filter((id) =>
casher_windows_menus.value.find((v) => v.menuId == id)
),
})
.finally(() => {
loading.value = false;
});
.then(() => {
ElMessage.success("分配菜单成功");
assignPermDialogVisible.value = false;
roleTemplateListAjax();
})
.finally(() => {
loading.value = false;
});
}
} catch (error) {
console.log(error);
}
}
// 获取角色模板列表
async function roleTemplateListAjax() {
try {
tableData.loading = true
const res = await roleTemplateList()
tableData.list = res
tableData.total = res.length
// console.log('tableData.list===', JSON.stringify(tableData.list));
// console.log('tableData.list===', tableData.list);
} catch (error) {
console.log(error);
}
setTimeout(() => {
tableData.loading = false
}, 500);
}
// 展开懒加载
async function loadChildNodes(row, treeNode, resolve) {
try {
const res = await roleTemplateList({ pid: row.id })
resolve(res)
} catch (error) {
console.log(error);
}
}
// 删除
async function deleteHandle(row) {
try {
await roleTemplateDel(row.id)
ElNotification({
title: '注意',
message: '已删除',
type: 'success'
})
roleTemplateListAjax()
} catch (error) {
console.log(error);
}
}
// 状态变化
async function isEnableChange(e, row) {
console.log('isEnableChange.e===', e);
console.log('isEnableChange.row===', row);
try {
await roleTemplateAdd(row)
roleTemplateListAjax()
} catch (error) {
console.log(error);
}
}
onMounted(() => {
getMenuPermOptions();
getCashMenus();
roleTemplateListAjax()
})
</script>
<style scoped lang="scss">
.container {
.gyq_container {
width: 100%;
padding: 14px;
}

View File

@@ -81,7 +81,7 @@ export const newMenus = [
{
name: "弹窗广告",
icon: "tcgg",
pathName: "",
pathName: "advertisement",
intro: "设置弹窗广告"
},
]
@@ -120,12 +120,12 @@ export const newMenus = [
pathName: "birthdayGift",
intro: "用户生日管理设置"
},
{
name: "推送活动消息",
icon: "tshdxx",
pathName: "",
intro: "给用户推送服务通知"
}
// {
// name: "推送活动消息",
// icon: "tshdxx",
// pathName: "",
// intro: "给用户推送服务通知"
// }
]
},
{

View File

@@ -1,13 +1,8 @@
<template>
<div class="app-container">
<div class="btn_wraps">
<div
class="btn"
:class="{ active: tableActive == item.autoKey }"
v-for="item in tableData"
:key="item.autoKey"
@click="selectItemChange(item.autoKey)"
>
<div class="btn" :class="{ active: tableActive == item.autoKey }" v-for="item in tableData" :key="item.autoKey"
@click="selectItemChange(item.autoKey)">
{{ item.name }}
</div>
</div>
@@ -15,17 +10,16 @@
<div class="preview_wrap">
<div class="phone_wrap">
<div class="index_bg" v-if="tableActive == 'index_bg'">
<img class="bg" :src="selectItem.value" />
<el-carousel height="500px">
<el-carousel-item v-for="item in JSON.parse(selectItem.value)">
<img class="bg" :src="item" />
</el-carousel-item>
</el-carousel>
<div class="menu_wrap">
<div class="menu_wrap_div">
<div class="left">
<div class="icon_wrap">
<SvgIcon
style="margin-right: 0"
color="rgb(0,0,0)"
size="30"
iconClass="Coffee"
></SvgIcon>
<SvgIcon style="margin-right: 0" color="rgb(0,0,0)" size="30" iconClass="Coffee"></SvgIcon>
</div>
<div class="info">
<div class="t1">点餐</div>
@@ -34,24 +28,14 @@
</div>
<div class="right">
<div class="btn">
<SvgIcon
style="margin-right: 0"
size="30"
color="rgb(0,0,0)"
iconClass="postcard"
></SvgIcon>
<SvgIcon style="margin-right: 0" size="30" color="rgb(0,0,0)" iconClass="postcard"></SvgIcon>
<div class="info">
<div class="t1">会员</div>
<div class="t2">入会享权益</div>
</div>
</div>
<div class="btn">
<SvgIcon
style="margin-right: 0"
color="rgb(0,0,0)"
size="30"
iconClass="wallet"
></SvgIcon>
<SvgIcon style="margin-right: 0" color="rgb(0,0,0)" size="30" iconClass="wallet"></SvgIcon>
<div class="info">
<div class="t1">充值</div>
<div class="t2">充值享更多优惠</div>
@@ -171,7 +155,11 @@
</div>
</div>
<div class="shopinfo_bg" v-if="tableActive == 'shopinfo_bg'">
<img class="bg" :src="selectItem.value" />
<el-carousel height="120px">
<el-carousel-item v-for="item in JSON.parse(selectItem.value)">
<img class="bg" :src="item" />
</el-carousel-item>
</el-carousel>
<div class="shop_name">{{ shopName }}</div>
<img class="content" src="@/assets/images/shop_editor_bg.png" alt="" />
</div>
@@ -203,10 +191,19 @@
<div class="t2">点击图片更换</div>
</div>
<div class="form_item">
<SingleImageUpload v-model="selectItem.value" @onSuccess="onSuccess">
<img v-if="selectItem.value" :src="selectItem.value" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</SingleImageUpload>
<div class="upload_wrap" style="display: flex;flex-direction: column;gap: 28px;"
v-if="isJsonArrayString(selectItem.value)">
<MultiImageUpload v-model="imgList" @upDataEvent="MultiOnSuccess" />
<div>
<el-button type="primary" size="large" @click="doSubmit">确认修改</el-button>
</div>
</div>
<div v-else>
<SingleImageUpload v-model="selectItem.value" @onSuccess="onSuccess">
<img v-if="selectItem.value" :src="selectItem.value" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</SingleImageUpload>
</div>
<!-- <el-upload
:headers="headers"
class="avatar-uploader"
@@ -237,6 +234,7 @@ export default {
tableData: [],
selectItem: {},
imageUrl: "",
imgList: []
};
},
mounted() {
@@ -255,14 +253,53 @@ export default {
});
this.getList();
},
// 单图上传成功
onSuccess(response) {
this.doSubmit();
},
// 多图上传成功
async MultiOnSuccess(response) {
if (!response && this.imgList.length > 0) {
console.log(this.imgList);
await nextTick()
this.selectItem.value = JSON.stringify(this.imgList)
console.log('onSuccess.selectItem.value', this.selectItem.value);
}
},
/**
* 判断字符串是否为合法的 JSON 数组
* @param {string} str - 待判断的字符串
* @returns {boolean} true=是JSON数组字符串 / false=普通字符串/其他
*/
isJsonArrayString(str) {
// 1. 非字符串直接返回 false
if (typeof str !== 'string') {
return false;
}
// 2. 空字符串返回 false根据业务可调整
if (str.trim() === '') {
return false;
}
try {
// 3. 尝试解析 JSON
const parsed = JSON.parse(str);
// 4. 校验解析结果是否为数组
return Array.isArray(parsed);
} catch (e) {
// 解析失败(普通字符串/非法 JSON→ 返回 false
return false;
}
},
// 切换类型
selectItemChange(key) {
this.tableActive = key;
const { autoKey, id, name, value } = this.tableData.find((item) => item.autoKey == key);
this.selectItem = { autoKey, id, name, value };
if (this.isJsonArrayString(value)) {
this.imgList = JSON.parse(value)
}
console.log(this.selectItem);
},
// 获取装修数据
@@ -319,6 +356,7 @@ export default {
.index_bg {
padding-bottom: 50px;
.bg {
width: 100%;
height: 500px;