Files
track/track-uniapp/src/pages/scan/records.vue

194 lines
6.8 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="page">
<view v-if="loading" class="loading">加载中...</view>
<view v-if="error" class="error-box">{{ error }}</view>
<template v-if="!loading && task">
<!-- 任务信息头 -->
<view class="task-header">
<text class="task-name">{{ task.task_name }}</text>
<text class="task-meta">负责人: {{ task.assignee_id || '—' }} · {{ statusLabel(task.status) }}</text>
</view>
<!-- 记录时间轴 -->
<view v-if="records.length" class="timeline">
<view v-for="(rec, i) in records" :key="rec.id" class="tl-item">
<!-- 时间轴竖线 + 圆点 -->
<view class="tl-line">
<view class="tl-dot" :class="i === 0 ? 'tl-dot-latest' : ''" />
<view v-if="i < records.length - 1" class="tl-connector" />
</view>
<!-- 内容卡片 -->
<view class="tl-card">
<view class="tl-top">
<text class="tl-time">{{ formatTime(rec.created_at) }}</text>
<view v-if="canEdit" class="tl-actions">
<text class="tl-act" @tap="openEditRecord(rec)">✏️</text>
<text class="tl-act" @tap="confirmDelete(rec)">🗑️</text>
</view>
</view>
<text v-if="rec.remark" class="tl-remark">{{ rec.remark }}</text>
<view v-if="rec.images && rec.images.length" class="tl-images">
<image
v-for="(img, j) in rec.images"
:key="j"
:src="imageUrl(img)"
mode="aspectFill"
class="tl-thumb"
@tap="previewImage(rec.images, j)"
/>
</view>
</view>
</view>
</view>
<view v-else class="empty-timeline">
<text class="empty-icon">📭</text>
<text class="empty-text">暂无历史记录</text>
</view>
</template>
</view>
</template>
<script>
import request, { get, put } from "../../utils/request";
export default {
data() {
return {
loading: true,
error: "",
task: null,
records: [],
currentUser: null,
};
},
computed: {
canEdit() {
if (!this.currentUser || !this.task) return false;
return (
this.currentUser.id == this.task.assignee_id ||
this.currentUser.username == this.task.assignee_id
);
},
},
onLoad(options) {
this.loadCurrentUser();
const taskId = options.taskId || "";
if (taskId) {
this.loadTask(taskId);
} else {
this.error = "缺少任务ID";
this.loading = false;
}
},
methods: {
loadCurrentUser() {
try {
let user = uni.getStorageSync("user");
if (typeof user === "string" && user) {
try { user = JSON.parse(user); } catch (e) { user = null; }
}
if (user && typeof user === "object") {
this.currentUser = user;
}
} catch {}
},
async loadTask(taskId) {
this.loading = true;
try {
// 通过产品扫码接口反向获取任务(后端可能没有单独的任务查询接口)
// 这里假设后端提供 GET /tasks/{taskId}
this.task = await get(`/tasks/${taskId}`);
this.records = this.task.records || [];
} catch {
this.error = "加载任务失败";
} finally {
this.loading = false;
}
},
imageUrl(url) {
if (!url) return "";
if (url.startsWith("http")) return url;
return "http://192.168.9.80:8011" + (url.startsWith("/") ? url : "/" + url);
},
previewImage(urls, index) {
const fullUrls = (urls || []).map((u) => this.imageUrl(u));
uni.previewImage({ urls: fullUrls, current: index });
},
formatTime(t) {
if (!t) return "";
const d = new Date(t);
const pad = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
},
statusLabel(s) {
const map = { PENDING: "待接收", WIP: "进行中", COMPLETED: "已完成", REJECTED: "已驳回", ARCHIVED: "已入库" };
return map[s] || s;
},
openEditRecord(rec) {
// 复用 detail.vue 的编辑流程 — 通过全局事件或直接跳回
// 简单方案:在当前页弹窗编辑
uni.showToast({ title: "编辑功能请返回详情页操作", icon: "none", duration: 2000 });
},
confirmDelete(rec) {
uni.showModal({
title: "删除记录",
content: "确定删除这条记录吗?",
confirmColor: "#dc2626",
success: async (res) => {
if (res.confirm) {
try {
await request({ url: `/records/${rec.id}`, method: "DELETE" });
uni.showToast({ title: "已删除", icon: "success" });
this.records = this.records.filter((r) => r.id !== rec.id);
} catch {
uni.showToast({ title: "删除失败", icon: "none" });
}
}
},
});
},
},
};
</script>
<style scoped>
.page { min-height: 100vh; padding: 16px; padding-bottom: 40px; background: #f3f4f6; }
.loading { text-align: center; padding: 48px 0; color: #6b7280; }
.error-box { padding: 12px; border-radius: 10px; background: #fef2f2; color: #dc2626; font-size: 13px; }
/* 任务头 */
.task-header { background: #fff; border-radius: 12px; padding: 16px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
.task-name { font-size: 18px; font-weight: 700; color: #1f2937; display: block; }
.task-meta { font-size: 13px; color: #9ca3af; margin-top: 4px; display: block; }
/* 时间轴 */
.timeline { padding-left: 8px; }
.tl-item { display: flex; gap: 12px; }
.tl-line { display: flex; flex-direction: column; align-items: center; width: 24px; flex-shrink: 0; }
.tl-dot { width: 12px; height: 12px; border-radius: 50%; background: #d1d5db; margin-top: 6px; }
.tl-dot-latest { background: #2563eb; box-shadow: 0 0 0 4px rgba(37,99,235,0.15); }
.tl-connector { flex: 1; width: 2px; background: #e5e7eb; min-height: 12px; }
.tl-card { flex: 1; background: #fff; border-radius: 10px; padding: 12px; margin-bottom: 12px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); }
.tl-top { display: flex; align-items: center; justify-content: space-between; }
.tl-time { font-size: 12px; color: #9ca3af; }
.tl-actions { display: flex; gap: 12px; }
.tl-act { font-size: 16px; padding: 2px; }
.tl-remark { display: block; font-size: 14px; color: #374151; margin-top: 6px; line-height: 1.5; word-break: break-all; }
.tl-images { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.tl-thumb { width: 80px; height: 80px; border-radius: 8px; background: #e5e7eb; }
.empty-timeline { display: flex; flex-direction: column; align-items: center; padding-top: 60px; }
.empty-icon { font-size: 48px; margin-bottom: 8px; }
.empty-text { font-size: 14px; color: #9ca3af; }
</style>