zhxy-jzd/src/pages/base/xszp/submit.vue
2026-03-16 15:19:39 +08:00

1121 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 作品任务提交页面 -->
<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") {
// 上传视频(视频大小限制 50MB
componentConfig = {
component: "ImageVideoUpload",
componentProps: {
enableImage: false,
enableVideo: true,
enableFile: false,
maxVideoCount: 30,
uploadApi: attachmentUpload,
compressConfig: {
...COMPRESS_PRESETS.medium,
video: {
...COMPRESS_PRESETS.medium.video,
maxSize: 50 * 1024 * 1024
}
},
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>