feat: 同步接口更新

Former-commit-id: 29a22a96043a55445d48a086aa0a9157bcf7d6c6
This commit is contained in:
郝先瑞
2022-08-02 23:48:23 +08:00
parent 0fbefbcf73
commit b71d999d5e
22 changed files with 390 additions and 702 deletions

View File

@@ -1,36 +0,0 @@
import { SeataFormData } from '@/types/api/lab/seata';
import request from '@/utils/request';
/**
* 订单支付
* @returns
*/
export function payOrder(data: SeataFormData) {
return request({
url: '/youlai-lab/api/v1/seata/order/_pay',
method: 'post',
data: data,
});
}
/**
* 获取Seata模拟数据(包括订单信息、商品信息、会员余额信息)
* @returns
*/
export function getSeataData() {
return request({
url: '/youlai-lab/api/v1/seata/data',
method: 'get',
});
}
/**
* 重置Seata模拟数据
* @returns
*/
export function resetSeataData() {
return request({
url: '/youlai-lab/api/v1/seata/data/_reset',
method: 'put',
});
}

View File

@@ -11,7 +11,7 @@ export function listCategories(queryParams: object) {
return request({ return request({
url: '/mall-pms/api/v1/categories', url: '/mall-pms/api/v1/categories',
method: 'get', method: 'get',
params: queryParams params: queryParams,
}); });
} }
@@ -23,7 +23,7 @@ export function listCategories(queryParams: object) {
export function listCategoryOptions(): AxiosPromise<Option[]> { export function listCategoryOptions(): AxiosPromise<Option[]> {
return request({ return request({
url: '/mall-pms/api/v1/categories/options', url: '/mall-pms/api/v1/categories/options',
method: 'get' method: 'get',
}); });
} }
@@ -35,7 +35,7 @@ export function listCategoryOptions(): AxiosPromise<Option[]> {
export function getCategoryDetail(id: number) { export function getCategoryDetail(id: number) {
return request({ return request({
url: '/mall-pms/api/v1/categories/' + id, url: '/mall-pms/api/v1/categories/' + id,
method: 'get' method: 'get',
}); });
} }
@@ -48,7 +48,7 @@ export function addCategory(data: object) {
return request({ return request({
url: '/mall-pms/api/v1/categories', url: '/mall-pms/api/v1/categories',
method: 'post', method: 'post',
data: data data: data,
}); });
} }
@@ -62,7 +62,7 @@ export function updateCategory(id: number, data: object) {
return request({ return request({
url: '/mall-pms/api/v1/categories/' + id, url: '/mall-pms/api/v1/categories/' + id,
method: 'put', method: 'put',
data: data data: data,
}); });
} }
@@ -74,7 +74,7 @@ export function updateCategory(id: number, data: object) {
export function deleteCategories(ids: string) { export function deleteCategories(ids: string) {
return request({ return request({
url: '/mall-pms/api/v1/categories/' + ids, url: '/mall-pms/api/v1/categories/' + ids,
method: 'delete' method: 'delete',
}); });
} }
@@ -88,6 +88,6 @@ export function updateCategoryPart(id: number, data: object) {
return request({ return request({
url: '/mall-pms/api/v1/categories/' + id, url: '/mall-pms/api/v1/categories/' + id,
method: 'patch', method: 'patch',
data: data data: data,
}); });
} }

View File

@@ -1,7 +1,7 @@
import { import {
CouponQueryParam, CouponQueryParam,
CouponPageResult, CouponPageResult,
CouponFormData CouponFormData,
} from '@/types/api/sms/coupon'; } from '@/types/api/sms/coupon';
import request from '@/utils/request'; import request from '@/utils/request';
import { AxiosPromise } from 'axios'; import { AxiosPromise } from 'axios';
@@ -17,7 +17,7 @@ export function lisCouponPages(
return request({ return request({
url: '/mall-sms/api/v1/coupons/pages', url: '/mall-sms/api/v1/coupons/pages',
method: 'get', method: 'get',
params: queryParams params: queryParams,
}); });
} }
@@ -29,7 +29,7 @@ export function lisCouponPages(
export function getCouponFormData(id: number): AxiosPromise<CouponFormData> { export function getCouponFormData(id: number): AxiosPromise<CouponFormData> {
return request({ return request({
url: '/mall-sms/api/v1/coupons/' + id + '/form_data', url: '/mall-sms/api/v1/coupons/' + id + '/form_data',
method: 'get' method: 'get',
}); });
} }
@@ -42,7 +42,7 @@ export function addCoupon(data: CouponFormData) {
return request({ return request({
url: '/mall-sms/api/v1/coupons', url: '/mall-sms/api/v1/coupons',
method: 'post', method: 'post',
data: data data: data,
}); });
} }
@@ -56,7 +56,7 @@ export function updateCoupon(id: number, data: CouponFormData) {
return request({ return request({
url: '/mall-sms/api/v1/coupons/' + id, url: '/mall-sms/api/v1/coupons/' + id,
method: 'put', method: 'put',
data: data data: data,
}); });
} }
@@ -68,6 +68,6 @@ export function updateCoupon(id: number, data: CouponFormData) {
export function deleteCoupons(ids: string) { export function deleteCoupons(ids: string) {
return request({ return request({
url: '/mall-sms/api/v1/coupons/' + ids, url: '/mall-sms/api/v1/coupons/' + ids,
method: 'delete' method: 'delete',
}); });
} }

View File

@@ -2,6 +2,7 @@ import {
MenuFormData, MenuFormData,
MenuItem, MenuItem,
MenuQueryParam, MenuQueryParam,
Resource,
} from '@/types/api/system/menu'; } from '@/types/api/system/menu';
import { Option } from '@/types/common'; import { Option } from '@/types/common';
import request from '@/utils/request'; import request from '@/utils/request';
@@ -43,9 +44,9 @@ export function listMenuOptions(): AxiosPromise<Option[]> {
} }
/** /**
* 获取菜单权限树形列表 * 获取资源(菜单+权限)树形列表
*/ */
export function getResource(): AxiosPromise<any> { export function listResources(): AxiosPromise<Resource[]> {
return request({ return request({
url: '/youlai-admin/api/v1/menus/resources', url: '/youlai-admin/api/v1/menus/resources',
method: 'get', method: 'get',
@@ -56,7 +57,7 @@ export function getResource(): AxiosPromise<any> {
* 获取菜单详情 * 获取菜单详情
* @param id * @param id
*/ */
export function getMenuDetail(id: number): AxiosPromise<MenuFormData> { export function getMenuDetail(id: string): AxiosPromise<MenuFormData> {
return request({ return request({
url: '/youlai-admin/api/v1/menus/' + id, url: '/youlai-admin/api/v1/menus/' + id,
method: 'get', method: 'get',

View File

@@ -2,7 +2,7 @@ import {
RoleFormData, RoleFormData,
RolePageResult, RolePageResult,
RoleQueryParam, RoleQueryParam,
RoleResourceData RoleResource,
} from '@/types/api/system/role'; } from '@/types/api/system/role';
import { Option } from '@/types/common'; import { Option } from '@/types/common';
@@ -20,7 +20,7 @@ export function listRolePages(
return request({ return request({
url: '/youlai-admin/api/v1/roles/pages', url: '/youlai-admin/api/v1/roles/pages',
method: 'get', method: 'get',
params: queryParams params: queryParams,
}); });
} }
@@ -35,7 +35,7 @@ export function listRoleOptions(
return request({ return request({
url: '/youlai-admin/api/v1/roles/options', url: '/youlai-admin/api/v1/roles/options',
method: 'get', method: 'get',
params: queryParams params: queryParams,
}); });
} }
@@ -44,10 +44,10 @@ export function listRoleOptions(
* *
* @param queryParams * @param queryParams
*/ */
export function getRoleResourceIds(roleId: string): AxiosPromise<any> { export function getRoleResources(roleId: string): AxiosPromise<RoleResource> {
return request({ return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/resources', url: '/youlai-admin/api/v1/roles/' + roleId + '/resources',
method: 'get' method: 'get',
}); });
} }
@@ -58,12 +58,12 @@ export function getRoleResourceIds(roleId: string): AxiosPromise<any> {
*/ */
export function updateRoleResource( export function updateRoleResource(
roleId: string, roleId: string,
data: RoleResourceData data: RoleResource
): AxiosPromise<any> { ): AxiosPromise<any> {
return request({ return request({
url: '/youlai-admin/api/v1/roles/' + roleId + '/resources', url: '/youlai-admin/api/v1/roles/' + roleId + '/resources',
method: 'put', method: 'put',
data: data data: data,
}); });
} }
@@ -75,7 +75,7 @@ export function updateRoleResource(
export function getRoleFormDetail(id: number): AxiosPromise<RoleFormData> { export function getRoleFormDetail(id: number): AxiosPromise<RoleFormData> {
return request({ return request({
url: '/youlai-admin/api/v1/roles/' + id, url: '/youlai-admin/api/v1/roles/' + id,
method: 'get' method: 'get',
}); });
} }
@@ -88,7 +88,7 @@ export function addRole(data: RoleFormData) {
return request({ return request({
url: '/youlai-admin/api/v1/roles', url: '/youlai-admin/api/v1/roles',
method: 'post', method: 'post',
data: data data: data,
}); });
} }
@@ -102,7 +102,7 @@ export function updateRole(id: number, data: RoleFormData) {
return request({ return request({
url: '/youlai-admin/api/v1/roles/' + id, url: '/youlai-admin/api/v1/roles/' + id,
method: 'put', method: 'put',
data: data data: data,
}); });
} }
@@ -114,6 +114,6 @@ export function updateRole(id: number, data: RoleFormData) {
export function deleteRoles(ids: string) { export function deleteRoles(ids: string) {
return request({ return request({
url: '/youlai-admin/api/v1/roles/' + ids, url: '/youlai-admin/api/v1/roles/' + ids,
method: 'delete' method: 'delete',
}); });
} }

View File

@@ -38,6 +38,7 @@ export const getLanguage = () => {
}; };
const i18n = createI18n({ const i18n = createI18n({
legacy: false,
locale: getLanguage(), locale: getLanguage(),
messages: messages messages: messages
}); });

View File

@@ -1,9 +0,0 @@
/**
* Seata表单类型声明
*/
export interface SeataFormData {
openTx: boolean;
stockEx: boolean;
accountEx: boolean;
orderEx: boolean;
}

View File

@@ -10,8 +10,9 @@ export interface MenuQueryParam {
*/ */
export interface MenuItem { export interface MenuItem {
id: number; id?: number;
parentId: number; parentId: number;
type?: string | 'CATEGORY' | 'MENU' | 'EXTLINK';
createTime: string; createTime: string;
updateTime: string; updateTime: string;
name: string; name: string;
@@ -65,3 +66,39 @@ export interface MenuFormData {
*/ */
type: string; type: string;
} }
/**
* 资源(菜单+权限)类型
*/
export interface Resource {
/**
* 菜单值
*/
value: string;
/**
* 菜单文本
*/
label: string;
/**
* 子菜单
*/
children: Resource[];
/**
* 权限集合
*/
perms: Permission[];
}
/**
* 权限类型
*/
export interface Permission {
/**
* 权限值
*/
value: string;
/**
* 权限文本
*/
label: string;
}

View File

@@ -1,3 +1,4 @@
import { StringMap } from 'i18next';
import { PageQueryParam, PageResult } from '../base'; import { PageQueryParam, PageResult } from '../base';
/** /**
@@ -40,7 +41,7 @@ export interface RoleFormData {
/** /**
* *
*/ */
export interface RoleResourceData { export interface RoleResource {
menuIds: string[]; menuIds: string[];
permIds: string[]; permIds: string[];
} }

View File

@@ -1,376 +0,0 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts">
export default {
name: 'seata',
};
</script>
<script setup lang="ts">
import { reactive, onMounted, toRefs } from 'vue';
import SvgIcon from '@/components/SvgIcon/index.vue';
import {
Money,
Refresh,
RefreshLeft,
Right,
CircleCheckFilled,
CircleCloseFilled,
} from '@element-plus/icons-vue';
import { payOrder, getSeataData, resetSeataData } from '@/api/lab/seata';
import { ElMessage, ElMessageBox } from 'element-plus';
import { SeataFormData } from '@/types/api/lab/seata';
const state = reactive({
// 保留改变前数据
cacheSeataData: {
status: undefined,
stockNum: undefined,
balance: undefined,
},
seataData: {
orderInfo: {
orderSn: undefined,
status: undefined,
},
stockInfo: {
name: undefined,
picUrl: undefined,
stockNum: undefined,
},
accountInfo: {
nickName: undefined,
avatarUrl: undefined,
balance: undefined,
},
},
loading: false,
submitData: {
openTx: true, // 是否开启事务
orderEx: true, // 订单修改异常
} as SeataFormData,
});
const { cacheSeataData, seataData, loading, submitData } = toRefs(state);
/**
* 订单支付(模拟)
*/
function handleOrderPay() {
// 数据校验
if (
(seataData.value.stockInfo.stockNum &&
seataData.value.stockInfo.stockNum != 999) ||
(seataData.value.accountInfo.balance &&
seataData.value.accountInfo.balance != 1000000000) ||
(seataData.value.orderInfo.status &&
seataData.value.orderInfo.status != 101)
) {
ElMessageBox.confirm(
'检查到当前数据已被污染,请先重置数据后尝试提交?',
'警告',
{
confirmButtonText: '重置数据',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
handleDataReset();
})
.catch(() => {});
} else {
// 订单支付模拟提交
loading.value = true;
payOrder(submitData.value)
.then(() => {
ElMessage.success('订单支付成功');
})
.finally(() => {
cacheSeataData.value = {
status: seataData.value.orderInfo.status,
stockNum: seataData.value.stockInfo.stockNum,
balance: seataData.value.accountInfo.balance,
};
loadData();
});
}
}
/**
* 加载数据
*/
function loadData() {
loading.value = true;
getSeataData().then((response: any) => {
seataData.value = response.data;
loading.value = false;
});
}
/**
* 刷新数据
*/
function handleDataRefresh() {
loading.value = true;
loadData();
}
/**
* 数据重置
*/
function handleDataReset() {
loading.value = true;
resetSeataData().then(() => {
ElMessage.success('数据还原成功');
loading.value = false;
cacheSeataData.value = {
status: undefined,
stockNum: undefined,
balance: undefined,
};
loadData();
});
}
onMounted(() => {
// 第一次加载重置数据测试
handleDataReset();
});
</script>
<template>
<div class="app-container">
<el-alert type="info">
<p style="font-size: 16px">
<b>模拟订单支付流程</b>
扣减商品库存 扣减会员余额 修改订单状态
</p>
<p style="font-size: 14px">
<b> 分布式事务生效判断</b>
<el-link :icon="CircleCheckFilled" type="success">全部成功</el-link>
<el-link type="danger" :icon="CircleCloseFilled">全部失败</el-link>
</p>
<p style="font-size: 14px">
<b> 博客教程</b>
<el-link
type="primary"
href="https://www.cnblogs.com/haoxianrui/"
target="_blank"
>
https://www.cnblogs.com/haoxianrui</el-link
>
</p>
</el-alert>
<el-card class="box-card" shadow="always" style="margin-top: 20px">
<el-form :inline="true">
<el-row>
<el-col :span="20">
<el-form-item>
<el-switch
v-model="submitData.openTx"
active-value=""
active-text="开启事务"
inactive-text="关闭事务"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="Money" @click="handleOrderPay"
>订单支付</el-button
>
<el-button :icon="Refresh" @click="handleDataRefresh"
>刷新数据</el-button
>
</el-form-item>
</el-col>
<el-col :span="4" style="text-align: right">
<el-button :icon="RefreshLeft" @click="handleDataReset"
>重置数据</el-button
>
</el-col>
</el-row>
</el-form>
</el-card>
<el-row :gutter="10" style="margin-top: 20px" v-loading="loading">
<el-col :span="8" :xs="24" class="card-panel__col">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="goods" />
商品信息
</template>
<div style="display: flex">
<el-image
style="width: 100px; height: 100px"
:src="seataData.stockInfo.picUrl"
fit="fill"
/>
<div style="margin-left: 10px">
<el-form-item label="商品名称:">
{{ seataData.stockInfo.name }}
</el-form-item>
<el-form-item label="库存数量:" style="display: flex">
<div v-if="cacheSeataData.stockNum != null">
{{ cacheSeataData.stockNum }}
<el-icon>
<right />
</el-icon>
</div>
{{ seataData.stockInfo.stockNum }}
<div v-if="cacheSeataData.stockNum" style="margin-left: 50px">
<el-link
v-if="
cacheSeataData.stockNum != seataData.stockInfo.stockNum
"
type="success"
:underline="false"
:icon="CircleCheckFilled"
>
修改成功
</el-link>
<el-link
v-else-if="
cacheSeataData.stockNum == seataData.stockInfo.stockNum
"
type="danger"
:underline="false"
:icon="CircleCloseFilled"
>
修改失败
</el-link>
</div>
</el-form-item>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8" :xs="24" class="card-panel__col">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="user" />
会员信息
</template>
<div style="display: flex">
<el-image
style="width: 100px; height: 100px"
:src="seataData.accountInfo.avatarUrl"
fit="fill"
/>
<div style="margin-left: 10px">
<el-form-item label="会员昵称:">
{{ seataData.accountInfo.nickName }}
</el-form-item>
<el-form-item label="会员余额:">
<div v-if="cacheSeataData.balance != null">
{{ (cacheSeataData.balance as any) / 100 }} 元
<el-icon>
<right />
</el-icon>
</div>
{{ (seataData.accountInfo.balance as any) / 100 }} 元
<div v-if="cacheSeataData.balance" style="margin-left: 50px">
<el-link
v-if="
cacheSeataData.balance != seataData.accountInfo.balance
"
type="success"
:underline="false"
:icon="CircleCheckFilled"
>
修改成功
</el-link>
<el-link
v-else-if="
cacheSeataData.balance == seataData.accountInfo.balance
"
type="danger"
:underline="false"
:icon="CircleCloseFilled"
>
修改失败
</el-link>
</div>
</el-form-item>
</div>
</div>
</el-card>
</el-col>
<el-col :span="8" :xs="24" class="card-panel__col">
<el-card class="box-card" shadow="always">
<template #header>
<svg-icon icon-class="order" />
订单信息
<el-checkbox
v-model="submitData.orderEx"
:label="true"
style="float: right; color: #f56c6c"
>
搞点异常</el-checkbox
>
</template>
<el-form-item label="订单编号:">
{{ seataData.orderInfo.orderSn }}
</el-form-item>
<el-form-item label="订单状态:">
<div v-if="cacheSeataData.status == 101">
<el-tag type="info"> 待支付 </el-tag>
<el-icon>
<right />
</el-icon>
</div>
<el-tag v-if="seataData.orderInfo.status == 101" type="info"
>待支付</el-tag
>
<el-tag v-else-if="seataData.orderInfo.status == 201" type="success"
>已支付</el-tag
>
<div v-if="cacheSeataData.balance" style="margin-left: 50px">
<el-link
v-if="cacheSeataData.status != seataData.orderInfo.status"
type="success"
:underline="false"
:icon="CircleCheckFilled"
>
修改成功
</el-link>
<el-link
v-else
type="danger"
:underline="false"
:icon="CircleCloseFilled"
>
修改失败
</el-link>
</div>
</el-form-item>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style lang="scss" scoped>
.card-panel__col {
margin-bottom: 12px;
.el-link {
font-size: 16px;
margin-right: 8px;
}
}
</style>

View File

@@ -1,111 +1,3 @@
<!-- 商品分类层级最多为三层level字段标识 -->
<template>
<div class="component-container">
<el-tree
v-loading="loading"
ref="categoryTreeRef"
:data="categoryOptions"
:props="{ label: 'name', children: 'children', disabled: '' }"
node-key="id"
:expand-on-click-node="false"
default-expand-all
:accordion="true"
@node-click="handleNodeClick"
>
<template #default="scope">
<div class="custom-tree-node">
<span>
<el-image
v-show="scope.data.level == 3"
:src="scope.data.iconUrl"
style="
width: 20px;
height: 20px;
vertical-align: middle;
margin-top: -5px;
"
>
<template #error>
<div class="image-slot">
<Picture style="width: 20px; height: 20px" />
</div>
</template>
</el-image>
{{ scope.data.name }}
</span>
<span>
<el-button
v-show="scope.data.level != 3"
type="success"
:icon="Plus"
circle
plain
@click.stop="handleAdd(scope.data)"
/>
<el-button
v-show="scope.data.id !== 0"
type="warning"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.data)"
/>
<el-button
v-show="
scope.data.id &&
(!scope.data.children || scope.data.children.length <= 0)
"
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.data)"
/>
</span>
</div>
</template>
</el-tree>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="750px">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="上级分类" prop="parentId">
<el-input v-model="parent.name" readonly />
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="分类图标" prop="iconUrl">
<single-upload v-model="formData.iconUrl" />
</el-form-item>
<el-form-item label="显示状态" prop="visible">
<el-radio-group v-model="formData.visible">
<el-radio :label="1">显示</el-radio>
<el-radio :label="0">隐藏</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel">取 消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { import {
listCategories, listCategories,
@@ -268,6 +160,114 @@ onMounted(() => {
}); });
</script> </script>
<!-- 商品分类层级最多为三层level字段标识 -->
<template>
<div class="component-container">
<el-tree
v-loading="loading"
ref="categoryTreeRef"
:data="categoryOptions"
:props="{ label: 'name', children: 'children', disabled: '' }"
node-key="id"
:expand-on-click-node="false"
default-expand-all
:accordion="true"
@node-click="handleNodeClick"
>
<template #default="scope">
<div class="custom-tree-node">
<span>
<el-image
v-show="scope.data.level == 3"
:src="scope.data.iconUrl"
style="
width: 20px;
height: 20px;
vertical-align: middle;
margin-top: -5px;
"
>
<template #error>
<div class="image-slot">
<Picture style="width: 20px; height: 20px" />
</div>
</template>
</el-image>
{{ scope.data.name }}
</span>
<span>
<el-button
v-show="scope.data.level != 3"
type="success"
:icon="Plus"
circle
plain
@click.stop="handleAdd(scope.data)"
/>
<el-button
v-show="scope.data.id !== 0"
type="warning"
:icon="Edit"
circle
plain
@click.stop="handleUpdate(scope.data)"
/>
<el-button
v-show="
scope.data.id &&
(!scope.data.children || scope.data.children.length <= 0)
"
type="danger"
:icon="Delete"
circle
plain
@click.stop="handleDelete(scope.data)"
/>
</span>
</div>
</template>
</el-tree>
<el-dialog :title="dialog.title" v-model="dialog.visible" width="750px">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-width="100px"
>
<el-form-item label="上级分类" prop="parentId">
<el-input v-model="parent.name" readonly />
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model="formData.name" />
</el-form-item>
<el-form-item label="分类图标" prop="iconUrl">
<single-upload v-model="formData.iconUrl" />
</el-form-item>
<el-form-item label="显示状态" prop="visible">
<el-radio-group v-model="formData.visible">
<el-radio :label="1">显示</el-radio>
<el-radio :label="0">隐藏</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input v-model="formData.sort"></el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm">确 定</el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style> <style>
.component-container { .component-container {
height: 100%; height: 100%;
@@ -280,13 +280,10 @@ onMounted(() => {
justify-content: space-between; justify-content: space-between;
font-size: 14px; font-size: 14px;
padding-right: 8px; padding-right: 8px;
line-height: 40px0;
} }
.el-tree-node__content { .el-tree-node__content {
height: 40px; height: 40px;
} }
.el-divider--horizontal {
margin: 30px 0 15px;
}
</style> </style>

View File

@@ -49,20 +49,20 @@ const emit = defineEmits(['next', 'update:modelValue']);
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Object, type: Object,
default: () => {} default: () => {},
} },
}); });
const goodsInfo: any = computed({ const goodsInfo: any = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: value => { set: (value) => {
emit('update:modelValue', value); emit('update:modelValue', value);
} },
}); });
const state = reactive({ const state = reactive({
categoryOptions: [] as Option[], categoryOptions: [] as Option[],
pathLabels: [] pathLabels: [],
}); });
const { categoryOptions, pathLabels } = toRefs(state); const { categoryOptions, pathLabels } = toRefs(state);

View File

@@ -1,7 +1,7 @@
<!-- setup 无法设置组件名称组件名称keepAlive必须 --> <!-- setup 无法设置组件名称组件名称keepAlive必须 -->
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'goods' name: 'goods',
}; };
</script> </script>
@@ -16,7 +16,7 @@ import {
Edit, Edit,
Refresh, Refresh,
Delete, Delete,
View View,
} from '@element-plus/icons-vue'; } from '@element-plus/icons-vue';
import { listSpuPages, deleteSpu } from '@/api/pms/goods'; import { listSpuPages, deleteSpu } from '@/api/pms/goods';
import { listCategoryOptions } from '@/api/pms/category'; import { listCategoryOptions } from '@/api/pms/category';
@@ -39,12 +39,12 @@ const state = reactive({
total: 0, total: 0,
queryParams: { queryParams: {
pageNum: 1, pageNum: 1,
pageSize: 10 pageSize: 10,
} as GoodsQueryParam, } as GoodsQueryParam,
goodsList: [] as GoodsItem[], goodsList: [] as GoodsItem[],
categoryOptions: [] as Option[], categoryOptions: [] as Option[],
goodDetail: undefined, goodDetail: undefined,
dialogVisible: false dialogVisible: false,
}); });
const { const {
@@ -55,7 +55,7 @@ const {
categoryOptions, categoryOptions,
goodDetail, goodDetail,
total, total,
dialogVisible dialogVisible,
} = toRefs(state); } = toRefs(state);
function handleQuery() { function handleQuery() {
@@ -72,7 +72,7 @@ function resetQuery() {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
name: undefined, name: undefined,
categoryId: undefined categoryId: undefined,
}; };
handleQuery(); handleQuery();
} }
@@ -89,7 +89,7 @@ function handleAdd() {
function handleUpdate(row: any) { function handleUpdate(row: any) {
router.push({ router.push({
path: 'goods-detail', path: 'goods-detail',
query: { goodsId: row.id, categoryId: row.categoryId } query: { goodsId: row.id, categoryId: row.categoryId },
}); });
} }
@@ -98,7 +98,7 @@ function handleDelete(row: any) {
ElMessageBox.confirm('是否确认删除选中的数据项?', '警告', { ElMessageBox.confirm('是否确认删除选中的数据项?', '警告', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning',
}) })
.then(function () { .then(function () {
return deleteSpu(ids); return deleteSpu(ids);

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'advert' name: 'advert',
}; };
</script> </script>
@@ -14,17 +14,17 @@ import {
getAdvertFormDetail, getAdvertFormDetail,
updateAdvert, updateAdvert,
addAdvert, addAdvert,
deleteAdverts deleteAdverts,
} from '@/api/sms/advert'; } from '@/api/sms/advert';
import { Dialog } from '@/types/common'; import { Dialog } from '@/types/common';
import { import {
AdvertFormData, AdvertFormData,
AdvertItem, AdvertItem,
AdvertQueryParam AdvertQueryParam,
} from '@/types/api/sms/advert'; } from '@/types/api/sms/advert';
const queryFormRef = ref(ElForm); const queryFormRef = ref(ElForm); // 属性名必须和元素的ref属性值一致
const dataFormRef = ref(ElForm); const dataFormRef = ref(ElForm); // 属性名必须和元素的ref属性值一致
const state = reactive({ const state = reactive({
loading: true, loading: true,
@@ -40,13 +40,13 @@ const state = reactive({
dialog: { title: '', visible: false } as Dialog, dialog: { title: '', visible: false } as Dialog,
formData: { formData: {
status: 1, status: 1,
sort: 100 sort: 100,
} as AdvertFormData, } as AdvertFormData,
rules: { rules: {
title: [{ required: true, message: '请输入广告名称', trigger: 'blur' }], title: [{ required: true, message: '请输入广告名称', trigger: 'blur' }],
picUrl: [{ required: true, message: '请上传广告图片', trigger: 'blur' }] picUrl: [{ required: true, message: '请上传广告图片', trigger: 'blur' }],
}, },
validityPeriod: '' as any validityPeriod: '' as any,
}); });
const { const {
@@ -58,7 +58,7 @@ const {
dialog, dialog,
formData, formData,
rules, rules,
validityPeriod validityPeriod,
} = toRefs(state); } = toRefs(state);
function handleQuery() { function handleQuery() {
@@ -84,14 +84,14 @@ function handleSelectionChange(selection: any) {
function handleAdd() { function handleAdd() {
state.dialog = { state.dialog = {
title: '添加广告', title: '添加广告',
visible: true visible: true,
}; };
} }
function handleUpdate(row: any) { function handleUpdate(row: any) {
state.dialog = { state.dialog = {
title: '修改广告', title: '修改广告',
visible: true visible: true,
}; };
const advertId = row.id || state.ids; const advertId = row.id || state.ids;
getAdvertFormDetail(advertId).then(({ data }) => { getAdvertFormDetail(advertId).then(({ data }) => {
@@ -138,7 +138,7 @@ function handleDelete(row: any) {
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', { ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning',
}) })
.then(() => { .then(() => {
deleteAdverts(ids).then(() => { deleteAdverts(ids).then(() => {

View File

@@ -1,7 +1,7 @@
<!--优惠券--> <!--优惠券-->
<script lang="ts"> <script lang="ts">
export default { export default {
name: 'coupon' name: 'coupon',
}; };
</script> </script>
@@ -14,7 +14,7 @@ import {
getCouponFormData, getCouponFormData,
updateCoupon, updateCoupon,
addCoupon, addCoupon,
deleteCoupons deleteCoupons,
} from '@/api/sms/coupon'; } from '@/api/sms/coupon';
import { listCategoryOptions } from '@/api/pms/category'; import { listCategoryOptions } from '@/api/pms/category';
@@ -23,7 +23,7 @@ import { Dialog, Option } from '@/types/common';
import { import {
CouponItem, CouponItem,
CouponQueryParam, CouponQueryParam,
CouponFormData CouponFormData,
} from '@/types/api/sms/coupon'; } from '@/types/api/sms/coupon';
import { GoodsItem, GoodsQueryParam } from '@/types/api/pms/goods'; import { GoodsItem, GoodsQueryParam } from '@/types/api/pms/goods';
@@ -40,38 +40,38 @@ const state = reactive({
couponList: [] as CouponItem[], couponList: [] as CouponItem[],
total: 0, total: 0,
dialog: { dialog: {
visible: false visible: false,
} as Dialog, } as Dialog,
//指定商品分类选择Dialog //指定商品分类选择Dialog
spuCategoryChooseDialog: { spuCategoryChooseDialog: {
visible: false visible: false,
} as Dialog, } as Dialog,
// 指定商品选择ialog // 指定商品选择ialog
spuChooseDialog: { spuChooseDialog: {
visible: false visible: false,
} as Dialog, } as Dialog,
formData: { formData: {
type: 1, type: 1,
platform: 0, platform: 0,
validityPeriodType: 1, validityPeriodType: 1,
perLimit: 1, perLimit: 1,
applicationScope: 0 applicationScope: 0,
} as CouponFormData, } as CouponFormData,
rules: { rules: {
type: [{ required: true, message: '请输入优惠券名称', trigger: 'blur' }], type: [{ required: true, message: '请输入优惠券名称', trigger: 'blur' }],
name: [{ required: true, message: '请选择优惠券类型', trigger: 'blur' }] name: [{ required: true, message: '请选择优惠券类型', trigger: 'blur' }],
}, },
validityPeriod: '' as any, validityPeriod: '' as any,
perLimitChecked: false, perLimitChecked: false,
spuCategoryOptions: [] as Option[], spuCategoryOptions: [] as Option[],
spuCategoryProps: { spuCategoryProps: {
multiple: true, multiple: true,
emitPath: false emitPath: false,
}, },
spuList: [] as GoodsItem[], spuList: [] as GoodsItem[],
spuTotal: 0, spuTotal: 0,
spuQueryParams: { pageNum: 1, pageSize: 10 } as GoodsQueryParam, spuQueryParams: { pageNum: 1, pageSize: 10 } as GoodsQueryParam,
checkedSpuIds: [] checkedSpuIds: [],
}); });
const { const {
@@ -89,7 +89,7 @@ const {
spuCategoryProps, spuCategoryProps,
spuList, spuList,
spuTotal, spuTotal,
checkedSpuIds checkedSpuIds,
} = toRefs(state); } = toRefs(state);
/** /**
@@ -136,7 +136,7 @@ async function loadSpuList() {
function handleAdd() { function handleAdd() {
dialog.value = { dialog.value = {
title: '新增优惠券', title: '新增优惠券',
visible: true visible: true,
}; };
loadSpuCategoryOptions(); loadSpuCategoryOptions();
@@ -146,7 +146,7 @@ function handleAdd() {
async function handleUpdate(row: any) { async function handleUpdate(row: any) {
dialog.value = { dialog.value = {
title: '编辑优惠券', title: '编辑优惠券',
visible: true visible: true,
}; };
const id = row.id; const id = row.id;
@@ -230,7 +230,7 @@ function handleDelete(row: any) {
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', { ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning',
}) })
.then(() => { .then(() => {
deleteCoupons(ids).then(() => { deleteCoupons(ids).then(() => {
@@ -492,7 +492,7 @@ onMounted(() => {
:titles="['商品列表', '已选择商品']" :titles="['商品列表', '已选择商品']"
:props="{ :props="{
key: 'id', key: 'id',
label: 'name' label: 'name',
}" }"
> >
<template #left-footer> <template #left-footer>

View File

@@ -142,16 +142,12 @@ function submitForm() {
}); });
} }
function resetForm() { function cancel() {
state.dialog.visible = false;
dataFormRef.value.resetFields(); dataFormRef.value.resetFields();
state.checkedAuthorizedGrantTypes = []; state.checkedAuthorizedGrantTypes = [];
} }
function cancel() {
resetForm();
state.dialog.visible = false;
}
function handleDelete(row: any) { function handleDelete(row: any) {
const clientIds = [row.clientId || ids].join(','); const clientIds = [row.clientId || ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', { ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {

View File

@@ -192,8 +192,10 @@ function handleDelete(row: any) {
* 取消/关闭弹窗 * 取消/关闭弹窗
**/ **/
function cancel() { function cancel() {
dialog.value.visible = false;
formData.value.id = undefined;
dataFormRef.value.resetFields(); dataFormRef.value.resetFields();
state.dialog.visible = false; dataFormRef.value.clearValidate();
} }
onMounted(() => { onMounted(() => {

View File

@@ -44,6 +44,16 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="菜单类型" align="center" width="100">
<template #default="scope">
<el-tag v-if="scope.row.type === 'MENU'" type="success">菜单</el-tag>
<el-tag v-if="scope.row.type === 'CATALOG'" type="warning"
>目录</el-tag
>
<el-tag v-if="scope.row.type === 'EXTLINK'" type="info">外链</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" width="100"> <el-table-column label="状态" align="center" width="100">
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.visible === 1" type="success">显示</el-tag> <el-tag v-if="scope.row.visible === 1" type="success">显示</el-tag>
@@ -261,7 +271,7 @@ const state = reactive({
name: '', name: '',
visible: 1, visible: 1,
sort: 1, sort: 1,
component: 'Layout', component: undefined,
type: 'MENU', type: 'MENU',
} as MenuFormData, } as MenuFormData,
rules: { rules: {
@@ -333,8 +343,12 @@ function handleRowClick(row: any) {
emit('menuClick', row); emit('menuClick', row);
} }
/**
* 新增菜单
* @param row
*/
async function handleAdd(row: any) { async function handleAdd(row: any) {
state.formData.id = undefined; formData.value.id = undefined;
await loadMenuData(); await loadMenuData();
state.dialog = { state.dialog = {
title: '添加菜单', title: '添加菜单',
@@ -342,7 +356,6 @@ async function handleAdd(row: any) {
}; };
if (row.id) { if (row.id) {
// 行点击新增 // 行点击新增
state.formData.parentId = row.id; state.formData.parentId = row.id;
if (row.id == '0') { if (row.id == '0') {
state.formData.type = 'CATALOG'; state.formData.type = 'CATALOG';
@@ -362,15 +375,15 @@ async function handleAdd(row: any) {
} }
/** /**
* 修改弹窗 * 编辑菜单
*/ */
async function handleUpdate(row: any) { async function handleUpdate(row: MenuFormData) {
await loadMenuData(); await loadMenuData();
state.dialog = { state.dialog = {
title: '修改菜单', title: '编辑菜单',
visible: true, visible: true,
}; };
const id = row.id || state.ids; const id = row.id as string;
getMenuDetail(id).then(({ data }) => { getMenuDetail(id).then(({ data }) => {
state.formData = data; state.formData = data;
cacheData.value.menuType = data.type; cacheData.value.menuType = data.type;
@@ -379,10 +392,10 @@ async function handleUpdate(row: any) {
} }
/** /**
* 菜单类型change事件 * 菜单类型 change
*/ */
function handleMenuTypeChange(val: any) { function handleMenuTypeChange(menuType: any) {
if (val !== cacheData.value.menuType) { if (menuType !== cacheData.value.menuType) {
formData.value.path = ''; formData.value.path = '';
} else { } else {
formData.value.path = cacheData.value.menuPath; formData.value.path = cacheData.value.menuPath;
@@ -412,6 +425,11 @@ function submitForm() {
}); });
} }
/**
* 删除菜单
*
* @param row
*/
function handleDelete(row: any) { function handleDelete(row: any) {
const ids = [row.id || state.ids].join(','); const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', { ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {

View File

@@ -25,6 +25,7 @@ import {
PermItem, PermItem,
PermQueryParam, PermQueryParam,
} from '@/types/api/system/perm'; } from '@/types/api/system/perm';
import { MenuItem } from '@/types/api/system/menu';
const { proxy }: any = getCurrentInstance(); const { proxy }: any = getCurrentInstance();
@@ -32,19 +33,23 @@ const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm); const dataFormRef = ref(ElForm);
const props = defineProps({ const props = defineProps({
menuId: { menu: {
type: String, type: Object,
default: () => { default: () => {
return ''; return {} as MenuItem;
}, },
}, },
}); });
watch( watch(
() => props.menuId, () => props.menu,
(value) => { (value) => {
state.queryParams.menuId = value; queryParams.value.menuId = value.id;
console.log('menu', value);
handleQuery(); handleQuery();
},
{
deep: true,
} }
); );
@@ -193,7 +198,7 @@ function submitForm() {
state.urlPerm.requestPath; state.urlPerm.requestPath;
} }
state.formData.menuId = props.menuId; formData.value.menuId = props.menu.id;
if (state.formData.id) { if (state.formData.id) {
updatePerm(state.formData.id, state.formData).then(() => { updatePerm(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功'); ElMessage.success('修改成功');
@@ -258,7 +263,7 @@ onMounted(() => {
<el-button <el-button
type="success" type="success"
:icon="Plus" :icon="Plus"
:disabled="!menuId" v-if="menu.id && menu.type == 'MENU'"
@click="handleAdd" @click="handleAdd"
>新增</el-button >新增</el-button
> >
@@ -267,6 +272,7 @@ onMounted(() => {
:icon="Delete" :icon="Delete"
:disabled="multiple" :disabled="multiple"
@click="handleDelete" @click="handleDelete"
v-if="menu.id && menu.type == 'MENU'"
>删除</el-button >删除</el-button
> >
</el-form-item> </el-form-item>

View File

@@ -15,14 +15,14 @@
<template #header> <template #header>
<svg-icon icon-class="perm" /> <svg-icon icon-class="perm" />
<span style="margin: 0 5px">权限列表</span> <span style="margin: 0 5px">权限列表</span>
<el-tag type="success" v-if="menuId" size="small">{{ <el-tag type="success" v-if="menu.id" size="small">{{
menuName menu.name
}}</el-tag> }}</el-tag>
<el-tag type="warning" v-else size="small" <el-link :underline="false" type="warning" v-else size="small"
>请点击左侧菜单列表选择</el-tag ><el-icon><WarningFilled /></el-icon>请选中左侧菜单</el-link
> >
</template> </template>
<perm-table :menuId="menuId" :menuName="menuName" /> <perm-table :menu="menu" />
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
@@ -35,21 +35,23 @@ import MenuTable from './components/Menu.vue';
import PermTable from './components/Perm.vue'; import PermTable from './components/Perm.vue';
import { reactive, toRefs } from 'vue'; import { reactive, toRefs } from 'vue';
import { WarningFilled } from '@element-plus/icons-vue';
import { MenuItem } from '@/types/api/system/menu';
const state = reactive({ const state = reactive({
menuId: undefined, menu: {} as MenuItem,
menuName: '',
}); });
const { menuId, menuName } = toRefs(state); const { menu } = toRefs(state);
function handleMenuClick(menuRow: any) { function handleMenuClick(menuRow: MenuItem) {
if (menuRow) { if (menuRow) {
state.menuId = menuRow.id; menu.value.id = menuRow.id;
state.menuName = menuRow.name; menu.value.type = menuRow.type;
menu.value.name = menuRow.name;
} else { } else {
state.menuId = undefined; menu.value.id = undefined;
state.menuName = ''; menu.value.type = undefined;
menu.value.name = '';
} }
} }
</script> </script>

View File

@@ -5,17 +5,17 @@ export default {
</script> </script>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref, toRefs, nextTick } from 'vue'; import { nextTick, onMounted, reactive, ref, toRefs } from 'vue';
import { import {
listRolePages, listRolePages,
updateRole, updateRole,
getRoleFormDetail, getRoleFormDetail,
addRole, addRole,
deleteRoles, deleteRoles,
getRoleResourceIds, getRoleResources,
updateRoleResource, updateRoleResource,
} from '@/api/system/role'; } from '@/api/system/role';
import { getResource } from '@/api/system/menu'; import { listResources } from '@/api/system/menu';
import { ElForm, ElMessage, ElMessageBox, ElTree } from 'element-plus'; import { ElForm, ElMessage, ElMessageBox, ElTree } from 'element-plus';
import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue'; import { Search, Plus, Edit, Refresh, Delete } from '@element-plus/icons-vue';
@@ -24,6 +24,7 @@ import {
RoleItem, RoleItem,
RoleQueryParam, RoleQueryParam,
} from '@/types/api/system/role'; } from '@/types/api/system/role';
import { Resource } from '@/types/api/system/menu';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
const emit = defineEmits(['roleClick']); const emit = defineEmits(['roleClick']);
@@ -55,8 +56,11 @@ const state = reactive({
code: [{ required: true, message: '请输入角色编码', trigger: 'blur' }], code: [{ required: true, message: '请输入角色编码', trigger: 'blur' }],
}, },
resourceDialogVisible: false, resourceDialogVisible: false,
menuOptions: [] as any[], resourceOptions: [] as Resource[],
permOptions: [] as any[], btnPerms: {} as any,
// 勾选的菜单ID
checkedMenuIds: new Set([]),
allPermIds: [] as string[],
checkedRole: { checkedRole: {
id: '', id: '',
name: '', name: '',
@@ -73,9 +77,9 @@ const {
formData, formData,
rules, rules,
resourceDialogVisible, resourceDialogVisible,
menuOptions,
permOptions,
checkedRole, checkedRole,
resourceOptions,
btnPerms,
} = toRefs(state); } = toRefs(state);
function handleQuery() { function handleQuery() {
@@ -87,7 +91,9 @@ function handleQuery() {
state.loading = false; state.loading = false;
}); });
} }
/**
* 查询重置
*/
function resetQuery() { function resetQuery() {
queryFormRef.value.resetFields(); queryFormRef.value.resetFields();
handleQuery(); handleQuery();
@@ -148,8 +154,10 @@ function submitFormData() {
* 取消 * 取消
*/ */
function cancel() { function cancel() {
state.dialog.visible = false; dialog.value.visible = false;
formData.value.id = undefined;
dataFormRef.value.resetFields(); dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
} }
/** /**
@@ -171,13 +179,26 @@ function handleDelete(row: any) {
.catch(() => ElMessage.info('已取消删除')); .catch(() => ElMessage.info('已取消删除'));
} }
const handleResourceCheckChange = (
data: Resource,
isCheck: boolean,
sonHasCheck: boolean
) => {
console.log('data', data);
console.log('isCheck', isCheck);
if (data.perms) {
data.perms.forEach((item) => {
btnPerms.value[item.value] = isCheck;
});
}
};
/** /**
* 分配资源权限 * 分配资源(菜单+权限)弹窗
*/ */
function handleResourceAssign(row: RoleItem) { function openRoleResourceDialog(row: RoleItem) {
resourceDialogVisible.value = true; resourceDialogVisible.value = true;
loading.value = true; loading.value = true;
permOptions.value.map((item) => (item.checked = false));
const roleId: any = row.id; const roleId: any = row.id;
checkedRole.value = { checkedRole.value = {
@@ -185,47 +206,72 @@ function handleResourceAssign(row: RoleItem) {
name: row.name, name: row.name,
}; };
//资源下拉数据 // 获取所有的资源
getResource().then((response) => { listResources().then((response) => {
state.menuOptions = response.data.menus; resourceOptions.value = response.data;
state.permOptions = response.data.perms;
// 获取角色拥有的资源数据进行勾选 // 获取角色拥有的资源
getRoleResourceIds(roleId).then((res) => { getRoleResources(roleId).then(({ data }) => {
const checkedMenuIds = res.data.menuIds; // 勾选的菜单回显
const checkedPermIds = res.data.permIds; const checkedMenuIds = data.menuIds;
resourceRef.value.setCheckedKeys(checkedMenuIds); resourceRef.value.setCheckedKeys(checkedMenuIds);
permOptions.value.forEach((perm) => { nextTick(() => {
if (checkedPermIds.includes(perm.value)) { // 勾选的权限回显
perm.checked = true; const rolePermIds = data.permIds;
state.allPermIds = filterResourcePermIds(response.data, []);
if (state.allPermIds) {
state.allPermIds.forEach((permId) => {
if (rolePermIds.indexOf(permId) > -1) {
btnPerms.value[permId] = true;
} else { } else {
perm.checked = false; btnPerms.value[permId] = false;
} }
}); });
}
loading.value = false; loading.value = false;
}); });
}); });
});
} }
const filterResourcePermIds = (resources: Resource[], permIds: string[]) => {
resources.forEach((resource) => {
if (resource.perms) {
resource.perms.forEach((perm) => {
permIds.push(perm.value);
});
}
if (resource.children) {
filterResourcePermIds(resource.children, permIds);
}
});
return permIds;
};
/** /**
* 分配资源权限提交 * 分配资源提交
*/ */
function handleRoleResourceSubmit() { function handleRoleResourceSubmit() {
const checkedMenuIds: any[] = resourceRef.value const checkedMenuIds: any[] = resourceRef.value
.getCheckedNodes(false, true) .getCheckedNodes(false, true)
.map((node: any) => node.value); .map((node: any) => node.value);
const checkedPermIds = state.permOptions const checkedPermIds = [] as string[];
.filter((item) => item.checked) if (state.allPermIds) {
.map((item) => item.value); state.allPermIds.forEach((permId) => {
if (btnPerms.value[permId]) {
checkedPermIds.push(permId);
}
});
}
const roleResourceData = { const RoleResource = {
menuIds: checkedMenuIds, menuIds: checkedMenuIds,
permIds: checkedPermIds, permIds: checkedPermIds,
}; };
updateRoleResource(checkedRole.value.id, roleResourceData).then((res) => { updateRoleResource(checkedRole.value.id, RoleResource).then((res) => {
ElMessage.success('分配权限成功'); ElMessage.success('分配权限成功');
state.resourceDialogVisible = false; state.resourceDialogVisible = false;
handleQuery(); handleQuery();
@@ -291,14 +337,16 @@ onMounted(() => {
<el-table-column label="角色编码" prop="code" /> <el-table-column label="角色编码" prop="code" />
<el-table-column label="操作" align="center" width="200"> <el-table-column label="操作" align="center" width="200">
<template #default="scope"> <template #default="scope">
<el-tooltip content="分配资源" effect="light">
<el-button <el-button
type="primary" type="success"
circle circle
plain plain
@click.stop="handleResourceAssign(scope.row)" @click.stop="openRoleResourceDialog(scope.row)"
> >
<svg-icon icon-class="perm" /> <svg-icon icon-class="perm" />
</el-button> </el-button>
</el-tooltip>
<el-button <el-button
type="primary" type="primary"
@@ -373,37 +421,38 @@ onMounted(() => {
</template> </template>
</el-dialog> </el-dialog>
<!--分配权限弹窗--> <!--分配资源弹窗-->
<el-dialog <el-dialog
:title="'【' + checkedRole.name + '】分配权限'" :title="'角色【' + checkedRole.name + '】资源分配'"
v-model="resourceDialogVisible" v-model="resourceDialogVisible"
width="1000px" width="800px"
> >
<el-scrollbar max-height="600px" v-loading="loading"> <el-scrollbar max-height="600px" v-loading="loading">
<el-tree <el-tree
ref="resourceRef" ref="resourceRef"
node-key="value" node-key="value"
show-checkbox show-checkbox
:data="menuOptions" :data="resourceOptions"
:default-expand-all="true" :default-expand-all="true"
@check-change="handleResourceCheckChange"
> >
<template #default="{ node, data }"> <template #default="{ data }">
<div v-if="data.isPerm == true" class="resource-tree-node"> {{ data.label }}
<div class="resource-tree-node__content">
<div v-if="data.perms" class="resource-tree-node">
<el-divider direction="vertical" />
<div class="node-content">
<el-checkbox <el-checkbox
v-for="perm in permOptions.filter( v-for="perm in data.perms"
(perm) => perm.parentId == data.permPid
)"
:key="perm.value" :key="perm.value"
:label="perm.value" :label="perm.value"
v-model="perm.checked"
border border
size="small" size="small"
v-model="btnPerms[perm.value]"
>{{ perm.label }}</el-checkbox >{{ perm.label }}</el-checkbox
> >
</div> </div>
</div> </div>
<span v-else>{{ node.label }}</span>
</template> </template>
</el-tree> </el-tree>
</el-scrollbar> </el-scrollbar>
@@ -425,18 +474,16 @@ onMounted(() => {
width: 100%; width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
display: flex; display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px; font-size: 14px;
padding-right: 8px; justify-content: flex-end;
margin-left: -28px !important; margin: 0 50px;
.node-content {
&__content { width: 400px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.el-checkbox--default { .el-divider--vertical {
background-color: transparent !important; height: 2em !important;
} }
} }
.el-tree-node__content { .el-tree-node__content {

View File

@@ -348,9 +348,10 @@ function handleDelete(row: { [key: string]: any }) {
* 取消 * 取消
*/ */
function cancel() { function cancel() {
state.dialog.visible = false; dialog.value.visible = false;
state.formData.id = undefined; formData.value.id = undefined;
dataFormRef.value.resetFields(); dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
} }
/** /**