| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!-- å®¡æ¹æ¨¡æ¿ï¼å¯é
置填æ¥é¡¹ï¼åºååå° formConfig --> |
| | | <template> |
| | | <div class="fce"> |
| | | <div class="fce-hint"> |
| | | <span class="fce-hint-label">å¡«æ¥æç¤º</span> |
| | | <el-input |
| | | v-model="inner.summaryPlaceholder" |
| | | placeholder="å¦ï¼è¯·å¡«åæ¥éäºç±ãéé¢ç" |
| | | maxlength="200" |
| | | show-word-limit |
| | | @input="emitOut" |
| | | /> |
| | | </div> |
| | | |
| | | <div class="fce-panel"> |
| | | <div class="fce-toolbar"> |
| | | <div class="fce-toolbar-left"> |
| | | <span class="fce-title">å¡«æ¥é¡¹é
ç½®</span> |
| | | <el-tag v-if="inner.fields.length" size="small" type="info" effect="plain"> |
| | | å
± {{ inner.fields.length }} 项 |
| | | </el-tag> |
| | | </div> |
| | | <div class="fce-toolbar-actions"> |
| | | <el-dropdown |
| | | trigger="click" |
| | | :disabled="disableImport" |
| | | @visible-change="onImportDropdownVisible" |
| | | @command="importFromTemplate" |
| | | > |
| | | <el-button size="small" :loading="templateImportLoading" :disabled="disableImport"> |
| | | ä»å·²ææ¨¡æ¿å¯¼å
¥ |
| | | </el-button> |
| | | <template #dropdown> |
| | | <el-dropdown-menu> |
| | | <el-dropdown-item v-if="!templateImportOptions.length" disabled> |
| | | ææ å
¶ä»å®¡æ¹æ¨¡æ¿ |
| | | </el-dropdown-item> |
| | | <el-dropdown-item |
| | | v-for="t in templateImportOptions" |
| | | :key="t.id" |
| | | :command="t.id" |
| | | > |
| | | <span>{{ t.label }}</span> |
| | | <el-tag v-if="!t.enabled" size="small" type="info" class="import-tag">å·²åç¨</el-tag> |
| | | </el-dropdown-item> |
| | | </el-dropdown-menu> |
| | | </template> |
| | | </el-dropdown> |
| | | <el-button type="primary" size="small" :icon="Plus" @click="addField">æ·»å å¡«æ¥é¡¹</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <el-empty |
| | | v-if="!inner.fields.length" |
| | | class="fce-empty" |
| | | description="ææ å¡«æ¥é¡¹ï¼å¯æ·»å æä»å·²æå®¡æ¹æ¨¡æ¿å¯¼å
¥" |
| | | :image-size="72" |
| | | /> |
| | | |
| | | <div v-else class="fce-list"> |
| | | <div |
| | | v-for="(field, index) in inner.fields" |
| | | :key="field._uid" |
| | | class="fce-card" |
| | | :class="{ |
| | | 'fce-card--required': field.required, |
| | | 'fce-card--locked': isFieldLocked(field), |
| | | }" |
| | | > |
| | | <div class="fce-card-badge">{{ index + 1 }}</div> |
| | | |
| | | <div class="fce-card-head"> |
| | | <div class="fce-card-title"> |
| | | <span class="fce-card-name">{{ field.label || `å¡«æ¥é¡¹ ${index + 1}` }}</span> |
| | | <el-tag size="small" effect="light" type="primary">{{ typeLabel(field.type) }}</el-tag> |
| | | <el-tag v-if="field.required" size="small" type="danger" effect="plain">å¿
å¡«</el-tag> |
| | | <el-tag v-if="isFieldLocked(field)" size="small" type="info" effect="plain">å
置项</el-tag> |
| | | </div> |
| | | <div v-if="!isFieldLocked(field)" class="fce-card-btns"> |
| | | <el-tooltip content="ä¸ç§»" placement="top"> |
| | | <el-button circle size="small" :disabled="index === 0" @click="moveField(index, -1)"> |
| | | <el-icon><Top /></el-icon> |
| | | </el-button> |
| | | </el-tooltip> |
| | | <el-tooltip content="ä¸ç§»" placement="top"> |
| | | <el-button |
| | | circle |
| | | size="small" |
| | | :disabled="index >= inner.fields.length - 1" |
| | | @click="moveField(index, 1)" |
| | | > |
| | | <el-icon><Bottom /></el-icon> |
| | | </el-button> |
| | | </el-tooltip> |
| | | <el-tooltip content="å é¤" placement="top"> |
| | | <el-button circle size="small" type="danger" plain @click="removeField(index)"> |
| | | <el-icon><Delete /></el-icon> |
| | | </el-button> |
| | | </el-tooltip> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="fce-section"> |
| | | <span class="fce-section-title">åºç¡ä¿¡æ¯</span> |
| | | <el-row :gutter="16"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¾ç¤ºåç§°" required class="fce-field-item"> |
| | | <el-input |
| | | v-model="field.label" |
| | | placeholder="å¦ï¼æ¥é说æ" |
| | | maxlength="50" |
| | | :disabled="isFieldLocked(field)" |
| | | @input="emitOut" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="åæ®µæ è¯" required class="fce-field-item"> |
| | | <el-input |
| | | v-model="field.key" |
| | | placeholder="å¦ï¼summary" |
| | | maxlength="50" |
| | | :disabled="isFieldLocked(field)" |
| | | @input="emitOut" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ§ä»¶ç±»å" class="fce-field-item"> |
| | | <el-select |
| | | v-model="field.type" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="onTypeChange(field)" |
| | | > |
| | | <el-option |
| | | v-for="t in FORM_FIELD_TYPE_OPTIONS" |
| | | :key="t.value" |
| | | :label="t.label" |
| | | :value="t.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <div class="fce-section"> |
| | | <span class="fce-section-title">æ ¡éªä¸æ ¼å¼</span> |
| | | <el-row :gutter="16" align="middle"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æ¯å¦å¿
å¡«" class="fce-field-item fce-field-item--switch"> |
| | | <el-switch |
| | | v-model="field.required" |
| | | inline-prompt |
| | | active-text="å¿
å¡«" |
| | | inactive-text="éå¡«" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="emitOut" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col v-if="field.type === 'textarea'" :span="8"> |
| | | <el-form-item label="è¡æ°" class="fce-field-item"> |
| | | <el-input-number |
| | | v-model="field.rows" |
| | | :min="1" |
| | | :max="10" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="emitOut" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <template v-if="field.type === 'number'"> |
| | | <el-col :span="8"> |
| | | <el-form-item label="æå°å¼" class="fce-field-item"> |
| | | <el-input-number |
| | | v-model="field.min" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="emitOut" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="8"> |
| | | <el-form-item label="å°æ°ä½" class="fce-field-item"> |
| | | <el-input-number |
| | | v-model="field.precision" |
| | | :min="0" |
| | | :max="4" |
| | | controls-position="right" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="emitOut" |
| | | /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </template> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <div class="fce-section fce-section--default"> |
| | | <span class="fce-section-title">é»è®¤å¼</span> |
| | | <p class="fce-section-desc">éæ©è¯¥æ¨¡æ¿æäº¤å®¡æ¹æ¶ï¼å°èªå¨é¢å¡«ä»¥ä¸å
容ï¼ç¨æ·ä»å¯ä¿®æ¹ï¼</p> |
| | | <el-input |
| | | v-if="field.type === 'text' || field.type === 'textarea'" |
| | | v-model="field.defaultValue" |
| | | :type="field.type === 'textarea' ? 'textarea' : 'text'" |
| | | :rows="field.type === 'textarea' ? 2 : undefined" |
| | | :placeholder="defaultPlaceholder(field)" |
| | | :disabled="isFieldLocked(field)" |
| | | clearable |
| | | @input="emitOut" |
| | | /> |
| | | <el-input-number |
| | | v-else-if="field.type === 'number'" |
| | | v-model="field.defaultValue" |
| | | :min="field.min" |
| | | :precision="field.precision ?? 0" |
| | | controls-position="right" |
| | | placeholder="éå¡«" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="emitOut" |
| | | /> |
| | | <el-date-picker |
| | | v-else-if="field.type === 'date'" |
| | | v-model="field.defaultValue" |
| | | type="date" |
| | | placeholder="éå¡«" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | clearable |
| | | @change="emitOut" |
| | | /> |
| | | <el-date-picker |
| | | v-else-if="field.type === 'datetimerange'" |
| | | v-model="field.defaultValue" |
| | | type="datetimerange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¶é´" |
| | | end-placeholder="ç»ææ¶é´" |
| | | format="YYYY-MM-DD HH:mm:ss" |
| | | value-format="YYYY-MM-DD HH:mm:ss" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | clearable |
| | | @change="emitOut" |
| | | /> |
| | | <el-select |
| | | v-else-if="field.type === 'select'" |
| | | v-model="field.defaultValue" |
| | | placeholder="éå¡«" |
| | | style="width: 100%" |
| | | clearable |
| | | filterable |
| | | :loading="optionSourceLoading" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="emitOut" |
| | | > |
| | | <el-option |
| | | v-for="o in resolvedSelectOptions(field)" |
| | | :key="String(o.value)" |
| | | :label="o.label || o.value" |
| | | :value="o.value" |
| | | /> |
| | | </el-select> |
| | | </div> |
| | | |
| | | <div v-if="field.type === 'select'" class="fce-section fce-section--options"> |
| | | <span class="fce-section-title">䏿é项</span> |
| | | <el-row :gutter="16" class="fce-source-row"> |
| | | <el-col :span="12"> |
| | | <el-form-item label="éé¡¹æ¥æº" class="fce-field-item"> |
| | | <el-select |
| | | v-model="field.optionSource" |
| | | style="width: 100%" |
| | | :disabled="isFieldLocked(field)" |
| | | @change="onOptionSourceChange(field)" |
| | | > |
| | | <el-option |
| | | v-for="s in SELECT_OPTION_SOURCE_OPTIONS" |
| | | :key="s.value" |
| | | :label="s.label" |
| | | :value="s.value" |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | <p v-if="isDynamicOptionSource(field.optionSource)" class="fce-source-tip"> |
| | | {{ optionSourceDesc(field.optionSource) }}ãæäº¤å®¡æ¹æ¶å°èªå¨å è½½ææ°æ°æ®ï¼æ éæå¨ç»´æ¤é项ã |
| | | </p> |
| | | <template v-if="!isDynamicOptionSource(field.optionSource)"> |
| | | <div class="fce-options-head"> |
| | | <span class="fce-section-subtitle">æå¨é项</span> |
| | | <el-button |
| | | type="primary" |
| | | link |
| | | size="small" |
| | | :icon="Plus" |
| | | :disabled="isFieldLocked(field)" |
| | | @click="addOption(field)" |
| | | > |
| | | æ·»å é项 |
| | | </el-button> |
| | | </div> |
| | | <div |
| | | v-for="(opt, oi) in field.options" |
| | | :key="oi" |
| | | class="fce-option-row" |
| | | > |
| | | <span class="fce-option-index">{{ oi + 1 }}</span> |
| | | <el-input |
| | | v-model="opt.label" |
| | | placeholder="æ¾ç¤ºææ¬" |
| | | :disabled="isFieldLocked(field)" |
| | | @input="emitOut" |
| | | /> |
| | | <el-input |
| | | v-model="opt.value" |
| | | placeholder="é项å¼" |
| | | class="fce-option-value" |
| | | :disabled="isFieldLocked(field)" |
| | | @input="emitOut" |
| | | /> |
| | | <el-button |
| | | type="danger" |
| | | link |
| | | :icon="Delete" |
| | | :disabled="isFieldLocked(field) || field.options.length <= 1" |
| | | @click="removeOption(field, oi)" |
| | | /> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Bottom, Delete, Plus, Top } from "@element-plus/icons-vue"; |
| | | import { |
| | | getApprovalTemplateDetail, |
| | | listApprovalTemplate, |
| | | TEMPLATE_TYPE_BUILTIN, |
| | | TEMPLATE_TYPE_CUSTOM, |
| | | } from "@/api/officeProcessAutomation/approvalTemplate.js"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | import { computed, reactive, ref, watch } from "vue"; |
| | | import { |
| | | mapEnabledFromApi, |
| | | unwrapTemplateDetail, |
| | | unwrapTemplateList, |
| | | } from "../approveTemplateConstants.js"; |
| | | import { |
| | | FORM_FIELD_TYPE_OPTIONS, |
| | | createEmptyFormConfigData, |
| | | createEmptyFormField, |
| | | formFieldTypeLabel, |
| | | parseFormConfigToData, |
| | | } from "../formConfigUtils.js"; |
| | | import { |
| | | SELECT_OPTION_SOURCE, |
| | | SELECT_OPTION_SOURCE_OPTIONS, |
| | | isDynamicOptionSource, |
| | | } from "../selectOptionSource.js"; |
| | | import { useSelectOptionSources } from "../useSelectOptionSources.js"; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { type: Object, default: () => createEmptyFormConfigData() }, |
| | | /** ç¼è¾å½åæ¨¡æ¿æ¶æé¤èªèº«ï¼é¿å
ä»èªå·±å¯¼å
¥ */ |
| | | excludeTemplateId: { type: [String, Number], default: null }, |
| | | /** ç¦ç¨ãä»å·²ææ¨¡æ¿å¯¼å
¥ã */ |
| | | disableImport: { type: Boolean, default: false }, |
| | | /** ç³»ç»å
置模æ¿ç¼è¾æ¶ï¼æå¼å¼¹çªå³åå¨çå¡«æ¥é¡¹ _uidï¼ä¸å¯æ¹å */ |
| | | lockedFieldUids: { type: Array, default: () => [] }, |
| | | }); |
| | | |
| | | const emit = defineEmits(["update:modelValue"]); |
| | | |
| | | const inner = reactive(createEmptyFormConfigData()); |
| | | |
| | | const { loading: optionSourceLoading, ensureForFields, getOptions } = useSelectOptionSources(); |
| | | |
| | | const templateImportOptions = ref([]); |
| | | const templateImportLoading = ref(false); |
| | | |
| | | const lockedUidSet = computed( |
| | | () => new Set((props.lockedFieldUids || []).filter(Boolean)) |
| | | ); |
| | | |
| | | function isFieldLocked(field) { |
| | | return field?._uid != null && lockedUidSet.value.has(field._uid); |
| | | } |
| | | |
| | | function typeLabel(type) { |
| | | return formFieldTypeLabel(type); |
| | | } |
| | | |
| | | function defaultPlaceholder(field) { |
| | | const name = field.label || "è¯¥åæ®µ"; |
| | | return `éå¡«ï¼éæ©æ¨¡æ¿æ¶å°é¢å¡«${name}`; |
| | | } |
| | | |
| | | function optionSourceDesc(source) { |
| | | return SELECT_OPTION_SOURCE_OPTIONS.find((x) => x.value === source)?.desc || ""; |
| | | } |
| | | |
| | | function resolvedSelectOptions(field) { |
| | | if (field.type !== "select") return []; |
| | | return getOptions(field); |
| | | } |
| | | |
| | | function syncFromProps(v) { |
| | | const src = v || createEmptyFormConfigData(); |
| | | inner.summaryPlaceholder = src.summaryPlaceholder || ""; |
| | | inner.fields = (src.fields || []).map((f) => ({ |
| | | ...createEmptyFormField(), |
| | | ...f, |
| | | _uid: f._uid || createEmptyFormField()._uid, |
| | | optionSource: f.optionSource || SELECT_OPTION_SOURCE.STATIC, |
| | | options: (f.options || [{ label: "", value: "" }]).map((o) => ({ ...o })), |
| | | })); |
| | | ensureForFields(inner.fields); |
| | | } |
| | | |
| | | function emitOut() { |
| | | emit("update:modelValue", { |
| | | summaryPlaceholder: inner.summaryPlaceholder, |
| | | fields: inner.fields.map((f) => ({ |
| | | _uid: f._uid, |
| | | key: f.key, |
| | | label: f.label, |
| | | type: f.type, |
| | | required: f.required, |
| | | rows: f.rows, |
| | | min: f.min, |
| | | precision: f.precision, |
| | | defaultValue: cloneDefaultValue(f), |
| | | optionSource: f.optionSource || SELECT_OPTION_SOURCE.STATIC, |
| | | options: (f.options || []).map((o) => ({ label: o.label, value: o.value })), |
| | | })), |
| | | }); |
| | | } |
| | | |
| | | function cloneDefaultValue(f) { |
| | | if (f.type === "datetimerange" && Array.isArray(f.defaultValue)) { |
| | | return [...f.defaultValue]; |
| | | } |
| | | return f.defaultValue; |
| | | } |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | (v) => syncFromProps(v), |
| | | { deep: true, immediate: true } |
| | | ); |
| | | |
| | | function addField() { |
| | | inner.fields.push(createEmptyFormField()); |
| | | ensureForFields(inner.fields); |
| | | emitOut(); |
| | | } |
| | | |
| | | function removeField(index) { |
| | | if (isFieldLocked(inner.fields[index])) return; |
| | | inner.fields.splice(index, 1); |
| | | emitOut(); |
| | | } |
| | | |
| | | function moveField(index, delta) { |
| | | if (isFieldLocked(inner.fields[index])) return; |
| | | const next = index + delta; |
| | | if (next < 0 || next >= inner.fields.length) return; |
| | | if (isFieldLocked(inner.fields[next])) return; |
| | | const t = inner.fields[index]; |
| | | inner.fields[index] = inner.fields[next]; |
| | | inner.fields[next] = t; |
| | | emitOut(); |
| | | } |
| | | |
| | | function resetDefaultValueForType(field) { |
| | | if (field.type === "number") field.defaultValue = undefined; |
| | | else if (field.type === "datetimerange") field.defaultValue = []; |
| | | else field.defaultValue = ""; |
| | | } |
| | | |
| | | function onTypeChange(field) { |
| | | if (field.type === "select") { |
| | | if (!field.optionSource) field.optionSource = SELECT_OPTION_SOURCE.STATIC; |
| | | if (!field.options || !field.options.length) { |
| | | field.options = [{ label: "", value: "" }]; |
| | | } |
| | | ensureForFields(inner.fields); |
| | | } |
| | | resetDefaultValueForType(field); |
| | | emitOut(); |
| | | } |
| | | |
| | | function onOptionSourceChange(field) { |
| | | field.defaultValue = ""; |
| | | if (!isDynamicOptionSource(field.optionSource) && (!field.options || !field.options.length)) { |
| | | field.options = [{ label: "", value: "" }]; |
| | | } |
| | | ensureForFields(inner.fields); |
| | | emitOut(); |
| | | } |
| | | |
| | | function addOption(field) { |
| | | field.options.push({ label: "", value: "" }); |
| | | emitOut(); |
| | | } |
| | | |
| | | function removeOption(field, oi) { |
| | | if (field.options.length <= 1) return; |
| | | field.options.splice(oi, 1); |
| | | emitOut(); |
| | | } |
| | | |
| | | async function loadTemplateImportOptions() { |
| | | templateImportLoading.value = true; |
| | | try { |
| | | const [customRes, builtinRes] = await Promise.all([ |
| | | listApprovalTemplate(TEMPLATE_TYPE_CUSTOM), |
| | | listApprovalTemplate(TEMPLATE_TYPE_BUILTIN), |
| | | ]); |
| | | const excludeId = |
| | | props.excludeTemplateId != null && props.excludeTemplateId !== "" |
| | | ? String(props.excludeTemplateId) |
| | | : ""; |
| | | templateImportOptions.value = [...unwrapTemplateList(customRes), ...unwrapTemplateList(builtinRes)] |
| | | .filter((row) => row?.id != null && String(row.id) !== excludeId) |
| | | .map((row) => ({ |
| | | id: row.id, |
| | | label: row.templateName || `æ¨¡æ¿ #${row.id}`, |
| | | enabled: mapEnabledFromApi(row.enabled), |
| | | })); |
| | | } catch { |
| | | templateImportOptions.value = []; |
| | | ElMessage.error("å è½½å®¡æ¹æ¨¡æ¿å表失败"); |
| | | } finally { |
| | | templateImportLoading.value = false; |
| | | } |
| | | } |
| | | |
| | | function onImportDropdownVisible(visible) { |
| | | if (props.disableImport) return; |
| | | if (visible) loadTemplateImportOptions(); |
| | | } |
| | | |
| | | async function importFromTemplate(templateId) { |
| | | if (!templateId) return; |
| | | if (inner.fields.length) { |
| | | try { |
| | | await ElMessageBox.confirm("å°è¦çå½åå¡«æ¥é¡¹é
ç½®ï¼æ¯å¦ç»§ç»ï¼", "仿¨¡æ¿å¯¼å
¥", { |
| | | type: "warning", |
| | | confirmButtonText: "ç»§ç»å¯¼å
¥", |
| | | cancelButtonText: "åæ¶", |
| | | }); |
| | | } catch { |
| | | return; |
| | | } |
| | | } |
| | | templateImportLoading.value = true; |
| | | try { |
| | | const res = await getApprovalTemplateDetail(templateId); |
| | | const row = unwrapTemplateDetail(res); |
| | | const data = parseFormConfigToData(row?.formConfig); |
| | | if (!data.fields?.length) { |
| | | ElMessage.warning("è¯¥æ¨¡æ¿æªé
置填æ¥é¡¹"); |
| | | return; |
| | | } |
| | | syncFromProps(data); |
| | | emitOut(); |
| | | ElMessage.success(`已导å
¥ã${row.templateName || "模æ¿"}ãçå¡«æ¥é¡¹`); |
| | | } catch { |
| | | ElMessage.error("å 载模æ¿è¯¦æ
失败"); |
| | | } finally { |
| | | templateImportLoading.value = false; |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .fce { |
| | | width: 100%; |
| | | } |
| | | |
| | | .fce-hint { |
| | | padding: 14px 16px; |
| | | margin-bottom: 14px; |
| | | border-radius: 10px; |
| | | background: linear-gradient(135deg, var(--el-color-primary-light-9) 0%, var(--el-fill-color-blank) 100%); |
| | | border: 1px solid var(--el-color-primary-light-7); |
| | | } |
| | | |
| | | .fce-hint-label { |
| | | display: block; |
| | | font-size: 13px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary); |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .fce-panel { |
| | | padding: 16px; |
| | | border-radius: 12px; |
| | | background: var(--el-fill-color-lighter); |
| | | border: 1px solid var(--el-border-color-lighter); |
| | | } |
| | | |
| | | .fce-toolbar { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | flex-wrap: wrap; |
| | | gap: 12px; |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .fce-toolbar-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .fce-title { |
| | | font-size: 15px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary); |
| | | } |
| | | |
| | | .fce-toolbar-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .import-tag { |
| | | margin-left: 8px; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | .fce-empty { |
| | | padding: 24px 0; |
| | | } |
| | | |
| | | .fce-list { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 14px; |
| | | } |
| | | |
| | | .fce-card { |
| | | position: relative; |
| | | padding: 16px 16px 12px; |
| | | border-radius: 12px; |
| | | background: var(--el-bg-color); |
| | | border: 1px solid var(--el-border-color-lighter); |
| | | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04); |
| | | transition: border-color 0.2s, box-shadow 0.2s; |
| | | } |
| | | |
| | | .fce-card:hover { |
| | | border-color: var(--el-color-primary-light-5); |
| | | box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06); |
| | | } |
| | | |
| | | .fce-card--required { |
| | | border-left: 3px solid var(--el-color-danger-light-3); |
| | | } |
| | | |
| | | .fce-card--locked { |
| | | background: var(--el-fill-color-light); |
| | | } |
| | | |
| | | .fce-card-badge { |
| | | position: absolute; |
| | | top: -10px; |
| | | left: 16px; |
| | | min-width: 22px; |
| | | height: 22px; |
| | | padding: 0 6px; |
| | | border-radius: 11px; |
| | | background: var(--el-color-primary); |
| | | color: #fff; |
| | | font-size: 12px; |
| | | font-weight: 700; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 2px 6px rgba(var(--el-color-primary-rgb), 0.35); |
| | | } |
| | | |
| | | .fce-card-head { |
| | | display: flex; |
| | | align-items: flex-start; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | margin-bottom: 14px; |
| | | padding-top: 4px; |
| | | } |
| | | |
| | | .fce-card-title { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | gap: 8px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .fce-card-name { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-primary); |
| | | } |
| | | |
| | | .fce-card-btns { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 4px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .fce-section { |
| | | margin-bottom: 12px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px dashed var(--el-border-color-extra-light); |
| | | } |
| | | |
| | | .fce-section:last-child { |
| | | margin-bottom: 0; |
| | | padding-bottom: 0; |
| | | border-bottom: none; |
| | | } |
| | | |
| | | .fce-section-title { |
| | | display: block; |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-secondary); |
| | | text-transform: uppercase; |
| | | letter-spacing: 0.5px; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .fce-section-desc { |
| | | margin: -6px 0 10px; |
| | | font-size: 12px; |
| | | color: var(--el-text-color-placeholder); |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .fce-section--default { |
| | | padding: 12px 14px; |
| | | border-radius: 8px; |
| | | background: var(--el-fill-color-lighter); |
| | | border-bottom: none; |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .fce-section--default .fce-section-title { |
| | | margin-bottom: 4px; |
| | | color: var(--el-color-primary); |
| | | text-transform: none; |
| | | letter-spacing: 0; |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .fce-section--options { |
| | | padding-top: 4px; |
| | | border-bottom: none; |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .fce-field-item { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .fce-field-item :deep(.el-form-item__label) { |
| | | font-size: 13px; |
| | | color: var(--el-text-color-regular); |
| | | } |
| | | |
| | | .fce-field-item--switch :deep(.el-form-item__content) { |
| | | line-height: 32px; |
| | | } |
| | | |
| | | .fce-options-head { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | |
| | | .fce-options-head .fce-section-title, |
| | | .fce-options-head .fce-section-subtitle { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .fce-section-subtitle { |
| | | font-size: 12px; |
| | | font-weight: 600; |
| | | color: var(--el-text-color-secondary); |
| | | } |
| | | |
| | | .fce-source-row { |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .fce-source-tip { |
| | | margin: 0 0 10px; |
| | | font-size: 12px; |
| | | color: var(--el-text-color-secondary); |
| | | line-height: 1.5; |
| | | } |
| | | |
| | | .fce-option-row { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | margin-bottom: 8px; |
| | | padding: 8px 10px; |
| | | border-radius: 8px; |
| | | background: var(--el-fill-color-lighter); |
| | | } |
| | | |
| | | .fce-option-row:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .fce-option-index { |
| | | flex-shrink: 0; |
| | | width: 20px; |
| | | height: 20px; |
| | | border-radius: 50%; |
| | | background: var(--el-color-info-light-8); |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 11px; |
| | | font-weight: 600; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .fce-option-value { |
| | | width: 140px; |
| | | flex-shrink: 0; |
| | | } |
| | | </style> |