From b014cdaf7fcf42cd2b310968f9d47d4420444a6a Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期四, 21 五月 2026 11:46:38 +0800
Subject: [PATCH] 审批模板增加配置模板导入,按钮权限控制,新建页面ui优化
---
src/pages/oa/ApproveManage/approve-list/apply.vue | 802 +++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 636 insertions(+), 166 deletions(-)
diff --git a/src/pages/oa/ApproveManage/approve-list/apply.vue b/src/pages/oa/ApproveManage/approve-list/apply.vue
index 12f49c5..3e873a8 100644
--- a/src/pages/oa/ApproveManage/approve-list/apply.vue
+++ b/src/pages/oa/ApproveManage/approve-list/apply.vue
@@ -16,83 +16,150 @@
<text class="loading-text">鍔犺浇涓�...</text>
</view>
<template v-else-if="detail">
- <view class="section">
- <view class="section-title">鍩烘湰淇℃伅</view>
- <view class="form-body">
- <view class="form-row">
- <text class="form-label required">瀹℃壒鏍囬</text>
+ <up-form :model="form"
+ label-width="88"
+ input-align="right">
+ <u-cell-group title="鍩烘湰淇℃伅"
+ class="form-section">
+ <up-form-item label="瀹℃壒鏍囬"
+ required
+ class="form-item-name">
<up-input v-model="form.title"
+ class="name-input-inline"
placeholder="璇疯緭鍏ュ鎵规爣棰�"
maxlength="100"
clearable />
- </view>
- <view class="form-row">
- <text class="form-label">瀹℃壒妯℃澘</text>
- <text class="form-readonly">{{ templateName }}</text>
- </view>
- <view class="form-row">
- <text class="form-label">鐢宠浜�</text>
- <text class="form-readonly">{{ displayApplicantName }}</text>
- </view>
- </view>
- </view>
+ </up-form-item>
+ <up-form-item label="瀹℃壒妯℃澘"
+ class="form-item-readonly">
+ <up-input :model-value="templateName"
+ readonly />
+ </up-form-item>
+ <up-form-item label="鐢宠浜�"
+ class="form-item-readonly">
+ <up-input :model-value="displayApplicantName"
+ readonly />
+ </up-form-item>
+ </u-cell-group>
+ </up-form>
- <view class="section">
- <view class="section-title">濉姤鍐呭</view>
+ <view class="section-card">
+ <view class="section-head">
+ <text class="section-title">濉姤鍐呭</text>
+ </view>
<view v-if="formConfigData.prompt"
class="form-prompt">
{{ formConfigData.prompt }}
</view>
- <view v-if="formConfigData.fields.length"
- class="form-body">
- <view v-for="field in formConfigData.fields"
- :key="field.key"
- class="form-row form-row--field">
- <text class="form-label"
- :class="{ required: field.required }">{{ field.label }}</text>
- <up-textarea v-if="field.type === 'textarea'"
+ <up-form v-if="formConfigData.fields.length"
+ :model="formValues"
+ label-width="88"
+ input-align="right"
+ class="dynamic-form">
+ <up-form-item v-for="field in formConfigData.fields"
+ :key="field.key"
+ :label="field.label"
+ :required="!!field.required"
+ :class="formItemClass(field)">
+ <up-textarea v-if="isTextareaField(field)"
v-model="formValues[field.key]"
:placeholder="`璇疯緭鍏�${field.label}`"
maxlength="500"
border="surround"
height="80" />
- <view v-else-if="field.type === 'date'"
- class="date-trigger"
- @click="openDatePicker(field.key)">
- <up-input :model-value="formValues[field.key]"
+ <view v-else-if="isDatetimerangeField(field)"
+ class="daterange-fill">
+ <view class="range-fill-row"
+ @click="openRangePicker(field, 'start')">
+ <text class="range-fill-label">寮�濮�</text>
+ <up-input :model-value="getRangePartDisplay(field, 'start')"
+ placeholder="寮�濮嬫椂闂�"
+ readonly />
+ <up-icon name="calendar"
+ size="16"
+ color="#909399" />
+ </view>
+ <text class="range-fill-sep">鑷�</text>
+ <view class="range-fill-row"
+ @click="openRangePicker(field, 'end')">
+ <text class="range-fill-label">缁撴潫</text>
+ <up-input :model-value="getRangePartDisplay(field, 'end')"
+ placeholder="缁撴潫鏃堕棿"
+ readonly />
+ <up-icon name="calendar"
+ size="16"
+ color="#909399" />
+ </view>
+ </view>
+ <view v-else-if="isDateLikeField(field)"
+ class="field-trigger"
+ @click="openDatePicker(field)">
+ <up-input :model-value="formatFieldDisplayValue(field, formValues[field.key])"
:placeholder="`璇烽�夋嫨${field.label}`"
readonly />
+ <up-icon :name="getDatePickerMode(field) === 'time' ? 'clock' : 'calendar'"
+ size="18"
+ color="#909399" />
+ </view>
+ <view v-else-if="isSelectField(field)"
+ class="field-trigger"
+ @click="openSelectPicker(field)">
+ <up-input :model-value="getSelectDisplayText(field)"
+ :placeholder="`璇烽�夋嫨${field.label}`"
+ readonly />
+ <up-icon name="arrow-right"
+ size="16"
+ color="#c0c4cc" />
</view>
<up-input v-else
v-model="formValues[field.key]"
- :type="field.type === 'number' ? 'digit' : 'text'"
+ :type="isNumberField(field) ? 'digit' : 'text'"
:placeholder="`璇疯緭鍏�${field.label}`"
clearable />
- </view>
- </view>
+ </up-form-item>
+ </up-form>
<view v-else
class="empty-hint">璇ユā鏉挎殏鏃犲~鎶ラ」</view>
</view>
- <view class="section">
- <view class="section-title">瀹℃壒娴佺▼</view>
+ <view class="section-card">
+ <view class="section-head">
+ <text class="section-title">瀹℃壒娴佺▼</text>
+ </view>
<view v-if="detail.nodes?.length"
- class="flow-list">
- <view v-for="(node, index) in detail.nodes"
- :key="node.id || index"
- class="flow-card">
- <view class="flow-card-head">
- <text class="flow-level">绗瑊{ levelLabel(node.levelNo || index + 1) }}绾�</text>
- <text class="flow-type">{{ approveTypeText(node.approveType) }}</text>
+ class="flow-wrap">
+ <view v-for="(node, nodeIndex) in detail.nodes"
+ :key="node.id || nodeIndex"
+ class="flow-node-block">
+ <view class="flow-node-card">
+ <view class="node-header">
+ <view class="node-level-badge">{{ node.levelNo || nodeIndex + 1 }}</view>
+ <text class="node-level-text">绗瑊{ levelLabel(node.levelNo || nodeIndex + 1) }}绾�</text>
+ </view>
+ <view class="approve-type-row approve-type-row--readonly">
+ <view class="type-btn"
+ :class="{ active: node.approveType !== 'OR' }">
+ 浼氱
+ </view>
+ <view class="type-btn"
+ :class="{ active: node.approveType === 'OR' }">
+ 鎴栫
+ </view>
+ </view>
+ <view class="approver-list">
+ <view v-for="(approver, aIdx) in node.approvers || []"
+ :key="approver.id || aIdx"
+ class="approver-chip">
+ <view class="approver-avatar">{{ (approver.approverName || "?").charAt(0) }}</view>
+ <text class="approver-name">{{ approver.approverName || "-" }}</text>
+ </view>
+ <text v-if="!(node.approvers || []).length"
+ class="empty-hint inline">鏆傛棤瀹℃壒浜�</text>
+ </view>
</view>
- <view class="approver-tags">
- <text v-for="(approver, aIdx) in node.approvers || []"
- :key="approver.id || aIdx"
- class="approver-tag">
- {{ approver.approverName || "-" }}
- </text>
- <text v-if="!(node.approvers || []).length"
- class="empty-hint inline">鏆傛棤瀹℃壒浜�</text>
+ <view v-if="nodeIndex < detail.nodes.length - 1"
+ class="flow-connector">
+ <view class="flow-connector-line" />
</view>
</view>
</view>
@@ -119,10 +186,16 @@
@close="showDatePicker = false">
<up-datetime-picker :show="true"
v-model="datePickerTs"
- mode="date"
+ :mode="datePickerMode"
@confirm="onDateConfirm"
- @cancel="showDatePicker = false" />
+ @cancel="onDatePickerCancel" />
</up-popup>
+
+ <up-action-sheet :show="showSelectSheet"
+ :title="selectSheetTitle"
+ :actions="selectSheetActions"
+ @select="onSelectOption"
+ @close="showSelectSheet = false" />
</view>
</template>
@@ -137,7 +210,28 @@
updateApprovalInstance,
} from "@/api/oa/approvalInstance.js";
import useUserStore from "@/store/modules/user";
- import { formatDateToYMD, parseTime } from "@/utils/ruoyi";
+ import { parseTime } from "@/utils/ruoyi";
+ import { getDept } from "@/api/collaborativeApproval/approvalProcess.js";
+ import { userListNoPageByTenantId } from "@/api/system/user";
+ import {
+ formatDatetimerangeDisplay,
+ formatFieldDateValue,
+ formatFieldDisplayValue,
+ getDatePickerMode,
+ getFieldInitialValue,
+ getFieldOptionLabel,
+ isDatetimerangeField,
+ isDateLikeField,
+ isNumberField,
+ isSelectField,
+ isTextareaField,
+ joinDatetimerangeValue,
+ mergeFormConfigForEdit,
+ parseDatetimerangeValue,
+ resolveFieldOptions,
+ parseApprovalFormConfig,
+ parseFieldDateToTs,
+ } from "../../_utils/approvalFormField.js";
const EDIT_STORAGE_KEY = "oa_approve_instance_edit_row";
const LEVEL_TEXT = ["", "涓�", "浜�", "涓�", "鍥�", "浜�", "鍏�", "涓�", "鍏�", "涔�", "鍗�"];
@@ -154,12 +248,25 @@
const showDatePicker = ref(false);
const datePickerTs = ref(Date.now());
- const activeDateFieldKey = ref("");
+ const activeDateField = ref(null);
+ const activeRangePart = ref("start");
+
+ const datePickerMode = computed(() => {
+ const field = activeDateField.value;
+ if (!field) return "date";
+ if (isDatetimerangeField(field)) return "datetime";
+ return getDatePickerMode(field);
+ });
+
+ const showSelectSheet = ref(false);
+ const activeSelectField = ref(null);
+ const pickerUserList = ref([]);
+ const pickerDeptList = ref([]);
const isEditMode = computed(() => !!instanceId.value);
- const pageTitle = computed(() => (isEditMode.value ? "缂栬緫瀹℃壒" : "鍙戣捣瀹℃壒"));
- const confirmText = computed(() => (isEditMode.value ? "淇濆瓨" : "鎻愪氦瀹℃壒"));
+ const pageTitle = computed(() => (isEditMode.value ? "淇敼瀹℃壒" : "鍙戣捣瀹℃壒"));
+ const confirmText = computed(() => (isEditMode.value ? "淇濆瓨淇敼" : "鎻愪氦瀹℃壒"));
const applicantName = computed(
() => userStore.nickName || userStore.name || "-"
@@ -173,28 +280,72 @@
() => detail.value?.templateName || instanceRow.value?.templateName || "-"
);
- const parseFormConfig = raw => {
- if (!raw) return { prompt: "", fields: [] };
- try {
- const obj = typeof raw === "string" ? JSON.parse(raw) : raw;
- return {
- prompt: obj?.prompt || "",
- fields: Array.isArray(obj?.fields) ? obj.fields : [],
- };
- } catch {
- return { prompt: "", fields: [] };
- }
- };
-
const formConfigData = computed(() => {
- const raw = isEditMode.value
- ? instanceRow.value?.formConfig
- : detail.value?.formConfig;
- return parseFormConfig(raw);
+ if (isEditMode.value) {
+ return mergeFormConfigForEdit(
+ detail.value?.formConfig,
+ instanceRow.value?.formConfig
+ );
+ }
+ return parseApprovalFormConfig(detail.value?.formConfig);
});
const levelLabel = n => LEVEL_TEXT[Number(n)] || String(n);
- const approveTypeText = type => (type === "OR" ? "鎴栫" : "浼氱");
+
+ const selectSheetTitle = computed(
+ () => (activeSelectField.value?.label ? `閫夋嫨${activeSelectField.value.label}` : "璇烽�夋嫨")
+ );
+
+ const selectSheetActions = computed(() => {
+ const field = activeSelectField.value;
+ if (!field) return [];
+ return resolveFieldOptions(field, {
+ users: pickerUserList.value,
+ depts: pickerDeptList.value,
+ }).map(opt => ({
+ name: opt.label,
+ value: opt.value,
+ }));
+ });
+
+ const formItemClass = field => {
+ if (isTextareaField(field)) return "form-item-textarea";
+ if (isDatetimerangeField(field)) return "form-item-daterange";
+ if (isSelectField(field) || isDateLikeField(field)) return "form-item-select";
+ return "form-item-inline";
+ };
+
+ const getRangePartDisplay = (field, part) => {
+ const parts = parseDatetimerangeValue(formValues[field.key]);
+ const val = part === "start" ? parts.start : parts.end;
+ return val ? formatFieldDisplayValue({ type: "datetime" }, val) : "";
+ };
+
+ const openRangePicker = (field, part) => {
+ activeDateField.value = field;
+ activeRangePart.value = part;
+ const parts = parseDatetimerangeValue(formValues[field.key]);
+ const val = part === "start" ? parts.start : parts.end;
+ datePickerTs.value = parseFieldDateToTs(val) ?? Date.now();
+ showDatePicker.value = true;
+ };
+
+ const getSelectDisplayText = field => {
+ const stored = formValues[field.key];
+ const options = resolveFieldOptions(field, {
+ users: pickerUserList.value,
+ depts: pickerDeptList.value,
+ });
+ const matched = options.find(
+ opt =>
+ String(opt.value) === String(stored) || String(opt.label) === String(stored)
+ );
+ return (
+ matched?.label ||
+ getFieldOptionLabel(field, stored) ||
+ (stored !== undefined && stored !== null ? String(stored) : "")
+ );
+ };
const initFormValues = fields => {
Object.keys(formValues).forEach(key => {
@@ -202,23 +353,60 @@
});
fields.forEach(field => {
if (!field?.key) return;
- formValues[field.key] = field.value ?? field.defaultValue ?? "";
+ formValues[field.key] = getFieldInitialValue(field);
});
};
- const openDatePicker = fieldKey => {
- activeDateFieldKey.value = fieldKey;
- const current = formValues[fieldKey];
- datePickerTs.value = current ? new Date(current).getTime() : Date.now();
+ const openSelectPicker = field => {
+ const options = resolveFieldOptions(field, {
+ users: pickerUserList.value,
+ depts: pickerDeptList.value,
+ });
+ if (!options.length) {
+ uni.showToast({ title: "璇ュ瓧娈垫湭閰嶇疆涓嬫媺閫夐」", icon: "none" });
+ return;
+ }
+ activeSelectField.value = field;
+ showSelectSheet.value = true;
+ };
+
+ const onSelectOption = action => {
+ const key = activeSelectField.value?.key;
+ if (key) {
+ formValues[key] = action.value;
+ }
+ showSelectSheet.value = false;
+ activeSelectField.value = null;
+ };
+
+ const openDatePicker = field => {
+ activeDateField.value = field;
+ const current = formValues[field.key];
+ datePickerTs.value = parseFieldDateToTs(current) ?? Date.now();
showDatePicker.value = true;
+ };
+
+ const onDatePickerCancel = () => {
+ showDatePicker.value = false;
+ activeDateField.value = null;
};
const onDateConfirm = e => {
const ts = e?.value ?? datePickerTs.value;
- if (activeDateFieldKey.value) {
- formValues[activeDateFieldKey.value] = formatDateToYMD(ts);
+ const field = activeDateField.value;
+ if (field?.key) {
+ if (isDatetimerangeField(field)) {
+ const parts = parseDatetimerangeValue(formValues[field.key]);
+ const formatted = formatFieldDateValue({ type: "datetime" }, ts);
+ formValues[field.key] = joinDatetimerangeValue(
+ activeRangePart.value === "start" ? formatted : parts.start,
+ activeRangePart.value === "end" ? formatted : parts.end
+ );
+ } else {
+ formValues[field.key] = formatFieldDateValue(field, ts);
+ }
}
- showDatePicker.value = false;
+ onDatePickerCancel();
};
const validateForm = () => {
@@ -230,8 +418,35 @@
if (!field.required) continue;
const val = formValues[field.key];
if (val === undefined || val === null || String(val).trim() === "") {
- uni.showToast({ title: `璇峰~鍐�${field.label}`, icon: "none" });
+ const action =
+ isSelectField(field) || isDateLikeField(field) || isDatetimerangeField(field)
+ ? "璇烽�夋嫨"
+ : "璇峰~鍐�";
+ uni.showToast({ title: `${action}${field.label}`, icon: "none" });
return false;
+ }
+ if (isDatetimerangeField(field)) {
+ const { start, end } = parseDatetimerangeValue(val);
+ if (!start || !end) {
+ uni.showToast({ title: `璇峰畬鏁撮�夋嫨${field.label}`, icon: "none" });
+ return false;
+ }
+ }
+ if (isSelectField(field)) {
+ const options = resolveFieldOptions(field, {
+ users: pickerUserList.value,
+ depts: pickerDeptList.value,
+ });
+ if (
+ options.length &&
+ !options.some(
+ opt =>
+ String(opt.value) === String(val) || String(opt.label) === String(val)
+ )
+ ) {
+ uni.showToast({ title: `${field.label}閫夐」鏃犳晥`, icon: "none" });
+ return false;
+ }
}
}
if (!detail.value?.nodes?.length) {
@@ -272,15 +487,23 @@
templateId: row.templateId ?? detail.value?.id,
templateName: row.templateName ?? detail.value?.templateName,
businessId: row.businessId,
- businessType: row.businessType,
+ businessType: row.businessType ?? detail.value?.businessType,
title: form.title.trim(),
status: row.status || "PENDING",
currentLevel: row.currentLevel,
applicantId: row.applicantId,
applicantName: row.applicantName,
applyTime: row.applyTime,
+ finishTime: row.finishTime,
+ createUser: row.createUser,
+ createTime: row.createTime,
+ updateUser: row.updateUser,
+ updateTime: row.updateTime,
deptId: row.deptId,
+ deleted: row.deleted,
formConfig: buildFormConfigPayload(),
+ approveAction: row.approveAction,
+ approveComment: row.approveComment,
};
};
@@ -296,7 +519,7 @@
submitApi(payload)
.then(() => {
uni.showToast({
- title: isEditMode.value ? "淇濆瓨鎴愬姛" : "鎻愪氦鎴愬姛",
+ title: isEditMode.value ? "淇敼鎴愬姛" : "鎻愪氦鎴愬姛",
icon: "success",
});
if (isEditMode.value) {
@@ -308,7 +531,7 @@
})
.catch(() => {
uni.showToast({
- title: isEditMode.value ? "淇濆瓨澶辫触" : "鎻愪氦澶辫触",
+ title: isEditMode.value ? "淇敼澶辫触" : "鎻愪氦澶辫触",
icon: "none",
});
})
@@ -352,9 +575,9 @@
const row = uni.getStorageSync(EDIT_STORAGE_KEY);
if (!row || String(row.id) !== String(instanceId.value)) {
uni.showToast({ title: "鏈幏鍙栧埌瀹℃壒鏁版嵁", icon: "none" });
+ setTimeout(() => uni.navigateBack(), 500);
return;
}
- uni.removeStorageSync(EDIT_STORAGE_KEY);
instanceRow.value = row;
templateId.value = row.templateId;
form.title = row.title || "";
@@ -363,6 +586,7 @@
detail.value = null;
try {
await loadTemplateDetail();
+ if (!detail.value) return;
initFormValues(formConfigData.value.fields);
} finally {
loading.value = false;
@@ -373,7 +597,25 @@
uni.navigateBack();
};
+ const loadPickerSourceData = () => {
+ userListNoPageByTenantId()
+ .then(res => {
+ pickerUserList.value = res?.data || [];
+ })
+ .catch(() => {
+ pickerUserList.value = [];
+ });
+ getDept()
+ .then(res => {
+ pickerDeptList.value = res?.data || [];
+ })
+ .catch(() => {
+ pickerDeptList.value = [];
+ });
+ };
+
onLoad(options => {
+ loadPickerSourceData();
if (options?.id) {
instanceId.value = options.id;
loadForEdit();
@@ -389,11 +631,22 @@
</script>
<style scoped lang="scss">
+ @import "@/static/scss/form-common.scss";
+
+ $primary: #2979ff;
+ $text: #1f2d3d;
+ $text-secondary: #606266;
+ $text-muted: #909399;
+ $bg-page: #f0f3f8;
+ $radius-lg: 12px;
+ $radius-md: 10px;
+ $shadow-card: 0 2px 12px rgba(31, 45, 61, 0.05);
+
.approve-apply-page {
display: flex;
flex-direction: column;
min-height: 100vh;
- background: #f0f3f8;
+ background: $bg-page;
}
.form-scroll {
@@ -412,130 +665,347 @@
.loading-text {
font-size: 14px;
+ color: $text-muted;
+ }
+
+ .form-section {
+ margin-bottom: 10px;
+ border-radius: $radius-lg;
+ overflow: hidden;
+ box-shadow: $shadow-card;
+ }
+
+ :deep(.form-section .u-cell-group__title) {
+ padding: 12px 16px 8px !important;
+ font-size: 15px !important;
+ font-weight: 600 !important;
+ color: $text !important;
+ background: #fff !important;
+ }
+
+ :deep(.form-section .u-form-item) {
+ padding: 0 16px !important;
+ }
+
+ :deep(.form-section .u-form-item__body) {
+ padding: 10px 0 !important;
+ min-height: auto !important;
+ }
+
+ :deep(.form-item-name .u-form-item__body) {
+ flex-direction: row !important;
+ align-items: center !important;
+ }
+
+ :deep(.form-item-name .u-form-item__content) {
+ flex: 1 !important;
+ min-width: 0 !important;
+ justify-content: flex-end !important;
+ }
+
+ :deep(.name-input-inline),
+ :deep(.name-input-inline .u-input__content) {
+ width: 100% !important;
+ flex: 1 !important;
+ }
+
+ :deep(.name-input-inline input),
+ :deep(.name-input-inline .u-input__content__field-wrapper__field) {
+ width: 100% !important;
+ text-align: right !important;
+ font-size: 15px !important;
+ }
+
+ :deep(.form-item-readonly .u-form-item__body) {
+ align-items: center !important;
+ }
+
+ :deep(.form-item-readonly .u-form-item__content) {
+ flex: 1 !important;
+ min-width: 0 !important;
+ justify-content: flex-end !important;
+ }
+
+ :deep(.form-item-readonly .u-input__content__field-wrapper__field) {
+ text-align: right !important;
+ color: #303133 !important;
+ }
+
+ .dynamic-form {
+ padding: 0 0 4px;
+ }
+
+ :deep(.dynamic-form .u-form-item) {
+ padding: 0 16px !important;
+ }
+
+ :deep(.dynamic-form .u-form-item__body) {
+ padding: 10px 0 !important;
+ min-height: auto !important;
+ }
+
+ :deep(.form-item-inline .u-form-item__body) {
+ flex-direction: row !important;
+ align-items: center !important;
+ }
+
+ :deep(.form-item-inline .u-form-item__content) {
+ flex: 1 !important;
+ min-width: 0 !important;
+ justify-content: flex-end !important;
+ }
+
+ :deep(.form-item-inline input),
+ :deep(.form-item-inline .u-input__content__field-wrapper__field) {
+ text-align: right !important;
+ font-size: 15px !important;
+ }
+
+ :deep(.form-item-select .u-form-item__body) {
+ align-items: center !important;
+ }
+
+ :deep(.form-item-select .u-form-item__content) {
+ flex: 1 !important;
+ min-width: 0 !important;
+ justify-content: flex-end !important;
+ }
+
+ :deep(.form-item-textarea .u-form-item__body) {
+ flex-direction: column !important;
+ align-items: stretch !important;
+ padding: 10px 0 12px !important;
+ }
+
+ :deep(.form-item-textarea .u-form-item__content) {
+ width: 100% !important;
+ justify-content: stretch !important;
+ }
+
+ .field-trigger {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 6px;
+ width: 100%;
+ min-width: 0;
+ }
+
+ :deep(.field-trigger .u-input) {
+ flex: 1 !important;
+ min-width: 0 !important;
+ }
+
+ :deep(.field-trigger .u-input__content__field-wrapper__field) {
+ text-align: right !important;
+ font-size: 15px !important;
+ }
+
+ :deep(.form-item-daterange .u-form-item__body) {
+ flex-direction: column !important;
+ align-items: stretch !important;
+ }
+
+ :deep(.form-item-daterange .u-form-item__content) {
+ width: 100% !important;
+ justify-content: stretch !important;
+ }
+
+ .daterange-fill {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .range-fill-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 10px;
+ background: #f7f9fc;
+ border: 1px solid #eef1f6;
+ border-radius: 8px;
+ }
+
+ .range-fill-label {
+ flex-shrink: 0;
+ width: 36px;
+ font-size: 13px;
color: #909399;
}
- .section {
- background: #fff;
- border-radius: 12px;
+ .range-fill-sep {
+ font-size: 12px;
+ color: #c0c4cc;
+ text-align: center;
+ }
+
+ :deep(.range-fill-row .u-input) {
+ flex: 1 !important;
+ min-width: 0 !important;
+ }
+
+ .section-card {
margin-bottom: 10px;
+ background: #fff;
+ border-radius: $radius-lg;
overflow: hidden;
- box-shadow: 0 2px 12px rgba(31, 45, 61, 0.05);
+ box-shadow: $shadow-card;
+ }
+
+ .section-head {
+ padding: 12px 16px;
+ border-bottom: 1px solid #f2f4f7;
}
.section-title {
- padding: 12px 16px;
font-size: 15px;
font-weight: 600;
- color: #1f2d3d;
- border-bottom: 1px solid #f2f4f7;
- border-left: 3px solid #2979ff;
- padding-left: 13px;
- }
-
- .form-body {
- padding: 8px 16px 16px;
- }
-
- .form-row {
- padding: 10px 0;
- border-bottom: 1px solid #f5f7fa;
-
- &:last-child {
- border-bottom: none;
- }
-
- &--field {
- flex-direction: column;
- align-items: stretch;
- }
- }
-
- .form-label {
- display: block;
- margin-bottom: 8px;
- font-size: 14px;
- color: #606266;
-
- &.required::before {
- content: "*";
- color: #f56c6c;
- margin-right: 4px;
- }
- }
-
- .form-readonly {
- font-size: 14px;
- color: #303133;
+ color: $text;
+ padding-left: 10px;
+ border-left: 3px solid $primary;
+ line-height: 1.2;
}
.form-prompt {
margin: 12px 16px 0;
padding: 10px 12px;
font-size: 13px;
- color: #606266;
+ color: $text-secondary;
background: #f8fafc;
border-radius: 8px;
line-height: 1.5;
}
- .date-trigger {
- width: 100%;
+ .flow-wrap {
+ padding: 10px 16px 14px;
}
- .flow-list {
+ .flow-node-block {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .flow-node-card {
+ background: #fafbfd;
+ border: 1px solid #e8eef5;
+ border-radius: $radius-md;
padding: 12px;
}
- .flow-card {
- padding: 12px;
- margin-bottom: 8px;
- background: #f8fafc;
+ .node-header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 10px;
+ }
+
+ .node-level-badge {
+ width: 26px;
+ height: 26px;
border-radius: 8px;
- border: 1px solid #eef2f6;
+ background: $primary;
+ color: #fff;
+ font-size: 14px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ }
- &:last-child {
- margin-bottom: 0;
+ .node-level-text {
+ flex: 1;
+ font-size: 15px;
+ font-weight: 600;
+ color: $text;
+ }
+
+ .approve-type-row {
+ display: flex;
+ background: #f0f3f8;
+ border-radius: 8px;
+ padding: 3px;
+ margin-bottom: 10px;
+
+ &--readonly {
+ pointer-events: none;
}
}
- .flow-card-head {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 8px;
- }
-
- .flow-level {
+ .type-btn {
+ flex: 1;
+ text-align: center;
+ padding: 8px 0;
font-size: 14px;
- font-weight: 600;
- color: #303133;
+ color: $text-secondary;
+ border-radius: 6px;
+
+ &.active {
+ background: #fff;
+ color: $primary;
+ font-weight: 500;
+ }
}
- .flow-type {
- font-size: 13px;
- color: #2979ff;
- }
-
- .approver-tags {
+ .approver-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
+ align-items: center;
}
- .approver-tag {
- padding: 4px 10px;
- font-size: 13px;
- color: #303133;
+ .approver-chip {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px 6px 6px;
background: #fff;
border: 1px solid #dce8f8;
- border-radius: 16px;
+ border-radius: 24px;
+ box-shadow: 0 2px 6px rgba(41, 121, 255, 0.06);
+ }
+
+ .approver-avatar {
+ width: 26px;
+ height: 26px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: #fff;
+ font-size: 12px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .approver-name {
+ font-size: 13px;
+ color: $text;
+ max-width: 120px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .flow-connector {
+ display: flex;
+ justify-content: center;
+ padding: 4px 0;
+ }
+
+ .flow-connector-line {
+ width: 2px;
+ height: 14px;
+ background: #d0dff0;
}
.empty-hint {
padding: 12px 16px 16px;
font-size: 13px;
- color: #909399;
+ color: $text-muted;
&.inline {
padding: 0;
--
Gitblit v1.9.3