zhxy-jzd/src/pages/base/xszp/submit.vue

1115 lines
32 KiB
Vue
Raw Normal View History

2026-02-04 09:20:42 +08:00
<!-- 作品任务提交页面 -->
<template>
<view class="zp-submit-page">
<!-- 提交遮罩层 -->
<view v-if="isSubmitting" class="submit-overlay">
<view class="submit-loading">
<view class="loading-spinner"></view>
<text class="loading-text">提交中...</text>
</view>
</view>
<view v-if="isLoading" class="loading-indicator">加载中...</view>
<view v-else class="content-wrapper" :class="{ 'full-height': hideBottomBtn }">
<view class="p-15">
<!-- 第一部分任务要求 -->
<view class="zp-info-section">
<view class="section-title">任务要求</view>
<!-- 任务名称 -->
<view class="info-item">
<text class="label">任务名称</text>
<text class="value title-bold">{{ zp.zpmc || '作品任务' }}</text>
</view>
<!-- 任务描述 -->
<view v-if="zp.zpms" class="info-item">
<text class="label">任务描述</text>
<text class="value">{{ zp.zpms }}</text>
</view>
<!-- 任务时间 -->
<view v-if="zp.zpkstime || zp.zpjstime" class="info-item">
<text class="label">任务时间</text>
<text class="value">{{ formatTimeRange(zp.zpkstime, zp.zpjstime) }}</text>
</view>
<!-- 附件预览 -->
<view v-if="zp.fileUrl" class="file-preview mt-15">
<BasicFilePreview
:file-url="zp.fileUrl"
:file-name="zp.fileName"
:file-format="zp.fileFormat"
/>
</view>
</view>
<!-- 第二部分任务执行 -->
<view class="zp-execute-section">
<view class="section-title">作品提交</view>
<!-- 动态分组渲染 -->
<view v-for="category in dynamicCategories" :key="category.zpfldm">
<view v-if="groupedSchema[category.zpfldm] && groupedSchema[category.zpfldm].length > 0" class="part-section">
<view class="part-title" :data-debug="`分类标题: ${category.label}`">{{ category.label }}</view>
<view class="execute-form">
<BasicForm :schema="groupedSchema[category.zpfldm]" v-model="formData">
</BasicForm>
</view>
</view>
</view>
<!-- 未分类的任务项 -->
<view v-if="groupedSchema[''] && groupedSchema[''].length > 0" class="part-section">
<view class="execute-form">
<BasicForm :schema="groupedSchema['']" v-model="formData">
</BasicForm>
</view>
</view>
</view>
</view>
</view>
<!-- 提交按钮 - 固定在底部 -->
<view v-if="showSubmitButton" class="submit-button-fixed" :class="{ 'hide-bottom': hideBottomBtn }">
<button class="action-button" @click="saveZpzx" :disabled="isSubmitting">
{{ isSubmitting ? '提交中...' : '提交' }}
</button>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { zpFindDetailByIdApi, zpzxSaveApi, zpqdFindByZpzxIdApi } from "@/api/base/server";
import { useUserStore } from "@/store/modules/user";
import { ImageVideoUpload, COMPRESS_PRESETS } from "@/components/ImageVideoUpload";
import { attachmentUpload } from "@/api/system/upload";
import BasicFilePreview from "@/components/BasicFile/preview.vue";
import { imagUrl } from "@/utils";
// 动态分类列表(从任务类型列表中提取)
const dynamicCategories = ref<Array<{ zpfldm: string; label: string }>>([]);
// 从任务类型列表中提取所有不同的 zpfldm 值
const extractCategoriesFromZplxList = () => {
const categoryMap = new Map<string, { zpfldm: string; label: string; minSort: number }>();
zplxList.value.forEach((zplx: ZplxItem, index: number) => {
const zpfldm = (zplx.zpfldm || '').trim();
if (zpfldm) {
// 使用索引作为排序依据(保持原始顺序)
const sort = index;
if (!categoryMap.has(zpfldm) || categoryMap.get(zpfldm)!.minSort > sort) {
categoryMap.set(zpfldm, {
zpfldm: zpfldm,
label: zpfldm,
minSort: sort
});
}
}
});
// 转换为数组并按 minSort 排序
const categories = Array.from(categoryMap.values())
.sort((a, b) => {
return a.minSort - b.minSort;
});
dynamicCategories.value = categories.map(cat => ({
zpfldm: cat.zpfldm,
label: cat.label
}));
console.log('提取的分类列表:', dynamicCategories.value);
};
// 按 zpfldm 动态分组 schema
const groupedSchema = computed(() => {
const groups: Record<string, any[]> = {};
// 初始化所有分类的分组
dynamicCategories.value.forEach(category => {
groups[category.zpfldm] = [];
});
// 添加未分类分组
groups[''] = [];
// 将 schema 项分配到对应分组
schema.value.forEach((item: any) => {
const zpfldm = (item.zpfldm || '').trim();
if (groups.hasOwnProperty(zpfldm)) {
groups[zpfldm].push(item);
} else {
// 如果分类不存在,放入未分类
groups[''].push(item);
}
});
return groups;
});
// 接口定义
interface ZpInfo {
id?: string;
zpmc?: string; // 作品名称
zpms?: string; // 作品描述
zpkstime?: string; // 开始时间
zpjstime?: string; // 结束时间
fileUrl?: string; // 附件URL
fileName?: string; // 附件名称
[key: string]: any;
}
interface ZplxItem {
id: string;
zpbt?: string; // 作品标题
zpfl?: string; // 作品分类(任务类型)
isbt?: number; // 是否必填
remark?: string; // 备注(选项内容等)
zpfldm?: string; // 部分代码:第一部分/第二部分/第三部分
[key: string]: any;
}
interface ZpqdItem {
id?: string;
zplxId?: string; // 作品类型ID
zpqdtx?: string; // 作品清单内容
wjmc?: string; // 文件名
[key: string]: any;
}
// 响应式数据
const formData: any = ref({});
const zpzxId = ref<string>(''); // 作品执行ID
const xsId = ref<string>(''); // 学生ID
const isSubmitting = ref<boolean>(false);
const submitTimer = ref<any>(null);
const showSubmitButton = ref<boolean>(false);
const isLoading = ref(false);
const zplxList = ref<ZplxItem[]>([]);
const zp = ref<ZpInfo>({});
const schema = ref<any[]>([]);
const zpqdList = ref<ZpqdItem[]>([]);
const userStore = useUserStore();
const zpzxData = ref<any>({}); // 作品执行数据,用于获取 ispj 字段
// 从路由参数中获取的状态字段
const routeZpzt = ref<string>(''); // 从路由参数获取的 zpzt
const routeIspj = ref<string>(''); // 从路由参数获取的 ispj
const hideBottomBtn = ref(false); // 控制底部按钮显示/隐藏
// 处理选择器弹窗状态变化
const handlePickerPopupChange = (e: any) => {
// e.show 为 true 表示弹窗打开false 表示关闭
hideBottomBtn.value = e.show;
};
// 格式化时间范围
const formatTimeRange = (startTime?: string, endTime?: string) => {
if (!startTime && !endTime) return '未设置';
const formatTime = (time: string) => {
if (!time) return '';
const date = new Date(time);
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
};
const start = formatTime(startTime || '');
const end = formatTime(endTime || '');
if (start && end) {
return `${start}${end}`;
} else if (start) {
return `${start} 开始`;
} else if (end) {
return `${end} 结束`;
}
return '未设置';
};
// 页面加载
onLoad(async (options) => {
console.log('作品任务提交页面加载参数:', options);
const zpId = options?.zpId || '';
zpzxId.value = options?.zpzxId || '';
xsId.value = options?.xsId || '';
const kcId = options?.kcId || ''; // 保存课程ID用于返回
routeZpzt.value = options?.zpzt || ''; // 从路由参数获取 zpzt
routeIspj.value = options?.ispj || ''; // 从路由参数获取 ispj
if (!zpId) {
uni.showToast({ title: '缺少任务ID', icon: 'error' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
return;
}
// 获取学生ID
if (!xsId.value) {
const curXs = userStore.curXs;
xsId.value = curXs?.id || '';
}
if (!xsId.value) {
uni.showToast({ title: '请先选择学生', icon: 'error' });
setTimeout(() => {
uni.navigateBack();
}, 1500);
return;
}
await loadTaskDetail(zpId);
});
// 加载任务详情
const loadTaskDetail = async (zpId: string) => {
try {
isLoading.value = true;
console.log('加载作品任务详情任务ID:', zpId);
const response: any = await zpFindDetailByIdApi({ id: zpId });
const detailData = response?.result || response?.data || response;
if (!detailData) {
throw new Error('未找到任务数据');
}
// 1. 填充任务基本信息
const zpData = detailData.zp || {};
zp.value = zpData;
// 2. 保存作品执行数据(用于获取 ispj 字段)
zpzxData.value = detailData.zpzx || {};
// 3. 加载任务类型列表(作品类型列表)
zplxList.value = detailData.zplxList || [];
console.log('任务类型列表:', zplxList.value);
// 4. 提取分类信息
extractCategoriesFromZplxList();
// 5. 生成表单schema
generateFormSchema();
// 6. 加载已提交的作品清单数据
await loadZpqdList();
} catch (error) {
console.error('加载任务详情失败:', error);
uni.showToast({
title: error instanceof Error ? error.message : '加载失败,请重试',
icon: 'error'
});
} finally {
isLoading.value = false;
}
};
// 生成表单schema
const generateFormSchema = () => {
schema.value = [];
for (let i = 0; i < zplxList.value.length; i++) {
const zplx = zplxList.value[i];
const fieldId = zplx.id;
console.log(`任务项 ${i + 1}:`, {
zpbt: zplx.zpbt,
zpfl: zplx.zpfl,
isbt: zplx.isbt,
remark: zplx.remark,
zpfldm: zplx.zpfldm
});
// 根据作品分类(任务类型)生成不同的表单组件
if (zplx.zpfl === "sctp" || zplx.zpfl === "scsp" || zplx.zpfl === "scwd") {
// 上传类型:图片、视频、文档
const fieldName = zplx.zpfl === "sctp" ? `${fieldId}_imageList` :
zplx.zpfl === "scsp" ? `${fieldId}_videoList` :
`${fieldId}_fileList`;
// 初始化表单数据
formData.value[fieldName] = [];
let componentConfig: any = {};
if (zplx.zpfl === "sctp") {
// 上传图片
componentConfig = {
component: "ImageVideoUpload",
componentProps: {
enableImage: true,
enableVideo: false,
enableFile: false,
maxImageCount: 30,
uploadApi: attachmentUpload,
compressConfig: COMPRESS_PRESETS.medium,
imageList: formData.value[fieldName] || [],
showSectionTitle: false
}
};
} else if (zplx.zpfl === "scsp") {
// 上传视频
componentConfig = {
component: "ImageVideoUpload",
componentProps: {
enableImage: false,
enableVideo: true,
enableFile: false,
maxVideoCount: 30,
uploadApi: attachmentUpload,
compressConfig: COMPRESS_PRESETS.medium,
videoList: formData.value[fieldName] || [],
showSectionTitle: false
}
};
} else if (zplx.zpfl === "scwd") {
// 上传文档
componentConfig = {
component: "ImageVideoUpload",
componentProps: {
enableImage: false,
enableVideo: false,
enableFile: true,
maxFileCount: 30,
allowedFileTypes: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'mp3', 'wav', 'zip', 'rar'],
uploadApi: attachmentUpload,
compressConfig: COMPRESS_PRESETS.medium,
fileList: formData.value[fieldName] || [],
showSectionTitle: false
}
};
}
const labelText = zplx.zpbt || '作品上传';
console.log('生成表单字段 - zpbt:', labelText, '长度:', labelText.length);
schema.value.push({
field: fieldName,
label: labelText,
required: !!(zplx.isbt === 1 || zplx.isbt === true),
zpfldm: zplx.zpfldm || '',
itemProps: {
labelPosition: "top",
},
...componentConfig
});
} else if (zplx.zpfl === "fwb") {
// 富文本类型
const labelText = zplx.zpbt || '作品描述';
console.log('生成表单字段 - zpbt:', labelText, '长度:', labelText.length);
schema.value.push({
field: fieldId,
label: labelText,
component: "BasicEditor",
required: !!(zplx.isbt === 1 || zplx.isbt === true),
zpfldm: zplx.zpfldm || '',
itemProps: {
labelPosition: "top",
},
componentProps: {
placeholder: "请输入作品描述,支持插入图片"
},
});
} else if (zplx.zpfl === "text") {
// 普通文本类型
const labelText = zplx.zpbt || '作品描述';
console.log('生成表单字段 - zpbt:', labelText, '长度:', labelText.length);
schema.value.push({
field: fieldId,
label: labelText,
component: "BasicInput",
required: !!(zplx.isbt === 1 || zplx.isbt === true),
zpfldm: zplx.zpfldm || '',
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
placeholder: "请输入作品描述"
},
});
} else if (zplx.zpfl === "dxsx" || zplx.zpfl === "dxxz") {
// 选择类型:多项选择或单项选择
// 支持中文分号(;)和英文分号(;)分割选项
let options = (zplx.remark || '').split(/[;]/).filter(Boolean);
let range = options.map(opt => ({ name: opt.trim() }));
const labelText = zplx.zpbt || '作品选择';
console.log('生成表单字段 - zpbt:', labelText, '长度:', labelText.length);
schema.value.push({
field: fieldId,
label: labelText,
component: "BasicPicker",
required: !!(zplx.isbt === 1 || zplx.isbt === true),
zpfldm: zplx.zpfldm || '',
itemProps: {
labelPosition: "top",
},
componentProps: {
range: range,
rangeKey: "name",
savaKey: "name",
onPopupChange: handlePickerPopupChange,
},
});
}
}
console.log('生成的表单schema:', schema.value);
};
// 加载作品清单数据
const loadZpqdList = async () => {
// 如果已有作品执行ID查询已提交的数据
if (zpzxId.value) {
try {
const response: any = await zpqdFindByZpzxIdApi({ zpzxId: zpzxId.value });
const pageData = response?.result || response;
if (pageData && pageData.rows) {
zpqdList.value = pageData.rows || [];
} else if (Array.isArray(response)) {
zpqdList.value = response;
} else {
zpqdList.value = [];
}
console.log('查询到的作品清单数据:', zpqdList.value);
// 如果 ispj 为 'A'(已评价),隐藏提交按钮
// 优先使用路由参数中的 ispj如果没有则使用从后端加载的数据
const ispjValue = routeIspj.value || (zpzxData.value && zpzxData.value.ispj) || '';
const isEvaluated = ispjValue === 'A';
// 只有 ispj 为 'A'(已评价)时,才隐藏提交按钮,不考虑是否有提交数据
showSubmitButton.value = !isEvaluated;
} catch (error) {
console.error('查询作品清单失败:', error);
zpqdList.value = [];
// 检查状态决定是否显示提交按钮
// 只有 ispj 为 'A'(已评价)时,才隐藏提交按钮
const ispjValue = routeIspj.value || (zpzxData.value && zpzxData.value.ispj) || '';
const isEvaluated = ispjValue === 'A';
showSubmitButton.value = !isEvaluated;
}
} else {
// 新任务,检查状态决定是否显示提交按钮
// 只有 ispj 为 'A'(已评价)时,才隐藏提交按钮
const ispjValue = routeIspj.value || (zpzxData.value && zpzxData.value.ispj) || '';
const isEvaluated = ispjValue === 'A';
showSubmitButton.value = !isEvaluated;
}
// 处理数据回显
if (zpqdList.value && zpqdList.value.length > 0) {
handleDataEcho();
}
};
// 处理数据回显
const handleDataEcho = () => {
console.log('开始处理数据回显zpqdList.value:', zpqdList.value);
const showData: Record<string, any> = {};
for (let i = 0; i < zpqdList.value.length; i++) {
const record: ZpqdItem = zpqdList.value[i];
const zplxId = record.zplxId;
const zpqdtx = record.zpqdtx;
// 查找对应的任务类型
const taskType = zplxList.value.find((item: ZplxItem) => item.id === zplxId);
if (taskType && zplxId) {
if (taskType.zpfl === "sctp") {
// 图片上传类型
if (zpqdtx) {
const urls = zpqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const images = urls.map((url: string, index: number) => ({
url: imagUrl(url.trim()),
name: names[index] || url.split('/').pop() || 'image.jpg',
tempPath: undefined
}));
showData[`${zplxId}_imageList`] = images;
}
} else if (taskType.zpfl === "scsp") {
// 视频上传类型
if (zpqdtx) {
const urls = zpqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const videos = urls.map((url: string, index: number) => ({
url: imagUrl(url.trim()),
name: names[index] || url.split('/').pop() || 'video.mp4',
tempPath: undefined
}));
showData[`${zplxId}_videoList`] = videos;
}
} else if (taskType.zpfl === "scwd") {
// 文档上传类型
if (zpqdtx) {
const urls = zpqdtx.split(',').filter(Boolean);
const names = record.wjmc ? record.wjmc.split(',').filter(Boolean) : [];
const files = urls.map((url: string, index: number) => ({
url: imagUrl(url.trim()),
name: names[index] || url.split('/').pop() || 'document',
tempPath: undefined,
type: 'document',
extension: url.split('.').pop() || ''
}));
showData[`${zplxId}_fileList`] = files;
}
} else {
// 文本等其他类型
if (zplxId) {
showData[zplxId] = zpqdtx;
}
}
}
}
// 合并已保存的数据
formData.value = { ...formData.value, ...showData };
// 更新 schema 中上传组件的初始值
for (let i = 0; i < schema.value.length; i++) {
const schemaItem = schema.value[i];
if (schemaItem.component === "ImageVideoUpload" && schemaItem.field) {
const fieldName = schemaItem.field;
if (fieldName.includes('_imageList') && formData.value[fieldName]) {
schemaItem.componentProps.imageList = formData.value[fieldName];
} else if (fieldName.includes('_videoList') && formData.value[fieldName]) {
schemaItem.componentProps.videoList = formData.value[fieldName];
} else if (fieldName.includes('_fileList') && formData.value[fieldName]) {
schemaItem.componentProps.fileList = formData.value[fieldName];
}
}
}
console.log('数据回显完成 - formData.value:', formData.value);
};
// 提交作品
const saveZpzx = async () => {
if (isSubmitting.value) {
console.log('正在提交中,请勿重复点击');
return;
}
if (submitTimer.value) {
clearTimeout(submitTimer.value);
}
submitTimer.value = setTimeout(async () => {
await performSubmit();
}, 300);
};
// 执行提交
const performSubmit = async () => {
if (isSubmitting.value) {
return;
}
// 验证必填项
const result = [];
for (let i = 0; i < zplxList.value.length; i++) {
const zplx = zplxList.value[i];
const fieldId = zplx.id;
let fieldValue = formData.value[fieldId];
// 处理上传类型的任务
let fileNames = '';
if (zplx.zpfl === "sctp" || zplx.zpfl === "scsp" || zplx.zpfl === "scwd") {
const fileUrls: string[] = [];
const names: string[] = [];
if (zplx.zpfl === "sctp") {
const images = formData.value[`${fieldId}_imageList`] || [];
images.forEach((img: any) => {
if (img.url) {
fileUrls.push(img.url);
names.push(img.name || img.url.split('/').pop() || 'image.jpg');
}
});
} else if (zplx.zpfl === "scsp") {
const videos = formData.value[`${fieldId}_videoList`] || [];
videos.forEach((video: any) => {
if (video.url) {
fileUrls.push(video.url);
names.push(video.name || video.url.split('/').pop() || 'video.mp4');
}
});
} else if (zplx.zpfl === "scwd") {
const files = formData.value[`${fieldId}_fileList`] || [];
files.forEach((file: any) => {
if (file.url) {
fileUrls.push(file.url);
names.push(file.name || file.url.split('/').pop() || 'document');
}
});
}
fieldValue = fileUrls.join(',');
fileNames = names.join(',');
}
// 验证必填项
if (zplx.isbt === 1 || zplx.isbt === true) {
let isEmpty = false;
if (zplx.zpfl === "sctp" || zplx.zpfl === "scsp" || zplx.zpfl === "scwd") {
if (zplx.zpfl === "sctp") {
const images = formData.value[`${fieldId}_imageList`] || [];
isEmpty = images.length === 0;
} else if (zplx.zpfl === "scsp") {
const videos = formData.value[`${fieldId}_videoList`] || [];
isEmpty = videos.length === 0;
} else if (zplx.zpfl === "scwd") {
const files = formData.value[`${fieldId}_fileList`] || [];
isEmpty = files.length === 0;
}
} else {
isEmpty = !fieldValue || fieldValue === "";
}
if (isEmpty) {
uni.showToast({ title: `请填写必填项:${zplx.zpbt || '作品内容'}`, icon: 'none' });
return;
}
}
// 查找对应的作品清单记录ID
const existingRecord = zpqdList.value.find((item: ZpqdItem) => item.zplxId === fieldId);
const recordId = existingRecord ? existingRecord.id : undefined;
result.push({
id: recordId,
zplxId: fieldId,
zpqdtx: fieldValue,
wjmc: fileNames || undefined,
});
}
try {
isSubmitting.value = true;
if (!xsId.value) {
uni.showToast({ title: '缺少学生ID无法提交', icon: 'error' });
isSubmitting.value = false;
return;
}
// 获取学生信息
const curXs = userStore.curXs;
if (!curXs) {
uni.showToast({ title: '请先选择学生', icon: 'error' });
isSubmitting.value = false;
return;
}
const currentXsId = xsId.value || curXs?.id || '';
if (!currentXsId) {
uni.showToast({ title: '请先选择学生', icon: 'error' });
isSubmitting.value = false;
return;
}
// 验证必须有作品执行ID
if (!zpzxId.value) {
uni.showToast({ title: '缺少作品执行ID无法提交', icon: 'error' });
isSubmitting.value = false;
return;
}
// 构建提交数据:只传递 id 和作品清单
const submitData: any = {
id: zpzxId.value, // 作品执行ID必须
zpqdDtos: result // 作品清单
};
await zpzxSaveApi(submitData);
uni.showToast({ title: '提交成功', icon: 'success' });
// 延迟返回并刷新列表页面
setTimeout(() => {
// 获取当前页面的参数
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.options || {};
const kcId = options.kcId || '';
const xsIdParam = options.xsId || xsId.value || '';
// 返回到 index.vue 并刷新
uni.navigateBack({
delta: 1,
success: () => {
// 通知列表页面刷新
uni.$emit('refreshTaskList');
},
fail: () => {
// 如果返回失败,直接跳转到列表页面
const pages = getCurrentPages();
if (pages.length > 1) {
uni.navigateBack({ delta: 1 });
} else {
uni.redirectTo({
url: `/pages/base/xszp/index${kcId ? `?kcId=${kcId}` : ''}${xsIdParam ? `&xsId=${xsIdParam}` : ''}`,
success: () => {
uni.$emit('refreshTaskList');
}
});
}
}
});
}, 1500);
} catch (error) {
console.error('提交失败:', error);
uni.showToast({
title: error instanceof Error ? error.message : '提交失败,请重试',
icon: 'error'
});
} finally {
isSubmitting.value = false;
}
};
</script>
<style scoped lang="scss">
.zp-submit-page {
background-color: #f4f5f7;
min-height: 100vh;
padding-bottom: 80px;
box-sizing: border-box;
}
.loading-indicator {
text-align: center;
color: #999;
padding: 40px 15px;
font-size: 14px;
}
.content-wrapper {
flex: 1;
transition: height 0.3s ease;
&.full-height {
height: 100vh;
}
.p-15 {
padding: 15px;
}
.mt-15 {
margin-top: 15px;
}
}
// 第一部分:任务要求
.zp-info-section {
margin-bottom: 20px;
padding: 15px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #4e73df;
padding-bottom: 5px;
}
.info-item {
display: flex;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
.label {
width: 80px;
color: #666;
font-size: 14px;
flex-shrink: 0;
}
.value {
flex: 1;
color: #333;
font-size: 14px;
word-break: break-word;
&.title-bold {
font-weight: bold;
font-size: 16px;
}
}
}
}
// 第二部分:任务执行
.zp-execute-section {
margin-bottom: 20px;
padding: 15px;
background: #fff;
.part-section {
margin-bottom: 30px;
&:last-child {
margin-bottom: 0;
}
.part-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #4e73df;
padding: 10px 15px;
background: linear-gradient(135deg, #f0f4ff 0%, #e8f0ff 100%);
border-left: 4px solid #4e73df;
border-radius: 4px;
// 允许换行 - 调试样式
white-space: normal !important;
word-break: break-word !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
line-height: 1.5 !important;
// 调试边框(可以临时启用查看)
// border: 1px solid red !important;
// background: rgba(255, 0, 0, 0.1) !important;
}
}
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.section-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #4e73df;
padding-bottom: 5px;
}
.execute-form {
padding-top: 5px;
}
}
// 提交按钮 - 固定在底部
.submit-button-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 15px;
background-color: #fff;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 10;
border-top: 1px solid #eee;
transition: transform 0.3s ease, opacity 0.3s ease;
&.hide-bottom {
transform: translateY(100%);
opacity: 0;
pointer-events: none;
}
.action-button {
width: 100%;
height: 44px;
line-height: 44px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
background-color: #4e73df;
color: #ffffff;
border: none;
&:active:not(:disabled) {
background-color: #2e59d9;
transform: translateY(1px);
}
&:disabled {
background-color: #d9d9d9 !important;
color: #999 !important;
cursor: not-allowed;
opacity: 0.6;
}
}
}
// 提交遮罩层样式
.submit-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.submit-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f3f3f3;
border-top: 3px solid #4e73df;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 16px;
color: #333;
font-weight: 500;
}
// 为输入框添加基本边框
:deep(.uni-input),
:deep(.uni-textarea) {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
:deep(input),
:deep(textarea) {
width: 100% !important;
min-height: 35px !important;
font-size: 13px !important;
border: 1px #CCCCCC solid !important;
border-radius: 3px !important;
padding: 8px 12px !important;
box-sizing: border-box !important;
}
// 只针对 zpbt 字段label 文本)的换行问题
:deep(.zp-execute-section) {
// 针对 FormsItem 中的 label 容器
// 只影响 forms-item-row 中直接包含 label 的 flex-row不影响选择器内部的 flex-row
.forms-item-row {
// label 容器flex-row- 移除宽度限制,允许换行
// 使用 :not() 排除选择器组件内部的 flex-row
> .flex-row:not(.wh-full) {
// 关键移除固定宽度限制FormsItem.vue 中默认是 80px
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
// 不允许 flex 容器换行,保持 * 号和文本在同一行
flex-wrap: nowrap !important;
display: flex !important;
align-items: flex-start !important;
// 必填标记(红色星号)- 与文本保持在同一行
> view:first-child {
flex-shrink: 0 !important;
width: 10px !important;
// 确保 * 号与文本顶部对齐
align-self: flex-start !important;
padding-top: 2px !important;
}
// 图标容器(如果有)
> view:nth-child(2) {
flex-shrink: 0 !important;
align-self: flex-start !important;
}
// 包含 label 文本的 view第15行的 viewzpbt 字段)
> view:last-child {
// 允许文本在容器内换行,但不与 * 号分离
white-space: normal !important;
word-break: break-word !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
width: auto !important;
max-width: calc(100% - 20px) !important; // 减去 * 号和图标的空间
min-width: 0 !important;
flex: 1 1 auto !important;
display: block !important;
line-height: 1.5 !important;
box-sizing: border-box !important;
// 确保文本与 * 号顶部对齐
align-self: flex-start !important;
}
}
}
// 确保选择器内部的提示信息可以换行
.wh-full.flex-row {
flex-wrap: wrap !important;
// 提示信息容器(第一个 view
> view:first-child {
white-space: normal !important;
word-break: break-word !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
flex: 1 1 auto !important;
min-width: 0 !important;
max-width: 100% !important;
// 文本内容
text,
span {
white-space: normal !important;
word-break: break-word !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
display: block !important;
}
}
// 右侧图标保持不换行
> view:last-child {
flex-shrink: 0 !important;
}
}
}
</style>