feat(权限控制): 实现按钮级权限控制功能

添加按钮权限控制功能,包括:
1. 新增权限存储模块和权限获取API
2. 在App.vue初始化时获取权限
3. 在多个页面组件中添加权限检查逻辑
4. 移除角色管理页面中不必要的分类显示
5. 在店铺表单中添加二维码和链接复制功能
This commit is contained in:
dzq 2025-06-18 16:57:33 +08:00
parent 1d82d1a5be
commit a2a615d18d
9 changed files with 93 additions and 20 deletions

View File

@ -1,11 +1,16 @@
<script setup lang="ts">
import { computed } from "vue";
import { computed, onMounted } from "vue";
import { ElConfigProvider } from "element-plus";
import zhCn from "element-plus/lib/locale/lang/zh-cn";
import { ReDialog } from "@/components/ReDialog";
import { useBtnPermissionStore } from "@/store/modules/btnPermission";
const currentLocale = computed(() => zhCn);
const btnPermissionStore = useBtnPermissionStore();
onMounted(async () => {
await btnPermissionStore.fetchPermissions();
})
</script>
<template>

View File

@ -188,3 +188,8 @@ export interface QyUserLoginDTO {
/** 角色名称 */
roleName?: string;
}
/** 获取动态菜单 */
export const getPermissions = () => {
return http.request<ResponseData<string[]>>("get", "/getPermissions");
};

View File

@ -0,0 +1,31 @@
import { defineStore } from "pinia";
import { ref } from "vue";
import { store } from "@/store";
import { getPermissions } from "@/api/common/login";
export const useBtnPermissionStore = defineStore("btnPermission", () => {
const btnPermissions = ref<string[]>([]);
const fetchPermissions = async () => {
const res = await getPermissions();
btnPermissions.value = res.data;
}
const hasPermission = (permission: string) => {
return btnPermissions.value.includes(permission);
}
return {
btnPermissions,
fetchPermissions,
hasPermission,
};
});
/**
* @description setup 使 store
*/
export function useBtnPermissionStoreOutside() {
return useBtnPermissionStore(store)
}

View File

@ -5,6 +5,8 @@ import { paymentMethodOptions, modeToPaymentMethodMap } from "@/utils/maps/payme
import { addShop, updateShop, ShopDTO, UpdateShopCommand, AddShopCommand } from "@/api/shop/shop";
import { useWxStore } from "@/store/modules/wx";
import Upload from "@iconify-icons/ep/upload";
import ReQrcode from "@/components/ReQrcode";
import { copyTextToClipboard } from "@pureadmin/utils";
const { VITE_APP_BASE_API } = import.meta.env;
const props = defineProps({
@ -120,6 +122,16 @@ const currentPaymentMethods = computed(() => {
return option ? { label: option.label, type: option.type } : null;
}).filter(Boolean);
});
const copyLink = () => {
if (!formData.value.shopId) {
ElMessage.warning("店铺ID不存在无法复制链接");
return;
}
const url = `http://wxshop.ab98.cn/shop-api/api/shop/wechatAuth?shopId=${formData.value.shopId}`;
const success = copyTextToClipboard(url);
success ? ElMessage.success('链接复制成功') : ElMessage.error('复制失败,请手动复制');
};
</script>
<template>
@ -175,15 +187,23 @@ const currentPaymentMethods = computed(() => {
</el-select>
</el-form-item> -->
<el-form-item label="支付方式">
<el-tag
v-for="method in currentPaymentMethods"
:key="method.label"
:type="method.type"
style="margin-right: 5px;"
>
<el-tag v-for="method in currentPaymentMethods" :key="method.label" :type="method.type"
style="margin-right: 5px;">
{{ method.label }}
</el-tag>
</el-form-item>
<el-form-item label="机柜数量">
{{ props.row.cabinetCount || 0 }}
</el-form-item>
<el-form-item label="二维码">
<div class="flex flex-col items-center">
<ReQrcode :text="`http://wxshop.ab98.cn/shop-api/api/shop/wechatAuth?shopId=${formData.shopId}`"
:options="{ width: 150 }" :width="150"/>
<el-button type="primary" size="small" @click="copyLink" style="margin-left: 10px;">
复制链接
</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose" style="margin-right: 5px;">取消</el-button>

View File

@ -23,6 +23,7 @@ import { getGoodsInfo } from "@/api/shop/goods";
import EditCabinetDrawer from "./edit-cabinet-drawer.vue";
import { CabinetMainboardDTO, deleteMainboard, getMainboardList, updateMainboard } from "@/api/cabinet/mainboards";
import { getShopById, ShopDTO, getModeText } from "@/api/shop/shop";
import { useBtnPermissionStore } from "@/store/modules/btnPermission";
defineOptions({
name: "SmartCabinetDetail"
@ -30,6 +31,7 @@ defineOptions({
const router = useRouter();
const route = useRoute();
const { hasPermission } = useBtnPermissionStore();
const currentCell = ref<CabinetCellDTO>();
const cabinetInfo = ref<SmartCabinetDTO>({
cabinetName: "",
@ -308,7 +310,7 @@ onMounted(() => {
<div class="info-details" v-if="activeTab === 'basic'">
<div style="display: flex; justify-content: flex-end; margin-bottom: 16px;">
<el-button type="primary" link :icon="useRenderIcon(EditPen)" @click="editCabinetDrawerVisible = true">
<el-button v-if="hasPermission('shop:cabinet:write')" type="primary" link :icon="useRenderIcon(EditPen)" @click="editCabinetDrawerVisible = true">
编辑柜体
</el-button>
</div>
@ -318,11 +320,11 @@ onMounted(() => {
<el-descriptions-item label="柜体格式">{{ CabinetImgMap[cabinetInfo.templateNo]?.name || '-'
}}</el-descriptions-item>
<el-descriptions-item label="柜体地址">{{ cabinetInfo.shopName || '-' }}
<el-button type="success" link @click="shopConfigVisible = true">
<el-button v-if="hasPermission('shop:cabinet:write')" type="success" link @click="shopConfigVisible = true">
配置
</el-button></el-descriptions-item>
<el-descriptions-item label="柜体网关">{{ cabinetInfo.mqttServerId || '-' }}
<el-button type="warning" link @click="gatewayConfigVisible = true">
<el-button v-if="hasPermission('shop:cabinet:write')" type="warning" link @click="gatewayConfigVisible = true">
配置
</el-button></el-descriptions-item>
<el-descriptions-item label="借呗支付">{{ getBalanceEnableText(shopInfo.balanceEnable)
@ -378,11 +380,13 @@ onMounted(() => {
<span class="line-number">{{ item.cellNo }}</span>
</div>
<div class="action-buttons">
<el-button v-if="!item.goodsId" link type="success"
<el-button v-if="!item.goodsId" :disabled="!hasPermission('shop:cabinet:write')"
link type="success"
@click="handleConfigure(item)" class="cell-btn">
商品配置
</el-button>
<el-button v-if="item.goodsId" link type="primary"
<el-button v-if="item.goodsId" :disabled="!hasPermission('shop:cabinet:write')"
link type="primary"
@click="handleEditCell(item)" class="cell-btn">
{{ item.goodsName }}
</el-button>
@ -410,10 +414,10 @@ onMounted(() => {
<el-table-column label="锁控板序号" prop="lockControlNo" width="120" />
<el-table-column label="操作" width="150" fixed="right">
<template #default="{ row }">
<el-button type="primary" link :icon="useRenderIcon(EditPen)" @click="handleEditMainboard(row)">
<el-button v-if="hasPermission('shop:cabinet:write')" type="primary" link :icon="useRenderIcon(EditPen)" @click="handleEditMainboard(row)">
编辑
</el-button>
<el-button type="danger" link :icon="useRenderIcon(Delete)" @click="handleDeleteMainboard(row)">
<el-button v-if="hasPermission('shop:cabinet:write')" type="danger" link :icon="useRenderIcon(Delete)" @click="handleDeleteMainboard(row)">
删除
</el-button>
</template>
@ -434,7 +438,8 @@ onMounted(() => {
@refresh="fetchCabinetDetail" />
</el-drawer>
<el-drawer v-model="goodsConfigVisible" title="配置商品" size="50%" direction="rtl">
<CabinetGoodsConfigModal v-model="goodsConfigVisible" :cell-id="currentCellId" :belong-type="shopInfo.mode == 4 ? 1 : 0" @refresh="fetchCellList" />
<CabinetGoodsConfigModal v-model="goodsConfigVisible" :cell-id="currentCellId"
:belong-type="shopInfo.mode == 4 ? 1 : 0" @refresh="fetchCellList" />
</el-drawer>
<el-drawer v-model="configuredGoodsVisible" title="管理商品" size="30%" direction="rtl">
<ConfiguredGoodsModal v-model="configuredGoodsVisible" :cell-id="currentCell?.cellId"

View File

@ -14,6 +14,7 @@ import AddFill from "@iconify-icons/ri/add-circle-line";
import SmartCabinetCardFormModal from "./smart-cabinet-card-form-modal.vue";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { getModeText } from "@/api/shop/shop";
import { useBtnPermissionStore } from "@/store/modules/btnPermission";
const { VITE_PUBLIC_IMG_PATH: IMG_PATH } = import.meta.env;
defineOptions({
@ -21,6 +22,7 @@ defineOptions({
});
const router = useRouter();
const { hasPermission } = useBtnPermissionStore();
const formRef = ref();
const modalVisible = ref(false);
const searchFormParams = ref({
@ -115,7 +117,7 @@ onMounted(() => {
<el-form-item class="space-item">
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true"
<el-button v-if="hasPermission('shop:cabinet:write')" type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true"
style="margin-right: 10px;">
新增设备
</el-button>

View File

@ -7,6 +7,7 @@ import { ElMessage } from "element-plus";
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
import EditPen from '@iconify-icons/ep/edit-pen';
import GoodsEditModal from "./goods-edit-modal.vue";
import { useBtnPermissionStore } from "@/store/modules/btnPermission";
const handleEdit = (row: GoodsDTO) => {
goodsInfo.value = row;
@ -21,6 +22,8 @@ defineOptions({
const router = useRouter();
const route = useRoute();
const { hasPermission } = useBtnPermissionStore();
const goodsInfo = ref<GoodsDTO>({
goodsId: 0,
goodsName: "",
@ -118,7 +121,7 @@ watch(goodsId, () => {
<el-tab-pane label="基本信息" name="basic"></el-tab-pane>
<el-tab-pane label="购买记录" name="order"></el-tab-pane>
</el-tabs>
<el-button v-if="goodsInfo.belongType == 0" type="primary" @click="handleEdit(goodsInfo)"
<el-button v-if="goodsInfo.belongType == 0 && hasPermission('shop:goods:write')" type="primary" @click="handleEdit(goodsInfo)"
style="margin-bottom: 12px" :size="'default'">
编辑商品
</el-button>

View File

@ -17,6 +17,7 @@ import { deleteGoodsApi } from "@/api/shop/goods";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { useRouter } from "vue-router";
import { useWxStore } from "@/store/modules/wx";
import { useBtnPermissionStore } from "@/store/modules/btnPermission";
defineOptions({
name: "ShopGoods"
@ -24,6 +25,7 @@ defineOptions({
const wxStore = useWxStore();
const router = useRouter();
const { hasPermission } = useBtnPermissionStore();
const formRef = ref();
const tableRef = ref();
const modalVisible = ref(false);
@ -184,7 +186,7 @@ const handleViewDetail = (row: GoodsDTO) => {
<el-form-item class="space-item">
</el-form-item>
<el-form-item>
<el-button type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true"
<el-button v-if="hasPermission('shop:goods:write')" type="primary" :icon="useRenderIcon(AddFill)" @click="modalVisible = true"
style="margin-right: 10px;">
新增商品
</el-button>

View File

@ -229,7 +229,7 @@ onSearch().then(() => {
</template>
<el-checkbox-group v-model="formData.menuIds" class="checkbox-group">
<template v-for="menu in processedMenuOptions" :key="menu.categoryName">
<span class="menu-category">{{ menu.categoryName }}</span>
<!-- <span class="menu-category">{{ menu.categoryName }}</span> -->
<el-checkbox v-for="item in menu.items" :key="item.id" :label="item.id" class="menu-checkbox">
{{ item.menuName }}
</el-checkbox>