路一步步走>> 设计模式二十三:Template-模板

本文深入探讨了设计模式中的模板模式,通过具体代码示例解释了其工作原理及应用场景。模板模式允许在不改变算法结构的情况下,重定义算法的某些步骤,适用于需要统一算法流程但细节可变的场景。
package com.test.DPs.XingWei.Template;
/**
 * 行为型:Template-模板		外观:作用面为 对象
 * 
 * 用途:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
 * 理解:相对于Strategy模式:接口 和 抽象类。例子:排序和计算。
 */
abstract class AbstractCalculator{
	abstract int calculate(int a, int b);
	int[] split(String exp, String opt){       
		String array[] = exp.split(opt);  
	    int arrayInt[] = new int[2];  
	    arrayInt[0] = Integer.parseInt(array[0]);  
	    arrayInt[1] = Integer.parseInt(array[1]);  
	    return arrayInt;  
    }		
	public final int calculate(String exp, String opt){
		int array[] = split(exp, opt);
		return calculate(array[0], array[1]);
	}
}
class Plus extends AbstractCalculator{
	@Override
	int calculate(int a, int b){
		return a+b;
	}
}

 

<template> <!-- 不要在里面加业务功能 组件不支持的能力 复制重写--> <Transition> <div v-show="visible" id="drop-down" ref="dropDownListBoxRef"> <div class="drop-down-box"> <div class="drop-down-box__body"> <div class="drop-cascader__header"> <div v-for="(i, idx) in areaType" :key="idx" class="header__item"> {{ $t(i.name) }} </div> </div> <div class="drop-cascader__body"> <ul v-for="(item, index) in areaType" :key="index" class="menu__list"> <slot :name="index" :item="item"> <template v-for="(i, idx) in item.list" :key="idx"> <li :class="['list-item', handleLight(i.nodeName)]" @click.stop="onClick(i.nodeName, index)" > <p>{{ name(item.name, i) }}</p> <el-icon> <ArrowRight /> </el-icon> </li> </template> </slot> </ul> </div> </div> <div class="line"></div> <div class="drop-down-box__footer"> <slot name="footerLeft"></slot> <div> <slot name="footerRight"></slot> <el-button class="cancel" @click.stop="onCancel"> {{ $t('取消') }} </el-button> <el-button type="primary" class="primary" @click="onConfirm"> {{ $t('确定') }} </el-button> </div> </div> </div> </div> </Transition> </template> <script lang="ts" setup> import { defineProps, defineEmits, withDefaults, computed, onMounted, onBeforeUnmount, ref, } from 'vue'; import { getI18n } from '@/util/i18n'; import { ArrowRight } from '@element-plus/icons-vue'; import { IArea, IList } from './composable/type'; import { DC } from './composable/config'; interface Props { modelValue?: boolean; areaType: IArea[]; geographicalLevel: string[]; } const props = withDefaults(defineProps<Props>(), { modelValue: true, areaType: () => [], geographicalLevel: () => [], }); interface Emits { (e: 'update:modelValue', modelValue: boolean): void; (e: 'onClick', nodeName: string, index: number): void; (e: 'onConfirm'): void; (e: 'onCancel'): void; } const emit = defineEmits<Emits>(); const { dt } = getI18n(); // 是否展开 const visible = computed({ get() { return props.modelValue; }, set(value) { emit('update:modelValue', value); }, }); const onClick = (nodeName: string, index: number) => { emit('onClick', nodeName, index); }; const onConfirm = () => { emit('onConfirm'); emit('update:modelValue', false); }; const onCancel = () => { emit('onCancel'); emit('update:modelValue', false); }; const handleLight = nodeName => (props.geographicalLevel.includes(nodeName) ? 'activate' : ''); const name = (type: string, item: IList) => { if (type === DC && item.building) { return `${dt(item, 'name')}_${item.building}`; } else { return dt(item, 'name'); } }; const dropDownListBoxRef = ref(); const clickOutside = (event: Event) => { if ( props.modelValue && (!dropDownListBoxRef.value || !dropDownListBoxRef.value.contains(event.target)) ) { emit('update:modelValue', false); } }; onMounted(() => { document.addEventListener('click', clickOutside, true); }); onBeforeUnmount(() => { document.removeEventListener('click', clickOutside, true); }); </script> <style lang="less" scoped> @import '@/styles/commonCmptStyle.less'; #getCommonPopperStyleId(drop-down); #drop-down { position: absolute; top: 44px; display: flex; flex-direction: column; align-items: center; transition: all 0.3s; z-index: 15; ::-webkit-scrollbar { width: 4px; } ::-webkit-scrollbar-thumb { width: 4px; } .drop-down-box { padding: 16px 16px 0; z-index: 3; min-width: 550px; min-height: 359px; background-color: var(--backgroundColor1); border-radius: 8px; display: flex; flex-direction: column; .drop-down-box__body { flex: 3; display: flex; flex-direction: column; position: relative; .drop-cascader__header { display: flex; align-items: center; padding-bottom: 12px; border-bottom: 1px solid var(--borderColor); .header__item { width: 132px !important; height: 100%; font-size: 14px; font-family: Microsoft YaHei, Microsoft YaHei-Bold, serif; color: var(--color1); font-weight: bold; display: flex; align-items: center; padding-left: 16px; &:last-child { border-right: 0; } } } .drop-cascader__body { flex: 3; display: flex; .menu__list { border-right: 0.5px solid var(--borderColor); width: 132px !important; height: 260px; overflow-y: auto; padding: 4px 3px; .list-item { display: flex; justify-content: space-between; align-items: center; min-height: 28px; padding: 0 8px 0 13px; border-radius: 0; font-size: 14px; font-family: Microsoft YaHei, Microsoft YaHei-Normal, serif; font-weight: normal; text-align: LEFT; color: var(--color2); line-height: 20px; word-break: break-all; cursor: pointer; &:not(.activate):hover { background: var(--backgroundColor2-hover) !important; } } .activate { background: var(--backgroundColor2) !important; color: var(--color3); font-weight: 700; .el-icon { color: var(--color3); } } &:last-child { border-right: 0; } } } } .line { width: 100%; height: 0.5px; background-color: var(--borderColor); } .drop-down-box__footer { height: 52px; display: flex; justify-content: space-between; align-items: center; button { padding: 0 18px; height: 28px; border-radius: 2px !important; } } } } .v-enter-active, .v-leave-active { transition: all 0.3s; } .v-enter-from, .v-leave-to { transform: translateY(-30px); opacity: 0; } </style> 这是<RegionalSearchBox>组件的代码
最新发布
10-28
非常感谢你提供了完整的 `<RegionalSearchBox>` 组件代码,这让我们可以 **精准地将权限控制逻辑集成进去**。 --- ## ✅ 目标回顾 我们要实现: > 在 **单选模式** 和 **多选模式** 下: - 用户角色为 `'amira.Infrastructure.assetSecurity'` 时 - 只能查看和操作自己有权限的区域(`own: true`) - 无权限的区域应:**置灰 + 禁止点击** - 权限数据来自接口 `/api/user/data-permissions` --- ## ✅ 当前组件分析 ### 📌 结构概览 ```vue <template> <div v-for="(item, index) in areaType" :key="index"> <ul class="menu__list"> <slot :name="index" :item="item"> <li v-for="i in item.list" @click="onClick(i.nodeName, index)"> {{ name(...) }} </li> </slot> </ul> </div> </template> ``` 👉 关键点: 1. 区域列表通过 `areaType` 传入; 2. 每一层是一个 `<ul>`,渲染 `item.list`; 3. 点击事件由父级通过 `@onClick` 接收; 4. 插槽允许外部自定义内容(比如多选组件); 5. 所以真正的“权限过滤”必须在 **父级传进来的 `areaType` 数据中处理好**。 --- ## ✅ 解决方案设计 由于这个组件是 **通用下拉级联选择器**,它不应该感知权限逻辑。 ✅ 正确做法是:**在父组件中预处理 `areaType` 数据,再传给它** --- ## ✅ 修改策略(三步走) ### ✅ Step 1: 父组件使用 `useHeaderAreaSearchHooks` 处理权限 我们已经写好了 `getFilteredRegionList(rawList)` 方法。 现在要对每一层的 `.list` 都做权限过滤。 #### 修改 `useHeaderAreaSearchHooks.ts` 中添加: ```ts // ✅ 新增:根据权限过滤整个 areaType 结构 const getFilteredAreaType = (originalAreaType: IArea[]): IArea[] => { if (!hasAssetSecurityPermission.value) { return originalAreaType; // 非权限用户,返回原始数据 } return originalAreaType.map(level => ({ ...level, list: getFilteredRegionList(level.list), // 使用已有的过滤函数 })); }; // 同时暴露给模板使用 return { // ...其他 getFilteredAreaType, }; ``` --- ### ✅ Step 2: 父组件调用时传入过滤后的 `areaType` 假设你在父组件中这样使用: ```vue <RegionalSearchBox v-model="visible" :area-type="areaType" :geographical-level="geographicalLevel" @on-click="onClick" @on-confirm="onConfirm" > <!-- 插槽 --> <template #[slotIndex]="data"> <MultipleChoiceOfRegions v-model:value="checkList" :arr="getFilteredRegionList(data.item.list)" /> </template> </RegionalSearchBox> ``` → 改为传入 **经过权限过滤的数据**: ```vue <RegionalSearchBox v-model="visible" :area-type="getFilteredAreaType(areaType)" :geographical-level="geographicalLevel" @on-click="onClick" @on-confirm="onConfirm" > <!-- 插槽 --> <template #[slotIndex]="data"> <MultipleChoiceOfRegions v-model:value="checkList" :arr="getFilteredRegionList(data.item.list)" /> </template> </RegionalSearchBox> ``` ✅ 这样即使不进插槽的层级(如第一、二层),也会被权限过滤。 --- ### ✅ Step 3: 修改 `onClick` 增加运行时校验(双重保险) 虽然数据已被过滤,但仍需防止意外点击。 修改 `onClick`: ```ts const onClick = (nodeName: string, index: number) => { if (isCanMultiple.value) return; // ✅ 权限校验:只有 own=true 才能点击 if (hasAssetSecurityPermission.value) { const permission = regionOptions.value.find(p => p.nodeName === nodeName); if (!permission?.own) { ElMessage.warning('您没有该区域的访问权限'); return; } } onBaseClick(nodeName, index); }; ``` --- ## ✅ 最终效果 | 场景 | 行为 | |------|------| | 用户有“华南”权限 | 显示且可点击 | | 用户无“华东”权限 | 不显示 or 显示但置灰不可点(取决于 UI 设计) | | 多选模式 | 插槽内组件接收的是过滤后数据 | | 单选模式 | 所有层级都只展示有权区域 | --- ## ✅ 可选:让无权限项显示但禁用(更友好) 如果你希望“让用户知道存在但不能访问”,可以在 CSS 上加提示: ### 修改 `<RegionalSearchBox>` 的 `<li>` 样式判断 ```vue <li v-for="(i, idx) in item.list" :key="idx" :class="['list-item', handleLight(i.nodeName), i.disabled ? 'disabled' : '']" @click.stop="!i.disabled && onClick(i.nodeName, index)" > <p>{{ name(item.name, i) }}</p> <el-icon><ArrowRight /></el-icon> </li> ``` ### 添加样式 ```less .list-item.disabled { color: #ccc !important; cursor: not-allowed !important; background: transparent !important; pointer-events: none; } ``` 这样视觉上能看出“这里有东西,但我不能点”。 --- ## ✅ 总结:你现在完成了 | 功能 | 实现方式 | |------|----------| | 全局权限控制 | 在 `useHeaderAreaSearchHooks` 中统一管理 | | 单选权限拦截 | `onClick` 前判断 `regionOptions` 是否 `own=true` | | 多选权限控制 | 插槽中使用 `getFilteredRegionList` | | 每一层权限过滤 | 父组件传入 `getFilteredAreaType(areaType)` | | 安全性保障 | 数据层 + 事件层双重校验 | --- ## ✅ 示例:最终父组件调用方式 ```vue <template> <RegionalSearchBox v-model="visible" :area-type="filteredAreaType" :geographical-level="geographicalLevel" @on-click="onClick" @on-confirm="onConfirm" > <!-- 多选插槽 --> <template #[slotIndex]="data"> <MultipleChoiceOfRegions v-model:value="checkList" :arr="getFilteredRegionList(data.item.list)" /> </template> </RegionalSearchBox> </template> <script setup lang="ts"> import { useHeaderAreaSearchHooks } from './composable/useHeaderAreaSearchHooks'; const { areaType, geographicalLevel, isCanMultiple, slotIndex, checkList, getFilteredAreaType, getFilteredRegionList, onClick, onConfirm, } = useHeaderAreaSearchHooks(); // ✅ 过滤后的 areaType 传给子组件 const filteredAreaType = computed(() => getFilteredAreaType(areaType.value)); </script> ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百草疯茂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值