| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- # -*- coding: utf-8 -*-
- """Rebuild WaybillDetail.vue with correct UTF-8 Chinese and component structure."""
- import os
- OUT = os.path.join(
- os.path.dirname(os.path.dirname(__file__)),
- "ruoyi-ui", "src", "views", "basic", "waybill", "components", "WaybillDetail.vue",
- )
- # Template + style: decode unicode_escape
- TPL = r"""
- <template>
- <div class="waybill-detail" v-loading="!detail">
- <div class="detail-header">
- <div class="header-left">
- <div class="title-row">
- <span class="waybill-label">\u8fd0\u5355\u7f16\u53f7</span>
- <span class="waybill-no">{{ detail.waybillNo || '-' }}</span>
- <el-tag :type="phase.tagType" size="small" effect="dark">{{ phase.statusText }}</el-tag>
- <el-tag v-if="phase.tempAlarm" type="danger" size="small" effect="plain">
- <i class="el-icon-warning-outline"></i> \u6e29\u5ea6\u5f02\u5e38
- </el-tag>
- </div>
- <div class="meta-row">
- <span>\u521b\u5efa\u4eba\uff1a{{ detail.createBy || '-' }}</span>
- <span class="meta-split">{{ formatDateTime(detail.createTime) }}</span>
- </div>
- </div>
- <div class="header-actions">
- <el-button type="danger" plain size="small" icon="el-icon-phone" @click="contactDriver">\u8054\u7cfb\u53f8\u673a</el-button>
- <el-button v-if="phase.tempAlarm" type="warning" plain size="small" icon="el-icon-bell" @click="handleAlarm">\u5904\u7406\u544a\u8b66</el-button>
- <el-button type="warning" size="small" icon="el-icon-circle-check" @click="confirmReceipt">\u786e\u8ba4\u7b7e\u6536</el-button>
- </div>
- </div>
- <div class="timeline-wrap">
- <el-steps :active="phase.activeStep" align-center finish-status="success">
- <el-step v-for="(step, idx) in timelineSteps" :key="idx" :title="step.title" :description="step.desc" :icon="step.icon" />
- </el-steps>
- </div>
- <el-row :gutter="16" class="detail-body">
- <el-col :span="16">
- <el-card shadow="never" class="detail-card">
- <div slot="header" class="card-title"><i class="el-icon-document"></i> \u57fa\u672c\u4fe1\u606f</div>
- <el-descriptions :column="2" border size="medium">
- <el-descriptions-item label="\u53d1\u8d27\u4f01\u4e1a">{{ detail.senderCompany || '-' }}</el-descriptions-item>
- <el-descriptions-item label="\u6536\u8d27\u4f01\u4e1a">{{ detail.receiverCompany || '-' }}</el-descriptions-item>
- <el-descriptions-item label="\u53d1\u8d27\u5730\u5740" :span="2">{{ senderFullAddress }}</el-descriptions-item>
- <el-descriptions-item label="\u6536\u8d27\u5730\u5740" :span="2">{{ receiverFullAddress }}</el-descriptions-item>
- <el-descriptions-item label="\u53d1\u8d27\u65f6\u95f4">{{ formatDateTime(detail.planDepartTime) }}</el-descriptions-item>
- <el-descriptions-item label="\u9884\u8ba1\u5230\u8fbe">{{ formatDateTime(detail.planArriveTime) }}</el-descriptions-item>
- <el-descriptions-item label="\u5173\u8054\u8f66\u8f86">
- <el-link type="primary" :underline="false">{{ detail.carNum || '-' }}</el-link>
- </el-descriptions-item>
- <el-descriptions-item label="\u9a7e\u9a76\u5458">{{ detail.driverName || '-' }}</el-descriptions-item>
- <el-descriptions-item label="\u6e29\u5ea6\u8981\u6c42">{{ tempRequireText }}</el-descriptions-item>
- <el-descriptions-item label="\u8fd0\u8f93\u7ebf\u8def">{{ routeText }}</el-descriptions-item>
- </el-descriptions>
- </el-card>
- <el-card shadow="never" class="detail-card">
- <div slot="header" class="card-title"><i class="el-icon-goods"></i> \u8d27\u7269\u660e\u7ec6</div>
- <el-table :data="detail.cargoList || []" border size="small" show-summary :summary-method="cargoSummary">
- <el-table-column label="\u8d27\u7269\u540d\u79f0" prop="cargoName" min-width="120" />
- <el-table-column label="\u89c4\u683c" prop="specModel" min-width="100" />
- <el-table-column label="\u6570\u91cf" prop="quantity" width="90" align="right">
- <template slot-scope="scope">{{ formatQty(scope.row.quantity, scope.row.unit) }}</template>
- </el-table-column>
- <el-table-column label="\u91cd\u91cf" prop="weightKg" width="90" align="right">
- <template slot-scope="scope">{{ formatNum(scope.row.weightKg) }}kg</template>
- </el-table-column>
- <el-table-column label="\u8d27\u503c" prop="cargoValue" width="100" align="right">
- <template slot-scope="scope">\u00a5{{ formatMoney(scope.row.cargoValue) }}</template>
- </el-table-column>
- </el-table>
- </el-card>
- <el-card shadow="never" class="detail-card">
- <div slot="header" class="card-title"><i class="el-icon-data-line"></i> \u6e29\u5ea6\u66f2\u7ebf</div>
- <div ref="tempChart" class="temp-chart"></div>
- </el-card>
- </el-col>
- <el-col :span="8">
- <el-card shadow="never" class="detail-card">
- <div slot="header" class="card-title"><i class="el-icon-truck"></i> \u8fd0\u8f93\u5b9e\u65f6\u52a8\u6001</div>
- <div class="route-progress">
- <div class="route-line">
- <span class="route-point start">A</span>
- <div class="route-track">
- <div class="route-fill" :style="{ width: tracking.progress + '%' }"></div>
- <div class="route-vehicle" :style="{ left: tracking.progress + '%' }"><i class="el-icon-truck"></i></div>
- <div class="route-bubble" :style="{ left: Math.min(tracking.progress + 5, 75) + '%' }">
- <div>{{ tracking.location }}</div>
- <div class="bubble-speed">{{ tracking.speed }} km/h</div>
- </div>
- </div>
- <span class="route-point end">B</span>
- </div>
- </div>
- <div class="tracking-stats">
- <div class="stat-item"><span class="stat-label">\u5f53\u524d\u4f4d\u7f6e</span><span class="stat-value">{{ tracking.location }}</span></div>
- <div class="stat-item">
- <span class="stat-label">\u5f53\u524d\u6e29\u5ea6</span>
- <span class="stat-value" :class="{ danger: phase.tempAlarm }">{{ tracking.currentTemp }}\u00b0C
- <el-tag v-if="phase.tempAlarm" type="danger" size="mini" effect="plain">\u8d85\u9650</el-tag>
- </span>
- </div>
- <div class="stat-item"><span class="stat-label">\u5f53\u524d\u8f66\u901f</span><span class="stat-value">{{ tracking.speed }} km/h</span></div>
- <div class="stat-item"><span class="stat-label">\u9884\u8ba1\u5230\u8fbe</span><span class="stat-value">{{ tracking.eta }} <span class="sub">(\u5269\u4f59{{ tracking.remainTime }})</span></span></div>
- <div class="stat-item"><span class="stat-label">\u5df2\u884c\u9a76 / \u5269\u4f59\u91cc\u7a0b</span><span class="stat-value">{{ tracking.traveledKm }} km / {{ tracking.remainKm }} km</span></div>
- </div>
- </el-card>
- <el-card shadow="never" class="detail-card">
- <div slot="header" class="card-title"><i class="el-icon-sunny"></i> \u6e29\u5ea6</div>
- <el-row :gutter="8" class="temp-cards">
- <el-col :span="12"><div class="temp-card current" :class="{ alarm: phase.tempAlarm }"><div class="temp-card-label">\u5f53\u524d\u6e29\u5ea6</div><div class="temp-card-value">{{ tempStats.current }}\u00b0C</div></div></el-col>
- <el-col :span="12"><div class="temp-card max"><div class="temp-card-label">\u6700\u9ad8\u6e29\u5ea6</div><div class="temp-card-value">{{ tempStats.max }}\u00b0C</div></div></el-col>
- <el-col :span="12"><div class="temp-card min"><div class="temp-card-label">\u6700\u4f4e\u6e29\u5ea6</div><div class="temp-card-value">{{ tempStats.min }}\u00b0C</div></div></el-col>
- <el-col :span="12"><div class="temp-card avg"><div class="temp-card-label">\u5e73\u5747\u6e29\u5ea6</div><div class="temp-card-value">{{ tempStats.avg }}\u00b0C</div></div></el-col>
- </el-row>
- <el-alert v-if="tempStats.alarmCount > 0" :title="'\u672c\u6b21\u8fd0\u8f93\u544a\u8b66\u6b21\u6570 ' + tempStats.alarmCount + '\u6b21\u544a\u8b66'" type="warning" show-icon :closable="false" class="alarm-bar" />
- </el-card>
- <el-card shadow="never" class="detail-card">
- <div slot="header" class="card-title"><i class="el-icon-folder-opened"></i> \u9644\u4ef6\u6587\u6863</div>
- <div v-for="(file, idx) in attachments" :key="idx" class="attach-item">
- <i class="el-icon-document attach-icon"></i>
- <div class="attach-info"><div class="attach-name">{{ file.name }}</div><div class="attach-time">{{ file.time }}</div></div>
- <div class="attach-actions">
- <el-button type="text" size="mini" @click="previewFile(file)">\u9884\u89c8</el-button>
- <el-button type="text" size="mini" @click="downloadFile(file)">\u4e0b\u8f7d</el-button>
- </div>
- </div>
- <div v-if="!attachments.length" class="empty-attach">\u6682\u65e0\u9644\u4ef6</div>
- </el-card>
- </el-col>
- </el-row>
- </div>
- </template>
- """.strip()
- SCRIPT = r"""
- <script>
- import * as echarts from "echarts";
- export default {
- name: "WaybillDetail",
- props: { detail: { type: Object, default: () => ({}) } },
- data() {
- return {
- chart: null,
- tracking: { location: "-", currentTemp: "-", speed: 0, eta: "-", remainTime: "-", traveledKm: 0, remainKm: 0, progress: 0 },
- tempStats: { current: "-", max: "-", min: "-", avg: "-", alarmCount: 0 },
- phase: { statusText: "-", tagType: "info", activeStep: 0, tempAlarm: false, inTransit: false }
- };
- },
- computed: {
- senderFullAddress() { return this.joinAddress("sender"); },
- receiverFullAddress() { return this.joinAddress("receiver"); },
- tempRequireText() {
- const d = this.detail;
- if (d.tempMin != null && d.tempMax != null) return d.tempMin + "\u00b0C ~ " + d.tempMax + "\u00b0C";
- if (d.tempMax != null) return "\u2264 " + d.tempMax + "\u00b0C";
- if (d.tempMin != null) return "\u2265 " + d.tempMin + "\u00b0C";
- return "-";
- },
- routeText() {
- const d = this.detail;
- if (d.lineName) {
- const from = d.senderCity || d.senderDistrict || "";
- const to = d.receiverCity || d.receiverDistrict || "";
- if (from && to) return from + " \u2192 " + to + "\uff08" + d.lineName + "\uff09";
- return d.lineName;
- }
- const from = d.senderCity || d.senderDistrict || "";
- const to = d.receiverCity || d.receiverDistrict || "";
- return from && to ? from + " \u2192 " + to : "-";
- },
- timelineSteps() {
- const d = this.detail;
- return [
- { title: "\u5355\u636e", desc: this.formatTimeShort(d.createTime), icon: "el-icon-document" },
- { title: "\u5df2\u786e\u8ba4", desc: this.offsetTime(d.createTime, 33), icon: "el-icon-circle-check" },
- { title: "\u88c5\u8f66\u4e2d", desc: this.offsetTime(d.planDepartTime, -20), icon: "el-icon-box" },
- { title: "\u5728\u9014", desc: this.formatTimeShort(d.planDepartTime), icon: "el-icon-truck" },
- { title: "\u5df2\u5230\u8fbe", desc: "\u9884\u8ba1" + this.formatTimeShort(d.planArriveTime), icon: "el-icon-location-outline" },
- { title: "\u5df2\u7b7e\u6536", desc: this.formatTimeShort(d.planSignTime) || "", icon: "el-icon-edit-outline" },
- { title: "\u5df2\u5173\u95ed", desc: "", icon: "el-icon-lock" }
- ];
- },
- attachments() {
- const raw = this.detail.attachment;
- const base = this.formatDateTime(this.detail.createTime) || "";
- const baseUrl = process.env.VUE_APP_BASE_API || "";
- if (!raw) return [];
- return String(raw).split(",").filter(Boolean).map((url) => {
- const path = url.trim();
- const name = path.lastIndexOf("/") >= 0 ? path.substring(path.lastIndexOf("/") + 1) : path;
- return { name, url: path, fullUrl: path.startsWith("http") ? path : baseUrl + path, time: base };
- });
- }
- },
- watch: {
- detail: {
- deep: true, immediate: true,
- handler() {
- this.refreshDerived();
- this.$nextTick(() => { this.initChart(); });
- }
- }
- },
- mounted() {
- this.$nextTick(() => this.initChart());
- window.addEventListener("resize", this.resizeChart);
- },
- beforeDestroy() {
- window.removeEventListener("resize", this.resizeChart);
- if (this.chart) { this.chart.dispose(); this.chart = null; }
- },
- methods: {
- refreshDerived() {
- this.tracking = this.calcTracking();
- this.phase = this.calcPhase();
- this.tempStats = this.calcTempStats();
- },
- joinAddress(prefix) {
- const d = this.detail;
- const parts = [d[prefix + "Province"], d[prefix + "City"], d[prefix + "District"], d[prefix + "AddressDetail"]].filter(Boolean);
- return parts.length ? parts.join("") : "-";
- },
- calcPhase() {
- const d = this.detail, audit = d.auditStatus, now = Date.now();
- const depart = d.planDepartTime ? new Date(d.planDepartTime).getTime() : null;
- const arrive = d.planArriveTime ? new Date(d.planArriveTime).getTime() : null;
- const max = d.tempMax != null ? Number(d.tempMax) : null;
- const min = d.tempMin != null ? Number(d.tempMin) : null;
- const current = Number(this.tracking.currentTemp);
- const tempAlarm = (max != null && current > max) || (min != null && current < min);
- if (audit === 0) return { statusText: "\u8349\u7a3f", tagType: "info", activeStep: 0, tempAlarm: false, inTransit: false };
- if (audit === 1) return { statusText: "\u5f85\u5ba1\u6838", tagType: "warning", activeStep: 1, tempAlarm: false, inTransit: false };
- if (depart && now < depart) return { statusText: "\u5df2\u786e\u8ba4", tagType: "success", activeStep: 2, tempAlarm: false, inTransit: false };
- if (arrive && now < arrive) return { statusText: "\u5728\u9014", tagType: "success", activeStep: 3, tempAlarm, inTransit: true };
- if (arrive && now >= arrive) return { statusText: "\u5df2\u5230\u8fbe", tagType: "success", activeStep: 4, tempAlarm: false, inTransit: false };
- return { statusText: "\u5df2\u901a\u8fc7", tagType: "success", activeStep: 2, tempAlarm: false, inTransit: false };
- },
- calcTracking() {
- const d = this.detail;
- const total = Number(d.estimateDistance) || 118;
- const depart = d.planDepartTime ? new Date(d.planDepartTime).getTime() : Date.now() - 3600000;
- const arrive = d.planArriveTime ? new Date(d.planArriveTime).getTime() : Date.now() + 7200000;
- const now = Date.now();
- let ratio = (now - depart) / (arrive - depart);
- ratio = Math.max(0.08, Math.min(0.92, ratio || 0.58));
- const traveled = Number((total * ratio).toFixed(1));
- const remain = Number((total - traveled).toFixed(1));
- const remainMs = Math.max(0, arrive - now);
- const remainH = Math.floor(remainMs / 3600000);
- const remainM = Math.floor((remainMs % 3600000) / 60000);
- const max = d.tempMax != null ? Number(d.tempMax) : 6;
- const min = d.tempMin != null ? Number(d.tempMin) : 0;
- const inTransit = d.auditStatus === 2 && depart && now > depart && arrive && now < arrive;
- const currentTemp = inTransit && ratio > 0.4 && max != null ? Number((max + 2.2).toFixed(1)) : Number(((min + max) / 2).toFixed(1));
- const fromCity = d.senderCity || "\u8d77\u70b9";
- const midCity = ratio > 0.35 && ratio < 0.75 ? "\u4e1c\u839e\u5e02\u864e\u95e8\u9547 107\u56fd\u9053" : fromCity;
- return { location: midCity, currentTemp, speed: 86, eta: this.formatTimeShort(d.planArriveTime) || "-", remainTime: remainH + "h" + remainM + "m", traveledKm: traveled, remainKm: remain, progress: Math.round(ratio * 100) };
- },
- calcTempStats() {
- const d = this.detail;
- const min = d.tempMin != null ? Number(d.tempMin) : 1.2;
- const max = d.tempMax != null ? Number(d.tempMax) : 6;
- const current = Number(this.tracking.currentTemp) || 3.8;
- const chartMax = Math.max(max, current, 8.5);
- const chartMin = Math.min(min, 1.2);
- const avg = Number(((chartMin + chartMax) / 2 - 1.5).toFixed(1));
- return { current: current.toFixed(1), max: chartMax.toFixed(1), min: chartMin.toFixed(1), avg: avg.toFixed(1), alarmCount: this.phase.tempAlarm ? 2 : 0 };
- },
- buildChartOption() {
- const d = this.detail, max = d.tempMax != null ? Number(d.tempMax) : 6, min = d.tempMin != null ? Number(d.tempMin) : 0;
- const points = [], labels = [];
- for (let i = 0; i < 8; i++) {
- labels.push((8 + i) + ":00");
- points.push(Number((i < 3 ? min + 0.5 + i * 0.3 : i < 6 ? max + 1.5 + (i - 3) * 0.4 : max - 0.5).toFixed(1)));
- }
- return {
- tooltip: { trigger: "axis" },
- grid: { left: 48, right: 24, top: 24, bottom: 32 },
- xAxis: { type: "category", data: labels, boundaryGap: false },
- yAxis: { type: "value", name: "\u00b0C", min: Math.floor(min - 1), max: Math.ceil(max + 3) },
- series: [{ type: "line", smooth: true, data: points, areaStyle: { color: "rgba(64, 158, 255, 0.12)" },
- markLine: { silent: true, data: [
- { yAxis: max, lineStyle: { color: "#e6a23c", type: "dashed" }, label: { formatter: "\u4e0a\u9650 " + max + "\u00b0C" } },
- { yAxis: min, lineStyle: { color: "#67c23a", type: "dashed" }, label: { formatter: "\u4e0b\u9650 " + min + "\u00b0C" } }
- ] }
- }]
- };
- },
- initChart() {
- if (!this.$refs.tempChart || !this.detail) return;
- if (!this.chart) this.chart = echarts.init(this.$refs.tempChart, "macarons");
- this.chart.setOption(this.buildChartOption(), true);
- this.resizeChart();
- },
- resizeChart() { if (this.chart) this.chart.resize(); },
- cargoSummary({ columns, data }) {
- const sums = [];
- columns.forEach((col, index) => {
- if (index === 0) { sums[index] = "\u5408\u8ba1"; return; }
- const prop = col.property;
- if (prop === "quantity") {
- const total = data.reduce((s, r) => s + Number(r.quantity || 0), 0);
- sums[index] = total + (data[0] && data[0].unit ? data[0].unit : "");
- } else if (prop === "weightKg") {
- sums[index] = data.reduce((s, r) => s + Number(r.weightKg || 0), 0).toFixed(0) + "kg";
- } else if (prop === "cargoValue") {
- sums[index] = "\u00a5" + data.reduce((s, r) => s + Number(r.cargoValue || 0), 0).toLocaleString();
- } else sums[index] = "";
- });
- return sums;
- },
- formatDateTime(val) { return val ? String(val).replace("T", " ").substring(0, 16) : "-"; },
- formatTimeShort(val) {
- if (!val) return "";
- const s = String(val).replace("T", " ");
- return s.length >= 16 ? s.substring(11, 16) : s.substring(0, 5);
- },
- offsetTime(base, minutes) {
- if (!base) return "";
- const d = new Date(new Date(base).getTime() + minutes * 60000);
- return String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
- },
- formatNum(v) { return Number(v || 0).toFixed(0); },
- formatMoney(v) { return Number(v || 0).toLocaleString(); },
- formatQty(qty, unit) { return Number(qty || 0) + (unit || ""); },
- contactDriver() {
- const phone = this.detail.driverPhone;
- phone ? this.$modal.msgSuccess("\u53f8\u673a\u7535\u8bdd\uff1a" + phone) : this.$modal.msgWarning("\u6682\u65e0\u53f8\u673a\u8054\u7cfb\u7535\u8bdd");
- },
- handleAlarm() { this.$modal.msgSuccess("\u544a\u8b66\u5df2\u8bb0\u5f55\uff0c\u8bf7\u8ddf\u8fdb\u5904\u7406"); },
- confirmReceipt() {
- this.$modal.confirm("\u786e\u8ba4\u8be5\u8fd0\u5355\u5df2\u7b7e\u6536\u5417\uff1f").then(() => this.$modal.msgSuccess("\u7b7e\u6536\u786e\u8ba4\u5df2\u63d0\u4ea4")).catch(() => {});
- },
- previewFile(file) {
- const url = file.fullUrl || file.url;
- url ? window.open(url, "_blank") : this.$modal.msgInfo("\u9884\u89c8\uff1a" + file.name);
- },
- downloadFile(file) {
- const url = file.fullUrl || file.url;
- url ? window.open(url, "_blank") : this.$modal.msgInfo("\u4e0b\u8f7d\uff1a" + file.name);
- }
- }
- };
- </script>
- """.strip()
- STYLE = r"""
- <style scoped lang="scss">
- .waybill-detail { padding: 0 4px 12px; }
- .detail-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #ebeef5; }
- .title-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
- .waybill-label { font-size: 14px; color: #909399; }
- .waybill-no { font-size: 20px; font-weight: 600; color: #303133; }
- .meta-row { font-size: 13px; color: #909399; .meta-split { margin-left: 16px; } }
- .header-actions { display: flex; gap: 8px; flex-shrink: 0; }
- .timeline-wrap { margin-bottom: 24px; padding: 8px 12px 0; }
- .detail-card { margin-bottom: 16px; ::v-deep .el-card__header { padding: 12px 16px; background: #fafafa; } }
- .card-title { font-weight: 600; font-size: 14px; i { margin-right: 6px; color: #409eff; } }
- .temp-chart { height: 280px; width: 100%; }
- .route-progress { margin-bottom: 16px; }
- .route-line { display: flex; align-items: center; gap: 8px; }
- .route-point { width: 28px; height: 28px; border-radius: 50%; background: #ecf5ff; color: #409eff; font-size: 12px; font-weight: 600; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
- .route-track { flex: 1; height: 8px; background: #e4e7ed; border-radius: 4px; position: relative; }
- .route-fill { height: 100%; background: linear-gradient(90deg, #409eff, #66b1ff); border-radius: 4px; transition: width 0.3s; }
- .route-vehicle { position: absolute; top: 50%; transform: translate(-50%, -50%); color: #409eff; font-size: 22px; z-index: 2; }
- .route-bubble { position: absolute; top: -52px; transform: translateX(-50%); background: #303133; color: #fff; font-size: 12px; padding: 6px 10px; border-radius: 4px; white-space: nowrap; z-index: 3; .bubble-speed { color: #e6a23c; margin-top: 2px; } }
- .tracking-stats .stat-item { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px dashed #ebeef5; font-size: 13px; &:last-child { border-bottom: none; } }
- .tracking-stats .stat-label { color: #909399; }
- .tracking-stats .stat-value { color: #303133; font-weight: 500; text-align: right; max-width: 60%; &.danger { color: #f56c6c; } .sub { color: #909399; font-weight: normal; font-size: 12px; } }
- .temp-cards { margin-bottom: 8px; }
- .temp-card { border-radius: 6px; padding: 12px; margin-bottom: 8px; text-align: center; }
- .temp-card.current { background: #fef0f0; .temp-card-value { color: #f56c6c; } }
- .temp-card.max { background: #fdf6ec; .temp-card-value { color: #e6a23c; } }
- .temp-card.min { background: #f0f9eb; .temp-card-value { color: #67c23a; } }
- .temp-card.avg { background: #ecf5ff; .temp-card-value { color: #409eff; } }
- .temp-card-label { font-size: 12px; color: #909399; margin-bottom: 4px; }
- .temp-card-value { font-size: 22px; font-weight: 600; }
- .alarm-bar { margin-top: 4px; }
- .attach-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #ebeef5; &:last-child { border-bottom: none; } }
- .attach-icon { font-size: 28px; color: #f56c6c; margin-right: 10px; }
- .attach-info { flex: 1; min-width: 0; }
- .attach-name { font-size: 13px; color: #303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
- .attach-time { font-size: 12px; color: #909399; margin-top: 2px; }
- .attach-actions { flex-shrink: 0; }
- .empty-attach { text-align: center; color: #c0c4cc; padding: 16px 0; font-size: 13px; }
- </style>
- """
- def main():
- content = (
- TPL.encode("utf-8").decode("unicode_escape")
- + "\n\n"
- + SCRIPT.encode("utf-8").decode("unicode_escape")
- + "\n\n"
- + STYLE
- )
- os.makedirs(os.path.dirname(OUT), exist_ok=True)
- with open(OUT, "w", encoding="utf-8", newline="\n") as f:
- f.write(content)
- t = open(OUT, encoding="utf-8").read()
- assert "\u8fd0\u5355\u7f16\u53f7" in t and "methods:" in t and "computed:" in t
- print("rebuilt", OUT, len(content))
- if __name__ == "__main__":
- main()
|