Pre Merge pull request !333 from puhui999/master-fix

pull/333/MERGE
puhui999 2026-03-04 02:21:36 +00:00 committed by Gitee
commit 880225a892
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
9 changed files with 85 additions and 82 deletions

View File

@ -2,18 +2,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { handleTree } from '@vben/utils';
import { Cascader } from 'ant-design-vue'; import { Cascader } from 'ant-design-vue';
import { getAreaTree } from '#/api/system/area'; import { getAreaTree } from '#/api/system/area';
import { AreaLevelEnum } from '@vben/constants';
defineOptions({ name: 'AreaSelect' }); defineOptions({ name: 'AreaSelect' });
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
modelValue: undefined, modelValue: undefined,
value: undefined, value: undefined,
level: 3, level: AreaLevelEnum.DISTRICT,
disabled: false, disabled: false,
placeholder: '请选择省市区', placeholder: '请选择省市区',
clearable: true, clearable: true,
@ -41,7 +40,7 @@ interface AreaVO {
interface Props { interface Props {
modelValue?: number[] | string[]; modelValue?: number[] | string[];
value?: number[] | string[]; value?: number[] | string[];
level?: 1 | 2 | 3; // 1- 2- 3- level?: typeof AreaLevelEnum[keyof typeof AreaLevelEnum];
disabled?: boolean; disabled?: boolean;
placeholder?: string; placeholder?: string;
clearable?: boolean; clearable?: boolean;
@ -69,7 +68,7 @@ async function loadAreaTree(): Promise<void> {
try { try {
loading.value = true; loading.value = true;
const data = await getAreaTree(); const data = await getAreaTree();
// level // level
areaTree.value = filterTreeByLevel(data || [], props.level); areaTree.value = filterTreeByLevel(data || [], props.level);
} catch (error) { } catch (error) {
@ -83,10 +82,10 @@ async function loadAreaTree(): Promise<void> {
// //
function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] { function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
if (maxLevel <= 0) return []; if (maxLevel <= 0) return [];
return tree.map((node) => { return tree.map((node) => {
const newNode = { ...node }; const newNode = { ...node };
// , children // , children
if (maxLevel === 1) { if (maxLevel === 1) {
delete newNode.children; delete newNode.children;
@ -94,7 +93,7 @@ function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
// //
newNode.children = filterTreeByLevel(node.children, maxLevel - 1); newNode.children = filterTreeByLevel(node.children, maxLevel - 1);
} }
return newNode; return newNode;
}); });
} }
@ -106,7 +105,7 @@ function handleChange(value: number[] | undefined): void {
emit('update:value', undefined); emit('update:value', undefined);
return; return;
} }
emit('update:modelValue', value); emit('update:modelValue', value);
emit('update:value', value); emit('update:value', value);
} }
@ -114,22 +113,22 @@ function handleChange(value: number[] | undefined): void {
// modelValue value // modelValue value
function syncSelectedValue(): void { function syncSelectedValue(): void {
const newValue = props.modelValue || props.value; const newValue = props.modelValue || props.value;
if (newValue === undefined || newValue === null) { if (newValue === undefined || newValue === null) {
selectedValue.value = undefined; selectedValue.value = undefined;
return; return;
} }
// //
if (Array.isArray(newValue)) { selectedValue.value = Array.isArray(newValue)
selectedValue.value = newValue as number[]; ? (newValue as number[])
} else { : [newValue as number];
selectedValue.value = [newValue as number];
}
} }
// modelValue value // modelValue value
watch(() => props.modelValue || props.value, syncSelectedValue, { immediate: true }); watch(() => props.modelValue || props.value, syncSelectedValue, {
immediate: true,
});
// //
onMounted(async () => { onMounted(async () => {

View File

@ -1,6 +1,8 @@
<!-- 网页 iframe 组件 (Ant Design Vue 版本) --> <!-- 网页 iframe 组件 (Ant Design Vue 版本) -->
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from 'vue'; import { computed } from 'vue';
import { isUrl } from '#/utils';
defineOptions({ name: 'IframeComponent' }); defineOptions({ name: 'IframeComponent' });
@ -16,11 +18,6 @@ const props = withDefaults(defineProps<Props>(), {
sandbox: '', sandbox: '',
}); });
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
(e: 'update:value', value: string): void;
}>();
// //
interface Props { interface Props {
modelValue?: string; modelValue?: string;
@ -36,23 +33,14 @@ interface Props {
} }
// URL使 url prop使 value modelValue // URL使 url prop使 value modelValue
const displayUrl = computed(() => props.url || props.value || props.modelValue || ''); const displayUrl = computed(
() => props.url || props.value || props.modelValue || '',
);
// //
const showPreview = computed(() => { const showPreview = computed(() => {
return displayUrl.value && isValidUrl(displayUrl.value); return displayUrl.value && isUrl(displayUrl.value);
}); });
// URL
function isValidUrl(url: string): boolean {
if (!url || url.trim() === '') return false;
try {
const urlObj = new URL(url);
return urlObj.protocol === 'http:' || urlObj.protocol === 'https:';
} catch {
return false;
}
}
</script> </script>
<template> <template>
@ -104,4 +92,3 @@ function isValidUrl(url: string): boolean {
background-color: #fafafa; background-color: #fafafa;
} }
</style> </style>

View File

@ -1,9 +1,8 @@
import { cloneDeep } from '@vben/utils';
import { import {
localeProps, localeProps,
makeRequiredRule, makeRequiredRule,
} from '#/components/form-create/helpers'; } from '#/components/form-create/helpers';
import { AreaLevelEnum } from '@vben/constants';
/** 省市区选择器规则 */ /** 省市区选择器规则 */
export function useAreaSelectRule() { export function useAreaSelectRule() {
@ -31,11 +30,11 @@ export function useAreaSelectRule() {
type: 'select', type: 'select',
field: 'level', field: 'level',
title: '选择层级', title: '选择层级',
value: 3, value: AreaLevelEnum.DISTRICT,
options: [ options: [
{ label: '省', value: 1 }, { label: '省', value: AreaLevelEnum.PROVINCE },
{ label: '省/市', value: 2 }, { label: '省/市', value: AreaLevelEnum.CITY },
{ label: '省/市/区', value: 3 }, { label: '省/市/区', value: AreaLevelEnum.DISTRICT },
], ],
info: '限制可选择的地区层级', info: '限制可选择的地区层级',
}, },

View File

@ -27,3 +27,16 @@ export const findIndex = <T = Recordable<any>>(
}); });
return index; return index;
}; };
/**
* URL
* @param path URL
*/
export const isUrl = (path: string): boolean => {
// fix:修复hash路由无法跳转的问题
/* eslint-disable regexp/no-unused-capturing-group, regexp/no-super-linear-backtracking, regexp/no-useless-quantifier */
const reg =
/(((^https?:(?:\/\/)?)(?:[-:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%#/.\w-]*)?\??[-+=&%@.\w]*(?:#\w*)?)?)$/;
return reg.test(path);
/* eslint-enable regexp/no-unused-capturing-group, regexp/no-super-linear-backtracking, regexp/no-useless-quantifier */
};

View File

@ -2,17 +2,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { handleTree } from '@vben/utils';
import { ElCascader } from 'element-plus'; import { ElCascader } from 'element-plus';
import { getAreaTree } from '#/api/system/area'; import { getAreaTree } from '#/api/system/area';
import { AreaLevelEnum } from '@vben/constants';
defineOptions({ name: 'AreaSelect' }); defineOptions({ name: 'AreaSelect' });
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
modelValue: undefined, modelValue: undefined,
level: 3, level: AreaLevelEnum.DISTRICT,
disabled: false, disabled: false,
placeholder: '请选择省市区', placeholder: '请选择省市区',
clearable: true, clearable: true,
@ -38,7 +37,7 @@ interface AreaVO {
// //
interface Props { interface Props {
modelValue?: number[] | string[]; modelValue?: number[] | string[];
level?: 1 | 2 | 3; // 1- 2- 3- level?: typeof AreaLevelEnum[keyof typeof AreaLevelEnum];
disabled?: boolean; disabled?: boolean;
placeholder?: string; placeholder?: string;
clearable?: boolean; clearable?: boolean;
@ -68,7 +67,7 @@ async function loadAreaTree(): Promise<void> {
try { try {
loading.value = true; loading.value = true;
const data = await getAreaTree(); const data = await getAreaTree();
// level // level
areaTree.value = filterTreeByLevel(data || [], props.level); areaTree.value = filterTreeByLevel(data || [], props.level);
} catch (error) { } catch (error) {
@ -82,10 +81,10 @@ async function loadAreaTree(): Promise<void> {
// //
function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] { function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
if (maxLevel <= 0) return []; if (maxLevel <= 0) return [];
return tree.map((node) => { return tree.map((node) => {
const newNode = { ...node }; const newNode = { ...node };
// children // children
if (maxLevel === 1) { if (maxLevel === 1) {
delete newNode.children; delete newNode.children;
@ -93,7 +92,7 @@ function filterTreeByLevel(tree: AreaVO[], maxLevel: number): AreaVO[] {
// //
newNode.children = filterTreeByLevel(node.children, maxLevel - 1); newNode.children = filterTreeByLevel(node.children, maxLevel - 1);
} }
return newNode; return newNode;
}); });
} }
@ -104,25 +103,23 @@ function handleChange(value: number[] | undefined): void {
emit('update:modelValue', undefined); emit('update:modelValue', undefined);
return; return;
} }
emit('update:modelValue', value); emit('update:modelValue', value);
} }
// modelValue // modelValue
function syncSelectedValue(): void { function syncSelectedValue(): void {
const newValue = props.modelValue; const newValue = props.modelValue;
if (newValue === undefined || newValue === null) { if (newValue === undefined || newValue === null) {
selectedValue.value = undefined; selectedValue.value = undefined;
return; return;
} }
// //
if (Array.isArray(newValue)) { selectedValue.value = Array.isArray(newValue)
selectedValue.value = newValue as number[]; ? (newValue as number[])
} else { : [newValue as number];
selectedValue.value = [newValue as number];
}
} }
// modelValue // modelValue

View File

@ -1,6 +1,8 @@
<!-- 网页 iframe 组件 (Element Plus 版本) --> <!-- 网页 iframe 组件 (Element Plus 版本) -->
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref, watch } from 'vue'; import { computed } from 'vue';
import { isUrl } from '#/utils';
defineOptions({ name: 'IframeComponent' }); defineOptions({ name: 'IframeComponent' });
@ -15,10 +17,6 @@ const props = withDefaults(defineProps<Props>(), {
sandbox: '', sandbox: '',
}); });
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
}>();
// //
interface Props { interface Props {
modelValue?: string; modelValue?: string;
@ -37,19 +35,8 @@ const displayUrl = computed(() => props.url || props.modelValue || '');
// //
const showPreview = computed(() => { const showPreview = computed(() => {
return displayUrl.value && isValidUrl(displayUrl.value); return displayUrl.value && isUrl(displayUrl.value);
}); });
// URL
function isValidUrl(url: string): boolean {
if (!url || url.trim() === '') return false;
try {
const urlObj = new URL(url);
return urlObj.protocol === 'http:' || urlObj.protocol === 'https:';
} catch {
return false;
}
}
</script> </script>
<template> <template>

View File

@ -1,9 +1,8 @@
import { cloneDeep } from '@vben/utils';
import { import {
localeProps, localeProps,
makeRequiredRule, makeRequiredRule,
} from '#/components/form-create/helpers'; } from '#/components/form-create/helpers';
import { AreaLevelEnum } from '@vben/constants';
/** 省市区选择器规则 */ /** 省市区选择器规则 */
export function useAreaSelectRule() { export function useAreaSelectRule() {
@ -31,11 +30,11 @@ export function useAreaSelectRule() {
type: 'select', type: 'select',
field: 'level', field: 'level',
title: '选择层级', title: '选择层级',
value: 3, value: AreaLevelEnum.DISTRICT,
options: [ options: [
{ label: '省', value: 1 }, { label: '省', value: AreaLevelEnum.PROVINCE },
{ label: '省/市', value: 2 }, { label: '省/市', value: AreaLevelEnum.CITY },
{ label: '省/市/区', value: 3 }, { label: '省/市/区', value: AreaLevelEnum.DISTRICT },
], ],
info: '限制可选择的地区层级', info: '限制可选择的地区层级',
}, },

View File

@ -28,3 +28,16 @@ export const findIndex = <T = Recordable<any>>(
}); });
return index; return index;
}; };
/**
* URL
* @param path URL
*/
export const isUrl = (path: string): boolean => {
// fix:修复hash路由无法跳转的问题
/* eslint-disable regexp/no-unused-capturing-group, regexp/no-super-linear-backtracking, regexp/no-useless-quantifier */
const reg =
/(((^https?:(?:\/\/)?)(?:[-:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%#/.\w-]*)?\??[-+=&%@.\w]*(?:#\w*)?)?)$/;
return reg.test(path);
/* eslint-enable regexp/no-unused-capturing-group, regexp/no-super-linear-backtracking, regexp/no-useless-quantifier */
};

View File

@ -57,3 +57,12 @@ export const SystemUserSocialTypeEnum = {
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png', img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png',
}, },
}; };
/**
*
*/
export const AreaLevelEnum = {
PROVINCE: 1, // 省
CITY: 2, // 市
DISTRICT: 3, // 区
} as const;