Merge remote-tracking branch 'yudao/dev' into dev

This commit is contained in:
jason
2025-11-20 16:27:47 +08:00
31 changed files with 219 additions and 243 deletions

View File

@@ -254,14 +254,13 @@ function open() {
function set() {
defaultValue.value = props.modelValue;
let arr = (props.modelValue || '* * * * * ?').split(' ');
/** 简单检查 */
// 简单检查
if (arr.length < 6) {
message.warning('cron表达式错误已转换为默认表达式');
arr = '* * * * * ?'.split(' ');
}
/** 秒 */
// 秒
if (arr[0] === '*') {
cronValue.second.type = '0';
} else if (arr[0]?.includes('-')) {
@@ -277,7 +276,7 @@ function set() {
cronValue.second.appoint = arr[0]?.split(',') || [];
}
/** 分 */
// 分
if (arr[1] === '*') {
cronValue.minute.type = '0';
} else if (arr[1]?.includes('-')) {
@@ -293,7 +292,7 @@ function set() {
cronValue.minute.appoint = arr[1]?.split(',') || [];
}
/** 小时 */
// 小时
if (arr[2] === '*') {
cronValue.hour.type = '0';
} else if (arr[2]?.includes('-')) {
@@ -309,21 +308,18 @@ function set() {
cronValue.hour.appoint = arr[2]?.split(',') || [];
}
/** 日 */
// 日
switch (arr[3]) {
case '*': {
cronValue.day.type = '0';
break;
}
case '?': {
cronValue.day.type = '5';
break;
}
case 'L': {
cronValue.day.type = '4';
break;
}
default: {
@@ -342,7 +338,7 @@ function set() {
}
}
/** 月 */
// 月
if (arr[4] === '*') {
cronValue.month.type = '0';
} else if (arr[4]?.includes('-')) {
@@ -358,7 +354,7 @@ function set() {
cronValue.month.appoint = arr[4]?.split(',') || [];
}
/** 周 */
// 周
if (arr[5] === '*') {
cronValue.week.type = '0';
} else if (arr[5] === '?') {
@@ -379,7 +375,7 @@ function set() {
cronValue.week.appoint = arr[5]?.split(',') || [];
}
/** 年 */
// 年
if (!arr[6]) {
cronValue.year.type = '-1';
} else if (arr[6] === '*') {

View File

@@ -284,7 +284,6 @@ async function handleOk() {
class="h-full w-full object-cover"
/>
</div>
<!-- 头像组合预览 -->
<template v-if="previewSource">
<div

View File

@@ -8,37 +8,24 @@ import type { Recordable } from '@vben/types';
export interface DescriptionItemSchema {
labelMinWidth?: number;
contentMinWidth?: number;
// 自定义标签样式
labelStyle?: CSSProperties;
// 对应 data 中的字段名
field: string;
// 内容的描述
label: JSX.Element | string | VNode;
// 包含列的数量
span?: number;
// 是否显示
show?: (...arg: any) => boolean;
// 插槽名称
slot?: string;
// 自定义需要展示的内容
labelStyle?: CSSProperties; // 自定义标签样式
field: string; // 对应 data 中的字段名
label: JSX.Element | string | VNode; // 内容的描述
span?: number; // 包含列的数量
show?: (...arg: any) => boolean; // 是否显示
slot?: string; // 插槽名称
render?: (
val: any,
data?: Recordable<any>,
) => Element | JSX.Element | number | string | undefined | VNode;
) => Element | JSX.Element | number | string | undefined | VNode; // 自定义需要展示的内容
}
export interface DescriptionProps extends DescriptionsProps {
// 是否包含卡片组件
useCard?: boolean;
// 描述项配置
schema: DescriptionItemSchema[];
// 数据
data: Recordable<any>;
// 标题
title?: string;
// 是否包含边框
bordered?: boolean;
// 列数
useCard?: boolean; // 是否包含卡片组件
schema: DescriptionItemSchema[]; // 描述项配置
data: Recordable<any>; // 数据
title?: string; // 标题
bordered?: boolean; // 是否包含边框
column?:
| number
| {
@@ -48,7 +35,7 @@ export interface DescriptionProps extends DescriptionsProps {
xl: number;
xs: number;
xxl: number;
};
}; // 列数
}
export interface DescInstance {

View File

@@ -7,18 +7,9 @@ import { isValidColor, TinyColor } from '@vben/utils';
import { Tag } from 'ant-design-vue';
interface DictTagProps {
/**
* 字典类型
*/
type: string;
/**
* 字典值
*/
value: any;
/**
* 图标
*/
icon?: string;
type: string; // 字典类型
value: any; // 字典
icon?: string; // 图标
}
const props = defineProps<DictTagProps>();

View File

@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<DictSelectProps>(), {
const attrs = useAttrs();
// 获得字典配置
/** 获得字典配置 */
const getDictOption = computed(() => {
switch (props.valueType) {
case 'bool': {

View File

@@ -16,7 +16,7 @@ export function useImagesUpload() {
},
},
setup() {
// TODO: @dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
// TODO: @puhui999@dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
return (props: { maxNumber?: number; multiple?: boolean }) => (
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
);

View File

@@ -19,12 +19,12 @@ import {
useUploadImagesRule,
} from './rules';
// 编码表单 Conf
/** 编码表单 Conf */
export function encodeConf(designerRef: any) {
return JSON.stringify(designerRef.value.getOption());
}
// 编码表单 Fields
/** 编码表单 Fields */
export function encodeFields(designerRef: any) {
const rule = JSON.parse(designerRef.value.getJson());
const fields: string[] = [];
@@ -34,7 +34,7 @@ export function encodeFields(designerRef: any) {
return fields;
}
// 解码表单 Fields
/** 解码表单 Fields */
export function decodeFields(fields: string[]) {
const rule: Rule[] = [];
fields.forEach((item) => {
@@ -43,7 +43,7 @@ export function decodeFields(fields: string[]) {
return rule;
}
// 设置表单的 Conf 和 Fields适用 FcDesigner 场景
/** 设置表单的 Conf 和 Fields适用 FcDesigner 场景 */
export function setConfAndFields(
designerRef: any,
conf: string,
@@ -55,7 +55,7 @@ export function setConfAndFields(
designerRef.value.setRule(decodeFields(fieldsArray));
}
// 设置表单的 Conf 和 Fields适用 form-create 场景
/** 设置表单的 Conf 和 Fields适用 form-create 场景 */
export function setConfAndFields2(
detailPreview: any,
conf: string,
@@ -155,9 +155,7 @@ export async function useFormCreateDesigner(designer: Ref) {
const uploadImageRule = useUploadImageRule();
const uploadImagesRule = useUploadImagesRule();
/**
* 构建表单组件
*/
/** 构建表单组件 */
function buildFormComponents() {
// 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代
designer.value?.removeMenuItem('upload');
@@ -200,9 +198,7 @@ export async function useFormCreateDesigner(designer: Ref) {
event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus'],
});
/**
* 构建系统字段菜单
*/
/** 构建系统字段菜单 */
function buildSystemMenu() {
// 移除自带的下拉选择器组件,使用 currencySelectRule 替代
// designer.value?.removeMenuItem('select')

View File

@@ -11,9 +11,7 @@ import {
} from '#/components/form-create/helpers';
import { selectRule } from '#/components/form-create/rules/data';
/**
* 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule
*/
/** 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule */
export function useDictSelectRule() {
const label = '字典选择器';
const name = 'DictSelect';

View File

@@ -137,11 +137,11 @@ function handleButtonClick(action: ActionItem) {
}
}
/** 监听props变化强制重新计算 */
/** 监听 props 变化,强制重新计算 */
watch(
() => [props.actions, props.dropDownActions],
() => {
// 这里不需要额外处理computed会自动重新计算
// 这里不需要额外处理computed 会自动重新计算
},
{ deep: true },
);

View File

@@ -17,10 +17,9 @@ const props = defineProps({
type: Boolean,
},
fullscreen: {
// 图片上传,是否放到全屏的位置
default: false,
type: Boolean,
},
}, // 图片上传,是否放到全屏的位置
});
const emit = defineEmits(['uploading', 'done', 'error']);

View File

@@ -31,6 +31,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue']);
// TODO @puhui999可以参考 apps/web-antd/src/components/cron-tab/cron-tab.vue 简化到 typesps可以用 idea 对比两个 ts 或者 vue 文件,看看差异的地方。差异的地方越少越好(容易维护)
interface shortcutsType {
text: string;
value: string;
@@ -46,6 +47,7 @@ const getYear = () => {
}
return v;
};
// TODO @puhui999可以参考 apps/web-antd/src/components/cron-tab/cron-tab.vue 简化到 types
const cronValue = reactive({
second: {
type: '0',
@@ -275,6 +277,7 @@ const value_second = computed(() => {
}
}
});
const value_minute = computed(() => {
const v = cronValue.minute;
switch (v.type) {
@@ -295,6 +298,7 @@ const value_minute = computed(() => {
}
}
});
const value_hour = computed(() => {
const v = cronValue.hour;
switch (v.type) {
@@ -315,6 +319,7 @@ const value_hour = computed(() => {
}
}
});
const value_day = computed(() => {
const v = cronValue.day;
switch (v.type) {
@@ -341,6 +346,7 @@ const value_day = computed(() => {
}
}
});
const value_month = computed(() => {
const v = cronValue.month;
switch (v.type) {
@@ -361,6 +367,7 @@ const value_month = computed(() => {
}
}
});
const value_week = computed(() => {
const v = cronValue.week;
switch (v.type) {
@@ -387,6 +394,7 @@ const value_week = computed(() => {
}
}
});
const value_year = computed(() => {
const v = cronValue.year;
switch (v.type) {
@@ -410,48 +418,56 @@ const value_year = computed(() => {
}
}
});
watch(
() => cronValue.week.type,
(val) => {
(val: string) => {
if (val !== '5') {
cronValue.day.type = '5';
}
},
);
watch(
() => cronValue.day.type,
(val) => {
(val: string) => {
if (val !== '5') {
cronValue.week.type = '5';
}
},
);
watch(
() => props.modelValue,
() => {
defaultValue.value = props.modelValue;
},
);
onMounted(() => {
defaultValue.value = props.modelValue;
});
const select = ref();
const select = ref<string>();
watch(
() => select.value,
() => {
if (select.value === 'custom') {
open();
} else {
defaultValue.value = select.value;
defaultValue.value = select.value || '';
emit('update:modelValue', defaultValue.value);
}
},
);
const open = () => {
function open() {
set();
dialogVisible.value = true;
};
const set = () => {
}
function set() {
defaultValue.value = props.modelValue;
let arr = (props.modelValue || '* * * * * ?').split(' ');
// 简单检查
@@ -463,145 +479,149 @@ const set = () => {
// 秒
if (arr[0] === '*') {
cronValue.second.type = '0';
} else if (arr[0]!.includes('-')) {
} else if (arr[0]?.includes('-')) {
cronValue.second.type = '1';
cronValue.second.range.start = Number(arr[0]!.split('-')[0]);
cronValue.second.range.end = Number(arr[0]!.split('-')[1]);
} else if (arr[0]!.includes('/')) {
cronValue.second.range.start = Number(arr[0].split('-')[0]);
cronValue.second.range.end = Number(arr[0].split('-')[1]);
} else if (arr[0]?.includes('/')) {
cronValue.second.type = '2';
cronValue.second.loop.start = Number(arr[0]!.split('/')[0]);
cronValue.second.loop.end = Number(arr[0]!.split('/')[1]);
cronValue.second.loop.start = Number(arr[0].split('/')[0]);
cronValue.second.loop.end = Number(arr[0].split('/')[1]);
} else {
cronValue.second.type = '3';
cronValue.second.appoint = arr[0]!.split(',');
cronValue.second.appoint = arr[0]?.split(',') || [];
}
// 分
if (arr[1] === '*') {
cronValue.minute.type = '0';
} else if (arr[1]!.includes('-')) {
} else if (arr[1]?.includes('-')) {
cronValue.minute.type = '1';
cronValue.minute.range.start = Number(arr[1]!.split('-')[0]);
cronValue.minute.range.end = Number(arr[1]!.split('-')[1]);
} else if (arr[1]!.includes('/')) {
cronValue.minute.range.start = Number(arr[1].split('-')[0]);
cronValue.minute.range.end = Number(arr[1].split('-')[1]);
} else if (arr[1]?.includes('/')) {
cronValue.minute.type = '2';
cronValue.minute.loop.start = Number(arr[1]!.split('/')[0]);
cronValue.minute.loop.end = Number(arr[1]!.split('/')[1]);
cronValue.minute.loop.start = Number(arr[1].split('/')[0]);
cronValue.minute.loop.end = Number(arr[1].split('/')[1]);
} else {
cronValue.minute.type = '3';
cronValue.minute.appoint = arr[1]!.split(',');
cronValue.minute.appoint = arr[1]?.split(',') || [];
}
// 小时
if (arr[2] === '*') {
cronValue.hour.type = '0';
} else if (arr[2]!.includes('-')) {
} else if (arr[2]?.includes('-')) {
cronValue.hour.type = '1';
cronValue.hour.range.start = Number(arr[2]!.split('-')[0]);
cronValue.hour.range.end = Number(arr[2]!.split('-')[1]);
} else if (arr[2]!.includes('/')) {
cronValue.hour.range.start = Number(arr[2].split('-')[0]);
cronValue.hour.range.end = Number(arr[2].split('-')[1]);
} else if (arr[2]?.includes('/')) {
cronValue.hour.type = '2';
cronValue.hour.loop.start = Number(arr[2]!.split('/')[0]);
cronValue.hour.loop.end = Number(arr[2]!.split('/')[1]);
cronValue.hour.loop.start = Number(arr[2].split('/')[0]);
cronValue.hour.loop.end = Number(arr[2].split('/')[1]);
} else {
cronValue.hour.type = '3';
cronValue.hour.appoint = arr[2]!.split(',');
cronValue.hour.appoint = arr[2]?.split(',') || [];
}
// 日
switch (arr[3]) {
case '*': {
cronValue.day.type = '0';
break;
}
case '?': {
cronValue.day.type = '5';
break;
}
case 'L': {
cronValue.day.type = '4';
break;
}
default: {
if (arr[3]!.includes('-')) {
if (arr[3]?.includes('-')) {
cronValue.day.type = '1';
cronValue.day.range.start = Number(arr[3]!.split('-')[0]);
cronValue.day.range.end = Number(arr[3]!.split('-')[1]);
} else if (arr[3]!.includes('/')) {
cronValue.day.range.start = Number(arr[3].split('-')[0]);
cronValue.day.range.end = Number(arr[3].split('-')[1]);
} else if (arr[3]?.includes('/')) {
cronValue.day.type = '2';
cronValue.day.loop.start = Number(arr[3]!.split('/')[0]);
cronValue.day.loop.end = Number(arr[3]!.split('/')[1]);
cronValue.day.loop.start = Number(arr[3].split('/')[0]);
cronValue.day.loop.end = Number(arr[3].split('/')[1]);
} else {
cronValue.day.type = '3';
cronValue.day.appoint = arr[3]!.split(',');
cronValue.day.appoint = arr[3]?.split(',') || [];
}
}
}
// 月
if (arr[4] === '*') {
cronValue.month.type = '0';
} else if (arr[4]!.includes('-')) {
} else if (arr[4]?.includes('-')) {
cronValue.month.type = '1';
cronValue.month.range.start = Number(arr[4]!.split('-')[0]);
cronValue.month.range.end = Number(arr[4]!.split('-')[1]);
} else if (arr[4]!.includes('/')) {
cronValue.month.range.start = Number(arr[4].split('-')[0]);
cronValue.month.range.end = Number(arr[4].split('-')[1]);
} else if (arr[4]?.includes('/')) {
cronValue.month.type = '2';
cronValue.month.loop.start = Number(arr[4]!.split('/')[0]);
cronValue.month.loop.end = Number(arr[4]!.split('/')[1]);
cronValue.month.loop.start = Number(arr[4].split('/')[0]);
cronValue.month.loop.end = Number(arr[4].split('/')[1]);
} else {
cronValue.month.type = '3';
cronValue.month.appoint = arr[4]!.split(',');
cronValue.month.appoint = arr[4]?.split(',') || [];
}
// 周
if (arr[5] === '*') {
cronValue.week.type = '0';
} else if (arr[5] === '?') {
cronValue.week.type = '5';
} else if (arr[5]!.includes('-')) {
} else if (arr[5]?.includes('-')) {
cronValue.week.type = '1';
cronValue.week.range.start = arr[5]!.split('-')[0]!;
cronValue.week.range.end = arr[5]!.split('-')[1]!;
} else if (arr[5]!.includes('#')) {
cronValue.week.range.start = arr[5].split('-')[0] || '';
cronValue.week.range.end = arr[5].split('-')[1] || '';
} else if (arr[5]?.includes('#')) {
cronValue.week.type = '2';
cronValue.week.loop.start = Number(arr[5]!.split('#')[1]);
cronValue.week.loop.end = arr[5]!.split('#')[0]!;
} else if (arr[5]!.includes('L')) {
cronValue.week.loop.start = Number(arr[5].split('#')[1]);
cronValue.week.loop.end = arr[5].split('#')[0] || '';
} else if (arr[5]?.includes('L')) {
cronValue.week.type = '4';
cronValue.week.last = arr[5]!.split('L')[0]!;
cronValue.week.last = arr[5].split('L')[0] || '';
} else {
cronValue.week.type = '3';
cronValue.week.appoint = arr[5]!.split(',');
cronValue.week.appoint = arr[5]?.split(',') || [];
}
// 年
if (!arr[6]) {
cronValue.year.type = '-1';
} else if (arr[6] === '*') {
cronValue.year.type = '0';
} else if (arr[6].includes('-')) {
} else if (arr[6]?.includes('-')) {
cronValue.year.type = '1';
cronValue.year.range.start = Number(arr[6].split('-')[0]);
cronValue.year.range.end = Number(arr[6].split('-')[1]);
} else if (arr[6].includes('/')) {
} else if (arr[6]?.includes('/')) {
cronValue.year.type = '2';
cronValue.year.loop.start = Number(arr[6].split('/')[1]);
cronValue.year.loop.end = Number(arr[6].split('/')[0]);
} else {
cronValue.year.type = '3';
cronValue.year.appoint = arr[6].split(',');
cronValue.year.appoint = arr[6]?.split(',') || [];
}
};
const submit = () => {
}
function submit() {
const year = value_year.value ? ` ${value_year.value}` : '';
defaultValue.value = `${value_second.value} ${value_minute.value} ${
value_hour.value
} ${value_day.value} ${value_month.value} ${value_week.value}${year}`;
emit('update:modelValue', defaultValue.value);
dialogVisible.value = false;
};
}
const inputChange = () => {
function inputChange() {
emit('update:modelValue', defaultValue.value);
};
}
</script>
<template>
<ElInput

View File

@@ -27,6 +27,7 @@ const props = withDefaults(defineProps<CropperAvatarProps>(), {
const emit = defineEmits(['update:value', 'change']);
const sourceValue = ref(props.value || '');
// TODO @puhui999这个有办法去掉么
const prefixCls = 'cropper-avatar';
const [CropperModal, modalApi] = useVbenModal({
connectedComponent: cropperModal,
@@ -73,12 +74,16 @@ defineExpose({
</script>
<template>
<!-- TODO @puhui999html 部分看看有没办法和 web-antd/src/components/cropper/cropper-avatar.vue 风格更接近 -->
<!-- 头像容器 -->
<div :class="getClass" :style="getStyle">
<!-- 图片包装器 -->
<div
:class="`${prefixCls}-image-wrapper`"
:style="getImageWrapperStyle"
@click="openModal"
>
<!-- 遮罩层 -->
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
<span
:style="{
@@ -90,8 +95,10 @@ defineExpose({
class="icon-[ant-design--cloud-upload-outlined] text-[#d6d6d6]"
></span>
</div>
<!-- 头像图片 -->
<img v-if="sourceValue" :src="sourceValue" alt="avatar" />
</div>
<!-- 上传按钮 -->
<ElButton
v-if="showBtn"
:class="`${prefixCls}-upload-btn`"
@@ -111,6 +118,7 @@ defineExpose({
</template>
<style lang="scss" scoped>
/* TODO @puhui999要类似 web-antd/src/components/cropper/cropper-avatar.vue 减少 scss通过 tindwind 么? */
.cropper-avatar {
display: inline-block;
text-align: center;

View File

@@ -121,7 +121,9 @@ async function handleOk() {
class="w-[800px]"
>
<div :class="prefixCls">
<!-- 左侧区域 -->
<div :class="`${prefixCls}-left`" class="w-full">
<!-- 裁剪器容器 -->
<div :class="`${prefixCls}-cropper`">
<CropperImage
v-if="src"
@@ -133,6 +135,7 @@ async function handleOk() {
/>
</div>
<!-- 工具栏 -->
<div :class="`${prefixCls}-toolbar`">
<ElUpload
:before-upload="handleBeforeUpload"
@@ -276,7 +279,10 @@ async function handleOk() {
</ElSpace>
</div>
</div>
<!-- 右侧区域 -->
<div :class="`${prefixCls}-right`">
<!-- 预览区域 -->
<div :class="`${prefixCls}-preview`">
<img
v-if="previewSource"
@@ -284,6 +290,7 @@ async function handleOk() {
:src="previewSource"
/>
</div>
<!-- 头像组合预览 -->
<template v-if="previewSource">
<div :class="`${prefixCls}-group`">
<ElAvatar :src="previewSource" size="large" />
@@ -298,6 +305,7 @@ async function handleOk() {
</template>
<style lang="scss">
/* TODO @puhui999要类似 web-antd/src/components/cropper/cropper-avatar.vue 减少 scss通过 tindwind 么? */
.cropper-am {
display: flex;

View File

@@ -33,6 +33,7 @@ const imgElRef = ref<ElRef<HTMLImageElement>>();
const cropper = ref<Cropper | null>();
const isReady = ref(false);
// TODO @puhui999这个有办法去掉么
const prefixCls = 'cropper-image';
const debounceRealTimeCropped = useDebounceFn(realTimeCropped, 80);

View File

@@ -8,30 +8,21 @@ import type { Recordable } from '@vben/types';
export interface DescriptionItemSchema {
labelMinWidth?: number;
contentMinWidth?: number;
// 自定义标签样式
labelStyle?: CSSProperties;
// 对应 data 中的字段名
field: string;
// 内容的描述
label: JSX.Element | string | VNode;
// 包含列的数量
span?: number;
// 是否显示
show?: (...arg: any) => boolean;
// 插槽名称
slot?: string;
// 自定义需要展示的内容
labelStyle?: CSSProperties; // 自定义标签样式
field: string; // 对应 data 中的字段名
label: JSX.Element | string | VNode; // 内容的描述
span?: number; // 包含列的数量
show?: (...arg: any) => boolean; // 是否显示
slot?: string; // 插槽名称
render?: (
val: any,
data?: Recordable<any>,
) => Element | JSX.Element | number | string | undefined | VNode;
) => Element | JSX.Element | number | string | undefined | VNode; // 自定义需要展示的内容
}
export interface DescriptionProps extends ElDescriptionProps {
// 描述项配置
schema: DescriptionItemSchema[];
// 数据
data: Recordable<any>;
schema: DescriptionItemSchema[]; // 描述项配置
data: Recordable<any>; // 数据
}
export interface DescInstance {

View File

@@ -1,24 +1,15 @@
<script setup lang="ts">
import { computed } from 'vue';
// import { isHexColor } from '@/utils/color' // TODO @芋艿:【可优化】增加 cssClass 的处理 https://gitee.com/yudaocode/yudao-ui-admin-vben/blob/v2.4.1/src/components/DictTag/src/DictTag.vue#L60
// import { isHexColor } from '@/utils/color' // TODO @芋艿:【可优化】增加 cssClass 的处理 https://gitee.com/yudaocode/yudao-ui-admin-vben/blob/v2.4.1/src/components/DictTag/src/DictTag.vue#L60 @xingyu这个要适配掉 ele 版本里么?
import { getDictObj } from '@vben/hooks';
import { ElTag } from 'element-plus';
interface DictTagProps {
/**
* 字典类型
*/
type: string;
/**
* 字典值
*/
value: any;
/**
* 图标
*/
icon?: string;
type: string; // 字典类型
value: any; // 字典
icon?: string; // 图标
}
const props = defineProps<DictTagProps>();

View File

@@ -24,8 +24,8 @@ const props = withDefaults(defineProps<DictSelectProps>(), {
const attrs = useAttrs();
// 获得字典配置
const dictOptions = computed(() => {
/** 获得字典配置 */
const getDictOption = computed(() => {
switch (props.valueType) {
case 'bool': {
return getDictOptions(props.dictType, 'boolean');
@@ -46,7 +46,7 @@ const dictOptions = computed(() => {
<template>
<ElSelect v-if="selectType === 'select'" class="w-1/1" v-bind="attrs">
<ElOption
v-for="(dict, index) in dictOptions"
v-for="(dict, index) in getDictOption"
:key="index"
:value="dict.value"
:label="dict.label"
@@ -54,7 +54,7 @@ const dictOptions = computed(() => {
</ElSelect>
<ElRadioGroup v-if="selectType === 'radio'" class="w-1/1" v-bind="attrs">
<ElRadio
v-for="(dict, index) in dictOptions"
v-for="(dict, index) in getDictOption"
:key="index"
:label="dict.value"
>
@@ -67,7 +67,7 @@ const dictOptions = computed(() => {
v-bind="attrs"
>
<ElCheckbox
v-for="(dict, index) in dictOptions"
v-for="(dict, index) in getDictOption"
:key="index"
:label="dict.value"
>

View File

@@ -15,7 +15,7 @@ import {
import { requestClient } from '#/api/request';
export const useApiSelect = (option: ApiSelectProps) => {
export function useApiSelect(option: ApiSelectProps) {
return defineComponent({
name: option.name,
props: {
@@ -285,4 +285,4 @@ export const useApiSelect = (option: ApiSelectProps) => {
);
},
});
};
}

View File

@@ -2,7 +2,7 @@ import { defineComponent } from 'vue';
import ImageUpload from '#/components/upload/image-upload.vue';
export const useImagesUpload = () => {
export function useImagesUpload() {
return defineComponent({
name: 'ImagesUpload',
props: {
@@ -16,10 +16,10 @@ export const useImagesUpload = () => {
},
},
setup() {
// TODO: @dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
// TODO: @puhui999@dhb52 其实还是靠 props 默认参数起作用,没能从 formCreate 传递
return (props: { maxNumber?: number; multiple?: boolean }) => (
<ImageUpload maxNumber={props.maxNumber} multiple={props.multiple} />
);
},
});
};
}

View File

@@ -1,9 +1,13 @@
import type { Rule } from '@form-create/element-ui';
import type { Ref } from 'vue';
import type { Menu } from '#/components/form-create/typing';
import { isRef, nextTick, onMounted } from 'vue';
import formCreate from '@form-create/element-ui';
import { apiSelectRule } from '#/components/form-create/rules/data';
import {
@@ -15,12 +19,12 @@ import {
useUploadImagesRule,
} from './rules';
// 编码表单 Conf
/** 编码表单 Conf */
export function encodeConf(designerRef: any) {
return JSON.stringify(designerRef.value.getOption());
}
// 编码表单 Fields
/** 编码表单 Fields */
export function encodeFields(designerRef: any) {
const rule = JSON.parse(designerRef.value.getJson());
const fields: string[] = [];
@@ -30,28 +34,28 @@ export function encodeFields(designerRef: any) {
return fields;
}
// 解码表单 Fields
/** 解码表单 Fields */
export function decodeFields(fields: string[]) {
const rule: object[] = [];
const rule: Rule[] = [];
fields.forEach((item) => {
rule.push(JSON.parse(item));
rule.push(formCreate.parseJson(item));
});
return rule;
}
// 设置表单的 Conf 和 Fields适用 FcDesigner 场景
/** 设置表单的 Conf 和 Fields适用 FcDesigner 场景 */
export function setConfAndFields(
designerRef: any,
conf: string,
fields: string | string[],
) {
designerRef.value.setOption(JSON.parse(conf));
designerRef.value.setOption(formCreate.parseJson(conf));
// 处理 fields 参数类型,确保传入 decodeFields 的是 string[] 类型
const fieldsArray = Array.isArray(fields) ? fields : [fields];
designerRef.value.setRule(decodeFields(fieldsArray));
}
// 设置表单的 Conf 和 Fields适用 form-create 场景
/** 设置表单的 Conf 和 Fields适用 form-create 场景 */
export function setConfAndFields2(
detailPreview: any,
conf: string,
@@ -61,7 +65,7 @@ export function setConfAndFields2(
if (isRef(detailPreview)) {
detailPreview = detailPreview.value;
}
detailPreview.option = JSON.parse(conf);
detailPreview.option = formCreate.parseJson(conf);
detailPreview.rule = decodeFields(fields);
if (value) {
detailPreview.value = value;
@@ -151,9 +155,7 @@ export async function useFormCreateDesigner(designer: Ref) {
const uploadImageRule = useUploadImageRule();
const uploadImagesRule = useUploadImagesRule();
/**
* 构建表单组件
*/
/** 构建表单组件 */
function buildFormComponents() {
// 移除自带的上传组件规则,使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代
designer.value?.removeMenuItem('upload');
@@ -196,9 +198,7 @@ export async function useFormCreateDesigner(designer: Ref) {
event: ['click', 'change', 'visibleChange', 'clear', 'blur', 'focus'],
});
/**
* 构建系统字段菜单
*/
/** 构建系统字段菜单 */
function buildSystemMenu() {
// 移除自带的下拉选择器组件,使用 currencySelectRule 替代
// designer.value?.removeMenuItem('select')

View File

@@ -121,7 +121,7 @@ const apiSelectRule = [
field: 'data',
title: '请求参数 JSON 格式',
props: {
autosize: true,
autosize: true, // TODO @puhui999这里时 autoSize 还是 autosize 哈?和 antd 不同
type: 'textarea',
placeholder: '{"type": 1}',
},
@@ -155,7 +155,7 @@ const apiSelectRule = [
info: `data 为接口返回值,需要写一个匿名函数解析返回值为选择器 options 列表
(data: any)=>{ label: string; value: any }[]`,
props: {
autosize: true,
autosize: true, // TODO @puhui999这里时 autoSize 还是 autosize 哈?和 antd 不同
rows: { minRows: 2, maxRows: 6 },
type: 'textarea',
placeholder: `

View File

@@ -11,10 +11,8 @@ import {
} from '#/components/form-create/helpers';
import { selectRule } from '#/components/form-create/rules/data';
/**
* 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule
*/
export const useDictSelectRule = () => {
/** 字典选择器规则,如果规则使用到动态数据则需要单独配置不能使用 useSelectRule */
export function useDictSelectRule() {
const label = '字典选择器';
const name = 'DictSelect';
const rules = cloneDeep(selectRule);
@@ -41,6 +39,7 @@ export const useDictSelectRule = () => {
title: label,
info: '',
$required: false,
// TODO @puhui999vben 版本里,这里有个 modelField: 'value', 需要添加么?
};
},
props(_: any, { t }: any) {
@@ -68,4 +67,4 @@ export const useDictSelectRule = () => {
]);
},
};
};
}

View File

@@ -5,7 +5,7 @@ import {
makeRequiredRule,
} from '#/components/form-create/helpers';
export const useEditorRule = () => {
export function useEditorRule() {
const label = '富文本';
const name = 'Tinymce';
return {
@@ -33,4 +33,4 @@ export const useEditorRule = () => {
]);
},
};
};
}

View File

@@ -13,7 +13,7 @@ import { selectRule } from '#/components/form-create/rules/data';
*
* @param option 规则配置
*/
export const useSelectRule = (option: SelectRuleOption) => {
export function useSelectRule(option: SelectRuleOption) {
const label = option.label;
const name = option.name;
const rules = cloneDeep(selectRule);
@@ -42,4 +42,4 @@ export const useSelectRule = (option: SelectRuleOption) => {
]);
},
};
};
}

View File

@@ -5,7 +5,7 @@ import {
makeRequiredRule,
} from '#/components/form-create/helpers';
export const useUploadFileRule = () => {
export function useUploadFileRule() {
const label = '文件上传';
const name = 'FileUpload';
return {
@@ -81,4 +81,4 @@ export const useUploadFileRule = () => {
]);
},
};
};
}

View File

@@ -5,7 +5,7 @@ import {
makeRequiredRule,
} from '#/components/form-create/helpers';
export const useUploadImageRule = () => {
export function useUploadImageRule() {
const label = '单图上传';
const name = 'ImageUpload';
return {
@@ -90,4 +90,4 @@ export const useUploadImageRule = () => {
]);
},
};
};
}

View File

@@ -5,7 +5,7 @@ import {
makeRequiredRule,
} from '#/components/form-create/helpers';
export const useUploadImagesRule = () => {
export function useUploadImagesRule() {
const label = '多图上传';
const name = 'ImagesUpload';
return {
@@ -86,4 +86,4 @@ export const useUploadImagesRule = () => {
]);
},
};
};
}

View File

@@ -3,13 +3,14 @@ export const ACTION_ICON = {
UPLOAD: 'lucide:upload',
ADD: 'lucide:plus',
EDIT: 'lucide:edit',
DELETE: 'lucide:trash',
DELETE: 'lucide:trash-2',
REFRESH: 'lucide:refresh-cw',
SEARCH: 'lucide:search',
FILTER: 'lucide:filter',
MORE: 'lucide:ellipsis-vertical',
VIEW: 'lucide:eye',
COPY: 'lucide:copy',
CLOSE: 'lucide:x',
BOOK: 'lucide:book',
AUDIT: 'lucide:file-check',
};

View File

@@ -1,5 +1,6 @@
<!-- add by 星语参考 vben2 的方式增加 TableAction 组件 -->
<script setup lang="ts">
// TODO @xingyu要不要和 apps/web-antd/src/components/table-action/table-action.vue 代码风格,进一步风格对齐?现在每个方法,会有一些差异
import type { PropType } from 'vue';
import type { ActionItem, PopConfirm } from './typing';
@@ -42,20 +43,24 @@ const props = defineProps({
const { hasAccessByCodes } = useAccess();
/** 检查是否显示 */
function isIfShow(action: ActionItem): boolean {
const ifShow = action.ifShow;
let isIfShow = true;
if (isBoolean(ifShow)) {
isIfShow = ifShow;
}
if (isFunction(ifShow)) {
isIfShow = ifShow(action);
}
if (isIfShow) {
isIfShow =
hasAccessByCodes(action.auth || []) || (action.auth || []).length === 0;
}
return isIfShow;
}
/** 处理按钮 actions */
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {

View File

@@ -2,8 +2,6 @@
import type { IPropTypes } from '@tinymce/tinymce-vue/lib/cjs/main/ts/components/EditorPropTypes';
import type { Editor as EditorType } from 'tinymce/tinymce';
import type { PropType } from 'vue';
import {
computed,
nextTick,
@@ -35,37 +33,26 @@ type InitOptions = IPropTypes['init'];
defineOptions({ name: 'Tinymce', inheritAttrs: false });
const props = defineProps({
options: {
type: Object as PropType<Partial<InitOptions>>,
default: () => ({}),
},
toolbar: {
type: String,
default: defaultToolbar,
},
plugins: {
type: String,
default: defaultPlugins,
},
height: {
type: [Number, String] as PropType<number | string>,
required: false,
default: 400,
},
width: {
type: [Number, String] as PropType<number | string>,
required: false,
default: 'auto',
},
showImageUpload: {
type: Boolean,
default: true,
},
const props = withDefaults(defineProps<TinymacProps>(), {
height: 400,
width: 'auto',
options: () => ({}),
plugins: defaultPlugins,
toolbar: defaultToolbar,
showImageUpload: true,
});
const emit = defineEmits(['change']);
interface TinymacProps {
options?: Partial<InitOptions>;
toolbar?: string;
plugins?: string;
height?: number | string;
width?: number | string;
showImageUpload?: boolean;
}
/** 外部使用 v-model 绑定值 */
const modelValue = defineModel('modelValue', { default: '', type: String });
@@ -151,7 +138,7 @@ const initOptions = computed((): InitOptions => {
'bold italic | quicklink h2 h3 blockquote quickimage quicktable',
toolbar_mode: 'sliding',
...options,
images_upload_handler: (blobInfo) => {
images_upload_handler: (blobInfo: any) => {
return new Promise((resolve, reject) => {
const file = blobInfo.blob() as File;
const { httpRequest } = useUpload();
@@ -165,9 +152,9 @@ const initOptions = computed((): InitOptions => {
});
});
},
setup: (editor) => {
setup: (editor: EditorType) => {
editorRef.value = editor;
editor.on('init', (e) => initSetup(e));
editor.on('init', (e: any) => initSetup(e));
},
};
});

View File

@@ -17,10 +17,9 @@ const props = defineProps({
type: Boolean,
},
fullscreen: {
// 图片上传,是否放到全屏的位置
default: false,
type: Boolean,
},
}, // 图片上传,是否放到全屏的位置
});
const emit = defineEmits(['uploading', 'done', 'error']);