chore: prepare release build

This commit is contained in:
wushumin
2026-05-16 16:32:56 +08:00
parent dd56e0861b
commit deecb5d33e
28 changed files with 4396 additions and 361 deletions

View File

@@ -26,6 +26,7 @@ const internalRemark = ref("");
const zhongjianReportNo = ref("");
const zhongjianFiles = ref<AdminFileAsset[]>([]);
const evidenceFiles = ref<AdminFileAsset[]>([]);
const activePreviewVideo = ref<AdminFileAsset | null>(null);
const supplementForm = reactive({
reason: "",
deadline: "",
@@ -33,6 +34,11 @@ const supplementForm = reactive({
});
const isZhongjian = computed(() => detail.value?.task_info.service_provider === "zhongjian");
const isTaskReadonly = computed(() => {
const status = detail.value?.task_info.status || "";
return status === "submitted" || status === "completed";
});
const internalTagNo = computed(() => detail.value?.task_info.internal_tag_no || "");
const resultSummary = computed(() => detail.value?.result_info.result_text || "暂未填写");
const reportSummary = computed(() => detail.value?.report_summary?.report_no || "");
type AppraisalTemplate = NonNullable<AdminAppraisalTaskDetail["appraisal_template"]>;
@@ -129,10 +135,18 @@ async function fetchDetail() {
}
function addSupplementItem() {
if (isTaskReadonly.value) {
showInfoToast("当前任务已完成,不能再编辑");
return;
}
supplementForm.items.push({ item_name: "", guide_text: "", is_required: true });
}
function removeSupplementItem(index: number) {
if (isTaskReadonly.value) {
showInfoToast("当前任务已完成,不能再编辑");
return;
}
if (supplementForm.items.length === 1) {
supplementForm.items[0].item_name = "";
supplementForm.items[0].guide_text = "";
@@ -143,8 +157,12 @@ function removeSupplementItem(index: number) {
}
async function removeEvidenceFile(fileUrl: string) {
if (isTaskReadonly.value || !detail.value) {
showInfoToast("当前任务已完成,不能再删除附件");
return;
}
try {
await adminApi.deleteAppraisalEvidenceFile(fileUrl);
await adminApi.deleteAppraisalEvidenceFile(fileUrl, detail.value.task_info.id);
evidenceFiles.value = evidenceFiles.value.filter((item) => item.file_url !== fileUrl);
showInfoToast("附件已删除");
} catch (error) {
@@ -153,8 +171,12 @@ async function removeEvidenceFile(fileUrl: string) {
}
async function removeZhongjianFile(fileUrl: string) {
if (isTaskReadonly.value || !detail.value) {
showInfoToast("当前任务已完成,不能再删除文件");
return;
}
try {
await adminApi.deleteAppraisalEvidenceFile(fileUrl);
await adminApi.deleteAppraisalEvidenceFile(fileUrl, detail.value.task_info.id);
zhongjianFiles.value = zhongjianFiles.value.filter((item) => item.file_url !== fileUrl);
showInfoToast("文件已删除");
} catch (error) {
@@ -162,7 +184,41 @@ async function removeZhongjianFile(fileUrl: string) {
}
}
function isImageAsset(item: AdminFileAsset) {
return item.file_type === "image" || item.mime_type?.startsWith("image/");
}
function isVideoAsset(item: AdminFileAsset) {
return item.file_type === "video" || item.mime_type?.startsWith("video/");
}
function attachmentTypeLabel(item: AdminFileAsset) {
if (isImageAsset(item)) return "图片";
if (isVideoAsset(item)) return "视频";
return "附件";
}
function previewAttachment(files: AdminFileAsset[], item: AdminFileAsset) {
if (isImageAsset(item)) {
const urls = files.filter(isImageAsset).map((asset) => asset.file_url);
uni.previewImage({ urls, current: item.file_url });
return;
}
if (isVideoAsset(item)) {
activePreviewVideo.value = item;
return;
}
showInfoToast("当前附件暂不支持预览");
}
function closePreviewVideo() {
activePreviewVideo.value = null;
}
function updateTemplatePoint(index: number, key: "point_value" | "point_remark", value: string) {
if (isTaskReadonly.value) return;
const template = detail.value?.appraisal_template;
if (!template) return;
const current = template.key_points[index];
@@ -195,7 +251,90 @@ function returnToWorkOrders(message: string) {
}, 700);
}
function confirmPublishReport() {
return new Promise<boolean>((resolve) => {
uni.showModal({
title: "提交确认",
content: "是否已鉴定完成并确定发布报告?",
cancelText: "取消",
confirmText: "去绑定",
success: (result) => resolve(Boolean(result.confirm)),
fail: () => resolve(false),
});
});
}
function promptMaterialTagQrInput() {
return new Promise<string>((resolve, reject) => {
uni.showModal({
title: "绑定验真吊牌",
content: "本地预览无法直接扫码,请输入或粘贴吊牌二维码内容。",
editable: true,
placeholderText: "二维码内容 / 验真吊牌编号",
cancelText: "取消",
confirmText: "绑定",
success: (result) => {
if (!result.confirm) {
reject(new Error("已取消绑定验真吊牌"));
return;
}
const qrInput = String(result.content || "").trim();
if (!qrInput) {
reject(new Error("请输入验真吊牌二维码内容"));
return;
}
resolve(qrInput);
},
fail: () => reject(new Error("已取消绑定验真吊牌")),
});
});
}
function scanMaterialTagQr() {
return new Promise<string>((resolve, reject) => {
uni.scanCode({
scanType: ["barCode", "qrCode"],
success: (result) => {
const qrInput = String(result.result || "").trim();
if (!qrInput) {
reject(new Error("未识别到验真吊牌二维码"));
return;
}
resolve(qrInput);
},
fail: () => {
// #ifdef H5
promptMaterialTagQrInput().then(resolve).catch(reject);
// #endif
// #ifndef H5
reject(new Error("已取消绑定验真吊牌"));
// #endif
},
});
});
}
async function confirmAndScanMaterialTag() {
const confirmed = await confirmPublishReport();
if (!confirmed) {
return "";
}
try {
return await scanMaterialTagQr();
} catch (error) {
showErrorToast(error, "验真吊牌扫码失败");
return "";
}
}
async function chooseEvidenceImage() {
if (isTaskReadonly.value || !detail.value) {
showInfoToast("当前任务已完成,不能再上传附件");
return;
}
try {
const result = await uni.chooseImage({
count: 9,
@@ -205,7 +344,7 @@ async function chooseEvidenceImage() {
if (!result.tempFilePaths?.length) return;
uploading.value = true;
for (const filePath of result.tempFilePaths) {
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath);
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath, detail.value.task_info.id);
evidenceFiles.value.push(asset);
}
showInfoToast("图片上传成功");
@@ -217,6 +356,10 @@ async function chooseEvidenceImage() {
}
async function chooseEvidenceVideo() {
if (isTaskReadonly.value || !detail.value) {
showInfoToast("当前任务已完成,不能再上传附件");
return;
}
try {
const result = await uni.chooseVideo({
sourceType: ["album", "camera"],
@@ -224,7 +367,7 @@ async function chooseEvidenceVideo() {
const filePath = result.tempFilePath;
if (!filePath) return;
uploading.value = true;
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath);
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath, detail.value.task_info.id);
evidenceFiles.value.push(asset);
showInfoToast("视频上传成功");
} catch (error) {
@@ -235,6 +378,10 @@ async function chooseEvidenceVideo() {
}
async function chooseZhongjianImage() {
if (isTaskReadonly.value || !detail.value) {
showInfoToast("当前任务已完成,不能再上传文件");
return;
}
try {
const result = await uni.chooseImage({
count: 9,
@@ -244,7 +391,7 @@ async function chooseZhongjianImage() {
if (!result.tempFilePaths?.length) return;
uploading.value = true;
for (const filePath of result.tempFilePaths) {
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath);
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath, detail.value.task_info.id);
zhongjianFiles.value.push(asset);
}
showInfoToast("图片上传成功");
@@ -256,6 +403,10 @@ async function chooseZhongjianImage() {
}
async function chooseZhongjianVideo() {
if (isTaskReadonly.value || !detail.value) {
showInfoToast("当前任务已完成,不能再上传文件");
return;
}
try {
const result = await uni.chooseVideo({
sourceType: ["album", "camera"],
@@ -263,7 +414,7 @@ async function chooseZhongjianVideo() {
const filePath = result.tempFilePath;
if (!filePath) return;
uploading.value = true;
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath);
const asset = await adminApi.uploadAppraisalEvidenceFile(filePath, detail.value.task_info.id);
zhongjianFiles.value.push(asset);
showInfoToast("视频上传成功");
} catch (error) {
@@ -275,6 +426,10 @@ async function chooseZhongjianVideo() {
async function submitResult(action: "save" | "submit") {
if (!detail.value) return;
if (isTaskReadonly.value) {
showInfoToast("当前任务已完成,不能再提交");
return;
}
if (isZhongjian.value) {
showInfoToast("中检订单请切换到中检报告区");
activeSection.value = "zhongjian";
@@ -286,6 +441,11 @@ async function submitResult(action: "save" | "submit") {
return;
}
const qrInput = action === "submit" ? await confirmAndScanMaterialTag() : "";
if (action === "submit" && !qrInput) {
return;
}
submitting.value = true;
try {
const conditionPayload = showConditionFields.value
@@ -330,10 +490,11 @@ async function submitResult(action: "save" | "submit") {
internal_remark: internalRemark.value.trim(),
attachments: evidenceFiles.value,
key_points: templateKeyPointsPayload(),
...(qrInput ? { qr_input: qrInput } : {}),
}),
);
if (action === "submit") {
returnToWorkOrders("鉴定已提交,正在返回工单");
returnToWorkOrders("验真吊牌已绑定,报告已发布");
return;
}
showInfoToast("鉴定已保存");
@@ -347,6 +508,10 @@ async function submitResult(action: "save" | "submit") {
async function submitSupplement() {
if (!detail.value) return;
if (isTaskReadonly.value) {
showInfoToast("当前任务已完成,不能再发起补资料");
return;
}
const items = supplementForm.items.filter((item) => item.item_name.trim());
if (!supplementForm.reason.trim()) {
showInfoToast("请先填写补资料原因");
@@ -380,6 +545,10 @@ async function submitSupplement() {
async function submitZhongjianReport() {
if (!detail.value) return;
if (isTaskReadonly.value) {
showInfoToast("当前任务已完成,不能再提交");
return;
}
if (!zhongjianReportNo.value.trim()) {
showInfoToast("请填写中检报告编号");
return;
@@ -389,14 +558,20 @@ async function submitZhongjianReport() {
return;
}
const qrInput = await confirmAndScanMaterialTag();
if (!qrInput) {
return;
}
submitting.value = true;
try {
await adminApi.saveZhongjianAppraisalReport({
id: detail.value.task_info.id,
zhongjian_report_no: zhongjianReportNo.value.trim(),
report_files: zhongjianFiles.value,
qr_input: qrInput,
});
returnToWorkOrders("中检报告已提交,正在返回工单");
returnToWorkOrders("验真吊牌已绑定,报告已发布");
} catch (error) {
showErrorToast(error, "中检报告录入失败");
} finally {
@@ -418,7 +593,7 @@ onLoad((options) => {
});
onShow(() => {
if (taskId.value) {
if (taskId.value && !pageReady.value) {
void fetchDetail();
}
});
@@ -461,9 +636,17 @@ onShow(() => {
<view class="meta-label">报告摘要</view>
<view class="meta-value">{{ reportSummary || "-" }}</view>
</view>
<view v-if="internalTagNo" class="meta-item meta-item--wide">
<view class="meta-label">流转码编号</view>
<view class="meta-value transfer-code-value">{{ internalTagNo }}</view>
</view>
</view>
</view>
<view v-if="isTaskReadonly" class="readonly-notice">
当前工单已完成鉴定内容和附件仅可查看
</view>
<view class="card">
<view class="segmented">
<view :class="['segment', activeSection === 'result' ? 'segment--active' : '']" @click="activeSection = 'result'">鉴定结论</view>
@@ -475,21 +658,21 @@ onShow(() => {
<view v-if="activeSection === 'result' && !isZhongjian" class="card">
<view class="card-title">鉴定结论</view>
<view class="stack" style="margin-top: 18rpx">
<input v-model="resultText" class="field" placeholder="结论,例如:正品 / 存疑" />
<textarea v-model="resultDesc" class="textarea" placeholder="结论说明" />
<input v-model="resultText" class="field" :disabled="isTaskReadonly" placeholder="结论,例如:正品 / 存疑" />
<textarea v-model="resultDesc" class="textarea" :disabled="isTaskReadonly" placeholder="结论说明" />
<template v-if="showConditionFields">
<input v-model="conditionGrade" class="field" placeholder="成色评级" />
<textarea v-model="conditionDesc" class="textarea" placeholder="成色说明" />
<input v-model="conditionGrade" class="field" :disabled="isTaskReadonly" placeholder="成色评级" />
<textarea v-model="conditionDesc" class="textarea" :disabled="isTaskReadonly" placeholder="成色说明" />
</template>
<template v-if="showValuationFields">
<view class="meta-grid">
<input v-model="valuationMin" class="field" placeholder="最低估值" />
<input v-model="valuationMax" class="field" placeholder="最高估值" />
<input v-model="valuationMin" class="field" :disabled="isTaskReadonly" placeholder="最低估值" />
<input v-model="valuationMax" class="field" :disabled="isTaskReadonly" placeholder="最高估值" />
</view>
<textarea v-model="valuationDesc" class="textarea" placeholder="估值说明" />
<textarea v-model="valuationDesc" class="textarea" :disabled="isTaskReadonly" placeholder="估值说明" />
</template>
<textarea v-model="externalRemark" class="textarea" placeholder="对外备注" />
<textarea v-model="internalRemark" class="textarea" placeholder="内部备注" />
<textarea v-model="externalRemark" class="textarea" :disabled="isTaskReadonly" placeholder="对外备注" />
<textarea v-model="internalRemark" class="textarea" :disabled="isTaskReadonly" placeholder="内部备注" />
</view>
<view v-if="detail.appraisal_template?.key_points?.length" class="stack" style="margin-top: 20rpx">
@@ -502,12 +685,14 @@ onShow(() => {
<input
:value="item.point_value"
class="field"
:disabled="isTaskReadonly"
:placeholder="`${item.point_name} 值`"
@input="updateTemplatePointFromInput(index, 'point_value', $event)"
/>
<textarea
:value="item.point_remark"
class="textarea"
:disabled="isTaskReadonly"
:placeholder="`${item.point_name} 说明`"
@input="updateTemplatePointFromInput(index, 'point_remark', $event)"
/>
@@ -515,15 +700,34 @@ onShow(() => {
</view>
<view class="card-desc evidence-title">证据附件</view>
<view v-if="evidenceFiles.length" class="list" style="margin-top: 14rpx">
<view v-for="item in evidenceFiles" :key="item.file_url" class="list-card">
<view class="row">
<view class="list-title">{{ item.name || item.file_id }}</view>
<text class="tag tag--danger" @click="removeEvidenceFile(item.file_url)">删除</text>
<view v-if="evidenceFiles.length" class="attachment-grid">
<view v-for="item in evidenceFiles" :key="item.file_url" class="attachment-tile">
<view class="attachment-preview" @click="previewAttachment(evidenceFiles, item)">
<image v-if="isImageAsset(item)" class="attachment-thumb" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
<video
v-else-if="isVideoAsset(item)"
class="attachment-thumb attachment-video-thumb"
:src="item.file_url"
:controls="false"
:muted="true"
:show-center-play-btn="false"
:enable-progress-gesture="false"
object-fit="cover"
@click.stop="previewAttachment(evidenceFiles, item)"
/>
<view v-else class="attachment-file-thumb">附件</view>
<view v-if="isVideoAsset(item)" class="attachment-play"></view>
</view>
<view class="attachment-meta">
<view class="attachment-name">{{ item.name || item.file_id }}</view>
<view class="attachment-actions">
<text class="attachment-type">{{ attachmentTypeLabel(item) }}</text>
<text v-if="!isTaskReadonly" class="attachment-remove" @click.stop="removeEvidenceFile(item.file_url)">删除</text>
</view>
</view>
</view>
</view>
<view class="upload-actions">
<view v-if="!isTaskReadonly" class="upload-actions">
<button class="action-button action-button--secondary" :disabled="uploading" @click="chooseEvidenceImage">
<text class="action-symbol">+</text>
<text>{{ uploading ? "上传中" : "添加图片" }}</text>
@@ -534,7 +738,7 @@ onShow(() => {
</button>
</view>
<view class="form-actions">
<view v-if="!isTaskReadonly" class="form-actions">
<button class="form-action form-action--secondary" :disabled="submitting" @click="submitResult('save')">保存</button>
<button class="form-action form-action--primary" :disabled="submitting" @click="submitResult('submit')">提交</button>
</view>
@@ -543,19 +747,19 @@ onShow(() => {
<view v-else-if="activeSection === 'supplement'" class="card">
<view class="card-title">补资料</view>
<view class="stack" style="margin-top: 18rpx">
<textarea v-model="supplementForm.reason" class="textarea" placeholder="补资料原因" />
<input v-model="supplementForm.deadline" class="field" placeholder="截止时间(可选)" />
<textarea v-model="supplementForm.reason" class="textarea" :disabled="isTaskReadonly" placeholder="补资料原因" />
<input v-model="supplementForm.deadline" class="field" :disabled="isTaskReadonly" placeholder="截止时间(可选)" />
<view v-for="(item, index) in supplementForm.items" :key="index" class="stack">
<input v-model="item.item_name" class="field" placeholder="补资料项名称" />
<textarea v-model="item.guide_text" class="textarea" placeholder="补资料说明" />
<view class="row">
<input v-model="item.item_name" class="field" :disabled="isTaskReadonly" placeholder="补资料项名称" />
<textarea v-model="item.guide_text" class="textarea" :disabled="isTaskReadonly" placeholder="补资料说明" />
<view v-if="!isTaskReadonly" class="row">
<text class="tag" :class="item.is_required ? 'tag--warning' : ''" @click="item.is_required = !item.is_required">
{{ item.is_required ? "必传" : "选传" }}
</text>
<text class="tag tag--danger" @click="removeSupplementItem(index)">删除</text>
</view>
</view>
<view class="form-actions">
<view v-if="!isTaskReadonly" class="form-actions">
<button class="form-action form-action--secondary" @click="addSupplementItem">添加一项</button>
<button class="form-action form-action--primary" :disabled="supplementSubmitting" @click="submitSupplement">发起补资料</button>
</view>
@@ -565,16 +769,35 @@ onShow(() => {
<view v-else class="card">
<view class="card-title">中检报告</view>
<view class="stack" style="margin-top: 18rpx">
<input v-model="zhongjianReportNo" class="field" placeholder="中检报告编号" />
<view v-if="zhongjianFiles.length" class="list">
<view v-for="item in zhongjianFiles" :key="item.file_url" class="list-card">
<view class="row">
<view class="list-title">{{ item.name || item.file_id }}</view>
<text class="tag tag--danger" @click="removeZhongjianFile(item.file_url)">删除</text>
<input v-model="zhongjianReportNo" class="field" :disabled="isTaskReadonly" placeholder="中检报告编号" />
<view v-if="zhongjianFiles.length" class="attachment-grid">
<view v-for="item in zhongjianFiles" :key="item.file_url" class="attachment-tile">
<view class="attachment-preview" @click="previewAttachment(zhongjianFiles, item)">
<image v-if="isImageAsset(item)" class="attachment-thumb" :src="item.thumbnail_url || item.file_url" mode="aspectFill" />
<video
v-else-if="isVideoAsset(item)"
class="attachment-thumb attachment-video-thumb"
:src="item.file_url"
:controls="false"
:muted="true"
:show-center-play-btn="false"
:enable-progress-gesture="false"
object-fit="cover"
@click.stop="previewAttachment(zhongjianFiles, item)"
/>
<view v-else class="attachment-file-thumb">附件</view>
<view v-if="isVideoAsset(item)" class="attachment-play"></view>
</view>
<view class="attachment-meta">
<view class="attachment-name">{{ item.name || item.file_id }}</view>
<view class="attachment-actions">
<text class="attachment-type">{{ attachmentTypeLabel(item) }}</text>
<text v-if="!isTaskReadonly" class="attachment-remove" @click.stop="removeZhongjianFile(item.file_url)">删除</text>
</view>
</view>
</view>
</view>
<view class="upload-actions">
<view v-if="!isTaskReadonly" class="upload-actions">
<button class="action-button action-button--secondary" :disabled="uploading" @click="chooseZhongjianImage">
<text class="action-symbol">+</text>
<text>{{ uploading ? "上传中" : "添加图片" }}</text>
@@ -584,13 +807,23 @@ onShow(() => {
<text>{{ uploading ? "上传中" : "添加视频" }}</text>
</button>
</view>
<view class="form-actions" :class="detail.report_summary?.id ? '' : 'form-actions--single'">
<button class="form-action form-action--primary" :disabled="submitting" @click="submitZhongjianReport">提交并发布</button>
<view v-if="!isTaskReadonly || detail.report_summary?.id" class="form-actions" :class="detail.report_summary?.id && !isTaskReadonly ? '' : 'form-actions--single'">
<button v-if="!isTaskReadonly" class="form-action form-action--primary" :disabled="submitting" @click="submitZhongjianReport">提交并发布</button>
<button v-if="detail.report_summary?.id" class="form-action form-action--secondary" @click="openReportDetail">查看报告</button>
</view>
</view>
</view>
<view v-if="activePreviewVideo" class="video-preview-mask" @click="closePreviewVideo">
<view class="video-preview-panel" @click.stop>
<view class="video-preview-head">
<text class="video-preview-title">{{ activePreviewVideo.name || "附件视频" }}</text>
<text class="video-preview-close" @click="closePreviewVideo">关闭</text>
</view>
<video class="video-preview-player" :src="activePreviewVideo.file_url" controls autoplay />
</view>
</view>
<view class="card">
<view class="card-title">任务信息</view>
<view class="meta-grid">
@@ -610,6 +843,10 @@ onShow(() => {
<view class="meta-label">处理人</view>
<view class="meta-value">{{ detail.task_info.assignee_name }}</view>
</view>
<view v-if="internalTagNo" class="meta-item meta-item--wide">
<view class="meta-label">流转码编号</view>
<view class="meta-value transfer-code-value">{{ internalTagNo }}</view>
</view>
</view>
</view>
</template>
@@ -622,12 +859,137 @@ onShow(() => {
gap: 14rpx;
}
.readonly-notice {
margin: -6rpx 0 18rpx;
padding: 18rpx 22rpx;
border: 1px solid var(--work-border);
border-radius: var(--work-radius-sm);
background: var(--work-card-muted);
color: var(--work-text-soft);
font-size: 24rpx;
font-weight: 700;
line-height: 1.5;
}
.meta-item--wide {
grid-column: 1 / -1;
}
.transfer-code-value {
color: var(--work-warning);
font-weight: 900;
word-break: break-all;
}
.evidence-title {
margin-top: 24rpx;
color: var(--work-text);
font-weight: 800;
}
.attachment-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 14rpx;
margin-top: 14rpx;
}
.attachment-tile {
min-width: 0;
}
.attachment-preview {
position: relative;
width: 100%;
aspect-ratio: 1;
overflow: hidden;
border: 1px solid var(--work-border);
border-radius: var(--work-radius-sm);
background: var(--work-card-muted);
}
.attachment-thumb {
display: block;
width: 100%;
height: 100%;
}
.attachment-video-thumb {
background: #202124;
}
.attachment-file-thumb {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: var(--work-text-soft);
font-size: 24rpx;
font-weight: 800;
}
.attachment-play {
position: absolute;
left: 50%;
top: 50%;
width: 54rpx;
height: 54rpx;
margin-left: -27rpx;
margin-top: -27rpx;
border-radius: 50%;
background: rgba(32, 33, 36, 0.72);
color: #ffffff;
font-size: 28rpx;
line-height: 54rpx;
text-align: center;
}
.attachment-meta {
min-width: 0;
margin-top: 8rpx;
}
.attachment-name {
min-width: 0;
overflow: hidden;
color: var(--work-text);
font-size: 22rpx;
font-weight: 700;
line-height: 1.35;
text-overflow: ellipsis;
white-space: nowrap;
}
.attachment-actions {
display: flex;
align-items: center;
gap: 8rpx;
margin-top: 6rpx;
}
.attachment-type,
.attachment-remove {
flex: 0 0 auto;
min-height: 34rpx;
padding: 0 10rpx;
border-radius: var(--work-radius-pill);
font-size: 20rpx;
font-weight: 700;
line-height: 34rpx;
white-space: nowrap;
}
.attachment-type {
background: var(--work-info-soft);
color: var(--work-info);
}
.attachment-remove {
background: var(--work-danger-soft);
color: var(--work-danger);
}
.upload-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -702,4 +1064,54 @@ onShow(() => {
background: var(--work-accent);
color: #ffffff;
}
.video-preview-mask {
position: fixed;
z-index: 20;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 32rpx;
background: rgba(0, 0, 0, 0.58);
}
.video-preview-panel {
width: 100%;
overflow: hidden;
border-radius: var(--work-radius);
background: #ffffff;
}
.video-preview-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18rpx;
padding: 22rpx 24rpx;
}
.video-preview-title {
min-width: 0;
overflow: hidden;
color: var(--work-text);
font-size: 28rpx;
font-weight: 800;
text-overflow: ellipsis;
white-space: nowrap;
}
.video-preview-close {
flex: 0 0 auto;
color: var(--work-info);
font-size: 26rpx;
font-weight: 800;
}
.video-preview-player {
display: block;
width: 100%;
height: 58vh;
background: #000000;
}
</style>