feat(projects): 新增项目、执行、任务等功能
This commit is contained in:
@@ -49,8 +49,8 @@ const visible = defineModel<boolean>({
|
||||
|
||||
const DIALOG_WIDTH_MAP: Record<DialogPreset, string> = {
|
||||
sm: '520px',
|
||||
md: '640px',
|
||||
lg: '720px'
|
||||
md: '720px',
|
||||
lg: '960px'
|
||||
};
|
||||
|
||||
const dialogWidth = computed(() => props.width ?? DIALOG_WIDTH_MAP[props.preset]);
|
||||
|
||||
169
src/components/custom/business-rich-text-editor.vue
Normal file
169
src/components/custom/business-rich-text-editor.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, shallowRef, watch } from 'vue';
|
||||
import '@wangeditor/editor/dist/css/style.css';
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
|
||||
import type { IDomEditor, IEditorConfig, IToolbarConfig } from '@wangeditor/editor';
|
||||
import { uploadFile } from '@/service/api/file';
|
||||
|
||||
defineOptions({ name: 'BusinessRichTextEditor' });
|
||||
|
||||
interface Props {
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
height?: number | string;
|
||||
/** 上传目录,传给后端 directory 字段 */
|
||||
uploadDirectory?: string;
|
||||
/** 单张图片大小上限(MB),默认 5 */
|
||||
maxImageSizeMB?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
placeholder: '请输入内容',
|
||||
disabled: false,
|
||||
height: 320,
|
||||
uploadDirectory: undefined,
|
||||
maxImageSizeMB: 5
|
||||
});
|
||||
|
||||
const model = defineModel<string | null | undefined>({ default: '' });
|
||||
|
||||
const editorRef = shallowRef<IDomEditor>();
|
||||
|
||||
const toolbarConfig: Partial<IToolbarConfig> = {
|
||||
excludeKeys: [
|
||||
// 视频组
|
||||
'group-video',
|
||||
'insertVideo',
|
||||
'uploadVideo',
|
||||
// 更多样式分组
|
||||
'group-more-style',
|
||||
// 图片:只允许本地上传,不允许插入网络图片 URL
|
||||
'insertImage',
|
||||
// 超链接:业务暂不需要
|
||||
'insertLink',
|
||||
'editLink',
|
||||
'unLink',
|
||||
'viewLink'
|
||||
]
|
||||
};
|
||||
|
||||
const editorConfig: Partial<IEditorConfig> = {
|
||||
placeholder: props.placeholder,
|
||||
readOnly: props.disabled,
|
||||
MENU_CONF: {
|
||||
uploadImage: {
|
||||
maxFileSize: props.maxImageSizeMB * 1024 * 1024,
|
||||
allowedFileTypes: ['image/png', 'image/jpeg', 'image/gif', 'image/webp', 'image/bmp'],
|
||||
async customUpload(file: File, insertFn: (url: string, alt?: string, href?: string) => void) {
|
||||
const result = await uploadFile(file, props.uploadDirectory);
|
||||
|
||||
if (result.error || !result.data) {
|
||||
const msg = result.error?.response?.data?.msg || '图片上传失败';
|
||||
window.$message?.error(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const url = result.data;
|
||||
insertFn(url, file.name, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.disabled,
|
||||
value => {
|
||||
const editor = editorRef.value;
|
||||
if (!editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
editor.disable();
|
||||
} else {
|
||||
editor.enable();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function handleCreated(editor: IDomEditor) {
|
||||
editorRef.value = editor;
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
editorRef.value?.destroy();
|
||||
editorRef.value = undefined;
|
||||
});
|
||||
|
||||
/** 当 height 传 '100%' 或 'auto' 时启用「撑满父容器」模式 —— 父级必须有具体高度。 */
|
||||
const isAutoFill = computed(() => props.height === '100%' || props.height === 'auto');
|
||||
|
||||
const containerClass = computed(() => ({
|
||||
'business-rich-text-editor': true,
|
||||
'business-rich-text-editor--auto-fill': isAutoFill.value
|
||||
}));
|
||||
|
||||
const editorStyle = computed(() => {
|
||||
if (isAutoFill.value) {
|
||||
return { flex: 1, minHeight: 0, overflowY: 'hidden' as const };
|
||||
}
|
||||
|
||||
return {
|
||||
height: typeof props.height === 'number' ? `${props.height}px` : props.height,
|
||||
overflowY: 'hidden' as const
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="containerClass">
|
||||
<Toolbar
|
||||
class="business-rich-text-editor__toolbar"
|
||||
:editor="editorRef"
|
||||
:default-config="toolbarConfig"
|
||||
mode="default"
|
||||
/>
|
||||
<Editor
|
||||
v-model="model"
|
||||
class="business-rich-text-editor__editor"
|
||||
:style="editorStyle"
|
||||
:default-config="editorConfig"
|
||||
mode="default"
|
||||
@on-created="handleCreated"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.business-rich-text-editor {
|
||||
width: 100%;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
overflow: hidden;
|
||||
background: var(--el-bg-color);
|
||||
|
||||
&__toolbar {
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
&__editor {
|
||||
background: var(--el-bg-color);
|
||||
}
|
||||
|
||||
&--auto-fill {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* wangeditor 弹层(链接、图片菜单等)默认 z-index 偏低,提高一档避免被 ElDialog 遮挡 */
|
||||
:deep(.w-e-modal),
|
||||
:deep(.w-e-drop-panel),
|
||||
:deep(.w-e-bar-divider),
|
||||
:deep(.w-e-hover-bar) {
|
||||
z-index: 3000 !important;
|
||||
}
|
||||
</style>
|
||||
87
src/components/custom/business-rich-text-view.vue
Normal file
87
src/components/custom/business-rich-text-view.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { sanitizeHtml } from '@/utils/sanitize';
|
||||
|
||||
defineOptions({ name: 'BusinessRichTextView' });
|
||||
|
||||
interface Props {
|
||||
value?: string | null;
|
||||
emptyText?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: '',
|
||||
emptyText: '—'
|
||||
});
|
||||
|
||||
const safeHtml = computed(() => sanitizeHtml(props.value));
|
||||
const isEmpty = computed(() => !safeHtml.value || safeHtml.value.replace(/<[^>]+>/g, '').trim() === '');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="business-rich-text-view">
|
||||
<span v-if="isEmpty" class="business-rich-text-view__empty">{{ props.emptyText }}</span>
|
||||
<div v-else class="business-rich-text-view__content" v-html="safeHtml" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.business-rich-text-view {
|
||||
width: 100%;
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
word-break: break-word;
|
||||
|
||||
&__empty {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
|
||||
&__content {
|
||||
:deep(p) {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
:deep(p:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(img) {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(ul),
|
||||
:deep(ol) {
|
||||
padding-left: 24px;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
:deep(blockquote) {
|
||||
padding: 6px 12px;
|
||||
margin: 0 0 8px;
|
||||
border-left: 3px solid var(--el-border-color);
|
||||
color: var(--el-text-color-regular);
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
:deep(table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
:deep(table td),
|
||||
:deep(table th) {
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
:deep(a) {
|
||||
color: var(--el-color-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
131
src/components/custom/business-user-select.vue
Normal file
131
src/components/custom/business-user-select.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
defineOptions({ name: 'BusinessUserSelect' });
|
||||
|
||||
interface Props {
|
||||
options: Api.SystemManage.UserSimple[];
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
clearable?: boolean;
|
||||
disabledUserIds?: readonly string[];
|
||||
excludeUserIds?: readonly string[];
|
||||
disabledLabel?: string;
|
||||
noDataText?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
placeholder: '请选择用户',
|
||||
disabled: false,
|
||||
clearable: true,
|
||||
disabledUserIds: () => [],
|
||||
excludeUserIds: () => [],
|
||||
disabledLabel: '',
|
||||
noDataText: ''
|
||||
});
|
||||
|
||||
const model = defineModel<string | null>('modelValue', {
|
||||
default: null
|
||||
});
|
||||
|
||||
const searchKeyword = ref('');
|
||||
const disabledUserIdSet = computed(() => new Set(props.disabledUserIds.map(id => String(id))));
|
||||
const excludeUserIdSet = computed(() => new Set(props.excludeUserIds.map(id => String(id))));
|
||||
|
||||
const visibleOptions = computed(() => {
|
||||
const keyword = searchKeyword.value.trim().toLocaleLowerCase();
|
||||
const options = props.options.filter(item => !excludeUserIdSet.value.has(String(item.id)));
|
||||
|
||||
if (!keyword) {
|
||||
return options;
|
||||
}
|
||||
|
||||
return options.filter(item => {
|
||||
const searchText = [item.nickname, item.username, item.deptName, item.id]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLocaleLowerCase();
|
||||
|
||||
return searchText.includes(keyword);
|
||||
});
|
||||
});
|
||||
|
||||
function handleFilter(value: string) {
|
||||
searchKeyword.value = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElSelect
|
||||
v-model="model"
|
||||
class="w-full"
|
||||
filterable
|
||||
:filter-method="handleFilter"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:no-data-text="noDataText || undefined"
|
||||
>
|
||||
<ElOption
|
||||
v-for="item in visibleOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="item.id"
|
||||
:disabled="disabledUserIdSet.has(String(item.id))"
|
||||
>
|
||||
<div class="business-user-select__option">
|
||||
<span class="business-user-select__name">{{ item.nickname }}</span>
|
||||
<span class="business-user-select__suffix">
|
||||
<ElTag
|
||||
v-if="disabledLabel && disabledUserIdSet.has(String(item.id))"
|
||||
size="small"
|
||||
type="warning"
|
||||
effect="light"
|
||||
disable-transitions
|
||||
>
|
||||
{{ disabledLabel }}
|
||||
</ElTag>
|
||||
<span v-if="item.deptName || item.username" class="business-user-select__meta">
|
||||
{{ [item.username, item.deptName].filter(Boolean).join(' · ') }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</ElOption>
|
||||
</ElSelect>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.business-user-select__option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.business-user-select__name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: rgb(15 23 42 / 94%);
|
||||
font-weight: 500;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.business-user-select__suffix {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
max-width: 58%;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.business-user-select__meta {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: rgb(100 116 139 / 88%);
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
314
src/components/custom/table-search-fields.vue
Normal file
314
src/components/custom/table-search-fields.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { ElButton, ElCol, ElDatePicker, ElFormItem, ElInput, ElOption, ElRow, ElSelect } from 'element-plus';
|
||||
import DictSelect from './dict-select.vue';
|
||||
|
||||
defineOptions({ name: 'TableSearchFields' });
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export interface SearchField {
|
||||
/** 字段键名 */
|
||||
key: string;
|
||||
/** 字段标签 */
|
||||
label: string;
|
||||
/** 字段类型 */
|
||||
type: 'input' | 'select' | 'date' | 'dateRange' | 'dict';
|
||||
/** 占位列数,默认 1 */
|
||||
span?: number;
|
||||
/** select 类型的选项 */
|
||||
options?: Option[];
|
||||
/** dict 类型的字典编码 */
|
||||
dictCode?: string;
|
||||
/** 占位提示文本 */
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** 绑定表单数据对象 */
|
||||
modelValue: Record<string, any>;
|
||||
/** 查询字段定义数组 */
|
||||
fields: SearchField[];
|
||||
/** 每行格子数(按钮占 1 格) */
|
||||
columns: number;
|
||||
/** 表单标签宽度 */
|
||||
labelWidth?: string | number;
|
||||
/** 格子间距 */
|
||||
gutter?: number;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
labelWidth: 80,
|
||||
gutter: 16,
|
||||
disabled: false
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'search'): void;
|
||||
(e: 'reset'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// 折叠/展开状态
|
||||
const expanded = ref(false);
|
||||
|
||||
// 是否需要折叠(字段数 > columns - 1)
|
||||
const needsCollapse = computed(() => props.fields.length > props.columns - 1);
|
||||
|
||||
// 第一行字段数(留一个位置给按钮)
|
||||
const firstRowFieldCount = computed(() => props.columns - 1);
|
||||
|
||||
// 计算第一行字段
|
||||
const firstRowFields = computed(() => {
|
||||
if (expanded.value || !needsCollapse.value) {
|
||||
return props.fields.slice(0, firstRowFieldCount.value);
|
||||
}
|
||||
return props.fields.slice(0, firstRowFieldCount.value);
|
||||
});
|
||||
|
||||
// 计算后续行字段(用于展开后显示)
|
||||
const remainingFields = computed(() => {
|
||||
if (expanded.value || !needsCollapse.value) {
|
||||
return props.fields.slice(firstRowFieldCount.value);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const firstRowButtonSpan = computed(() => {
|
||||
return Math.floor(24 / props.columns);
|
||||
});
|
||||
|
||||
// 计算第一行字段的 span(字段和按钮区保持同一列宽)
|
||||
const firstRowFieldSpan = computed(() => {
|
||||
return firstRowButtonSpan.value;
|
||||
});
|
||||
|
||||
// 计算每个字段的 span(用于后续行)
|
||||
const fieldSpan = computed(() => {
|
||||
return Math.floor(24 / props.columns);
|
||||
});
|
||||
|
||||
// 字段不足时补足首行空列,确保按钮区始终落在 columns 定义的最后一格。
|
||||
const firstRowPlaceholderSpan = computed(() => {
|
||||
const emptySlotCount = Math.max(props.columns - 1 - firstRowFields.value.length, 0);
|
||||
return emptySlotCount * fieldSpan.value;
|
||||
});
|
||||
|
||||
function handleToggle() {
|
||||
expanded.value = !expanded.value;
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
emit('reset');
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
emit('search');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- eslint-disable vue/no-mutating-props -->
|
||||
<template>
|
||||
<ElCard class="card-wrapper">
|
||||
<ElForm :model="props.modelValue" :label-width="props.labelWidth" @submit.prevent @keyup.enter="handleSearch">
|
||||
<!-- 第一行:fields + 按钮 -->
|
||||
<ElRow :gutter="props.gutter">
|
||||
<ElCol
|
||||
v-for="field in firstRowFields"
|
||||
:key="field.key"
|
||||
class="table-search-fields__col"
|
||||
:span="firstRowFieldSpan"
|
||||
>
|
||||
<ElFormItem :label="field.label">
|
||||
<ElInput
|
||||
v-if="field.type === 'input'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
<ElSelect
|
||||
v-else-if="field.type === 'select'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
>
|
||||
<ElOption v-for="opt in field.options" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</ElSelect>
|
||||
<ElDatePicker
|
||||
v-else-if="field.type === 'date'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
type="date"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
value-format="YYYY-MM-DD"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
<ElDatePicker
|
||||
v-else-if="field.type === 'dateRange'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
type="daterange"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
value-format="YYYY-MM-DD"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
<DictSelect
|
||||
v-else-if="field.type === 'dict'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
:dict-code="field.dictCode!"
|
||||
:placeholder="field.placeholder"
|
||||
:disabled="props.disabled"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
|
||||
<ElCol
|
||||
v-if="firstRowPlaceholderSpan > 0"
|
||||
class="table-search-fields__col table-search-fields__placeholder-col"
|
||||
:span="firstRowPlaceholderSpan"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<!-- 按钮区域 -->
|
||||
<ElCol class="table-search-fields__col table-search-fields__action-col" :span="firstRowButtonSpan">
|
||||
<ElFormItem class="table-search-fields__actions" label-width="0">
|
||||
<ElButton
|
||||
v-if="needsCollapse"
|
||||
circle
|
||||
:title="expanded ? '收起' : '展开'"
|
||||
:aria-label="expanded ? '收起查询条件' : '展开查询条件'"
|
||||
:disabled="props.disabled"
|
||||
@click="handleToggle"
|
||||
>
|
||||
<icon-mdi-chevron-double-up v-if="expanded" />
|
||||
<icon-mdi-chevron-double-down v-else />
|
||||
</ElButton>
|
||||
<ElButton :disabled="props.disabled" @click="handleReset">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
重置
|
||||
</ElButton>
|
||||
<ElButton type="primary" :disabled="props.disabled" @click="handleSearch">
|
||||
<template #icon>
|
||||
<icon-ic-round-search class="text-icon" />
|
||||
</template>
|
||||
查询
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 展开后的后续行 -->
|
||||
<ElRow v-if="expanded && remainingFields.length > 0" :gutter="props.gutter">
|
||||
<ElCol v-for="field in remainingFields" :key="field.key" class="table-search-fields__col" :span="fieldSpan">
|
||||
<ElFormItem :label="field.label">
|
||||
<ElInput
|
||||
v-if="field.type === 'input'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
<ElSelect
|
||||
v-else-if="field.type === 'select'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
>
|
||||
<ElOption v-for="opt in field.options" :key="opt.value" :label="opt.label" :value="opt.value" />
|
||||
</ElSelect>
|
||||
<ElDatePicker
|
||||
v-else-if="field.type === 'date'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
type="date"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
value-format="YYYY-MM-DD"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
<ElDatePicker
|
||||
v-else-if="field.type === 'dateRange'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
type="daterange"
|
||||
:placeholder="field.placeholder"
|
||||
clearable
|
||||
:disabled="props.disabled"
|
||||
value-format="YYYY-MM-DD"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
<DictSelect
|
||||
v-else-if="field.type === 'dict'"
|
||||
:model-value="props.modelValue[field.key]"
|
||||
:dict-code="field.dictCode!"
|
||||
:placeholder="field.placeholder"
|
||||
:disabled="props.disabled"
|
||||
@update:model-value="val => (props.modelValue[field.key] = val)"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElForm>
|
||||
</ElCard>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.el-form-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.table-search-fields__col {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.table-search-fields__placeholder-col {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.table-search-fields__actions {
|
||||
:deep(.el-form-item__content) {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.el-button + .el-button) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.el-input),
|
||||
:deep(.el-select),
|
||||
:deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user