chore: 删除 track-uniapp 根目录与 src/ 重复的陈旧副本

构建实际读取的是 track-uniapp/src/ —— @dcloudio/vite-plugin-uni 的
UNI_INPUT_DIR 默认取 path.resolve(cwd, 'src'),检测到 src/manifest.json
即以其为输入目录。根目录那套 pages.json / App.vue / main.js / manifest.json
停留在 2026-08-04,是早期 HBuilderX 布局的遗留,早已不参与构建。

危害在于「同名同结构」:IDE 搜索 pages/profile/index.vue 会命中两个结果,
改到根目录那份既不编译也不报错,表现为「代码明明改了,App 里没变化」,
排查成本极高。

- 删除 pages/(4 个 .vue)、pages.json、App.vue、main.js、manifest.json
- 根 manifest.json 版本号为 T1.0.0(字符串),src/ 已是 T1.0.8(108),
  且缺 Barcode/Camera 模块与图标配置
- 保留 index.html:它是生效的 H5 入口模板,内容引用 /src/main.js,
  uni-app CLI 布局中本就在项目根
- 保留 static/:src/ 下无同名目录,非重复项
This commit is contained in:
2026-09-15 15:51:19 +08:00
parent 7b8084a43d
commit ab75b9613f
9 changed files with 0 additions and 474 deletions

View File

@ -1,23 +0,0 @@
<script>
export default {
onLaunch() {
console.log("生产流转 T1.0.0 启动");
// 无 Token 跳转登录页
const token = uni.getStorageSync("token");
if (!token) {
uni.reLaunch({ url: "/pages/login/login" });
}
},
onShow() {},
onHide() {},
};
</script>
<style>
page {
background-color: #f3f4f6;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 14px;
color: #1f2937;
}
</style>

View File

@ -1,7 +0,0 @@
import App from "./App.vue";
import { createSSRApp } from "vue";
export function createApp() {
const app = createSSRApp(App);
return { app };
}

View File

@ -1,43 +0,0 @@
{
"name": "Track",
"appid": "__UNI__B572616",
"description": "Track - 生产流转管理",
"versionName": "T1.0.0",
"versionCode": "100",
"transformPx": false,
"vueVersion": "3",
"app-plus": {
"usingComponents": true,
"nvueCompiler": "uni-app",
"nvueStyleCompiler": "uni-app",
"compilerVersion": 3,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {},
"distribute": {
"android": {
"permissions": [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>"
]
},
"orientation": ["portrait-primary"]
}
},
"h5": {
"router": {
"mode": "hash",
"base": ""
},
"title": "生产流转"
}
}

View File

@ -1,73 +0,0 @@
{
"pages": [
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/scan/index",
"style": {
"navigationBarTitleText": "扫码干活",
"navigationBarBackgroundColor": "#2563EB",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/tasks/index",
"style": {
"navigationBarTitleText": "我的任务",
"navigationBarBackgroundColor": "#2563EB",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/notify/index",
"style": {
"navigationBarTitleText": "消息通知",
"navigationBarBackgroundColor": "#2563EB",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/profile/index",
"style": {
"navigationBarTitleText": "个人中心",
"navigationBarBackgroundColor": "#2563EB",
"navigationBarTextStyle": "white"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "生产流转",
"navigationBarBackgroundColor": "#2563EB",
"backgroundColor": "#F3F4F6"
},
"tabBar": {
"color": "#9CA3AF",
"selectedColor": "#2563EB",
"backgroundColor": "#FFFFFF",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/scan/index",
"text": "扫码干活"
},
{
"pagePath": "pages/tasks/index",
"text": "我的任务"
},
{
"pagePath": "pages/notify/index",
"text": "消息"
},
{
"pagePath": "pages/profile/index",
"text": "我的"
}
]
}
}

View File

@ -1,68 +0,0 @@
<template>
<view class="page">
<view class="header">
<text class="logo">🏭</text>
<text class="title">Track</text>
<text class="version">T1.0.0</text>
</view>
<view class="form">
<input v-model="username" class="input" placeholder="用户名" />
<input v-model="password" class="input" type="password" placeholder="密码" />
<button class="login-btn" @tap="handleLogin" :disabled="loading">
{{ loading ? '登录中...' : '登 录' }}
</button>
<text v-if="error" class="error">{{ error }}</text>
</view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { post } from "../../utils/request";
const username = ref("");
const password = ref("");
const loading = ref(false);
const error = ref("");
async function handleLogin() {
if (!username.value || !password.value) {
error.value = "请输入用户名和密码";
return;
}
loading.value = true;
error.value = "";
try {
const res = await post("/auth/login", {
username: username.value,
password: password.value,
});
// 保存 Token + 用户信息
uni.setStorageSync("token", res.access_token);
uni.setStorageSync("user", JSON.stringify(res.user));
uni.showToast({ title: "登录成功", icon: "success" });
// 跳转到扫码页
setTimeout(() => {
uni.switchTab({ url: "/pages/scan/index" });
}, 500);
} catch {
// request.js 已弹 toast
} finally {
loading.value = false;
}
}
</script>
<style scoped>
.page { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 32px; background: #f3f4f6; }
.header { display: flex; flex-direction: column; align-items: center; margin-bottom: 40px; }
.logo { font-size: 64px; }
.title { font-size: 20px; font-weight: 700; color: #1f2937; margin-top: 12px; }
.version { font-size: 12px; color: #9ca3af; margin-top: 4px; }
.form { width: 100%; max-width: 320px; }
.input { width: 100%; height: 48px; padding: 0 16px; border: 1px solid #e5e7eb; border-radius: 10px; font-size: 15px; background: #fff; margin-bottom: 12px; box-sizing: border-box; }
.login-btn { width: 100%; height: 48px; background: #2563EB; color: #fff; border: none; border-radius: 10px; font-size: 16px; font-weight: 700; line-height: 48px; }
.login-btn[disabled] { opacity: 0.6; }
.error { display: block; text-align: center; color: #dc2626; font-size: 13px; margin-top: 12px; }
</style>

View File

@ -1,25 +0,0 @@
<template>
<view class="page">
<view class="header">
<text class="title">消息通知</text>
<text class="subtitle">任务流转和系统通知</text>
</view>
<view class="empty">
<text class="empty-icon">🔔</text>
<text class="empty-text">暂无新消息</text>
</view>
</view>
</template>
<script setup>
</script>
<style scoped>
.page { min-height: 100vh; padding: 16px; padding-bottom: 80px; }
.header { margin-bottom: 24px; }
.title { font-size: 20px; font-weight: 700; color: #1f2937; display: block; }
.subtitle { font-size: 13px; color: #9ca3af; margin-top: 4px; display: block; }
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 80px; }
.empty-icon { font-size: 64px; margin-bottom: 12px; }
.empty-text { font-size: 14px; color: #9ca3af; }
</style>

View File

@ -1,48 +0,0 @@
<template>
<view class="page">
<view class="user-card">
<view class="avatar">{{ initial }}</view>
<view class="user-info">
<text class="user-name">{{ user?.display_name || '未登录' }}</text>
<text class="user-role">{{ user?.role === 'admin' ? '管理员' : '操作员' }}</text>
</view>
</view>
<view class="menu-card">
<view v-for="item in ['工作统计', '设置', '帮助与反馈', '关于']" :key="item" class="menu-item">
<text class="menu-text">{{ item }}</text>
</view>
</view>
<button class="logout-btn" @tap="handleLogout">退出登录</button>
<view class="version">T1.0.0</view>
</view>
</template>
<script setup>
import { ref, computed } from "vue";
const user = ref(null);
try { const r = uni.getStorageSync("user"); if (r) user.value = JSON.parse(r); } catch {}
const initial = computed(() => (user.value?.display_name || "?")[0]);
function handleLogout() {
uni.removeStorageSync("token");
uni.removeStorageSync("user");
uni.reLaunch({ url: "/pages/login/login" });
}
</script>
<style scoped>
.page { min-height: 100vh; padding: 16px; padding-bottom: 80px; }
.user-card { display: flex; align-items: center; gap: 12px; background: #fff; border-radius: 12px; padding: 16px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
.avatar { width: 48px; height: 48px; border-radius: 50%; background: #dbeafe; color: #2563EB; font-size: 20px; font-weight: 700; display: flex; align-items: center; justify-content: center; }
.user-name { font-size: 16px; font-weight: 700; color: #1f2937; display: block; }
.user-role { font-size: 12px; color: #9ca3af; }
.menu-card { background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); overflow: hidden; }
.menu-item { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; border-bottom: 1px solid #f3f4f6; }
.menu-item:last-child { border-bottom: none; }
.menu-text { font-size: 14px; color: #374151; }
.logout-btn { width: 100%; height: 44px; background: #fff; color: #dc2626; border: 1px solid #fecaca; border-radius: 10px; font-size: 14px; margin-top: 24px; line-height: 44px; }
.version { text-align: center; font-size: 11px; color: #d1d5db; margin-top: 16px; }
</style>

View File

@ -1,162 +0,0 @@
<template>
<view class="page">
<!-- ======== 扫码按钮 ======== -->
<view class="scan-area">
<view class="scan-btn" @tap="handleScanCode">
<text class="scan-icon">📷</text>
<text class="scan-text">点击扫码</text>
<text class="scan-hint">调用原生摄像头扫描二维码/条码</text>
</view>
<!-- 手动输入 -->
<view class="manual-input">
<input
v-model="serialNumber"
class="input"
type="text"
maxlength="16"
placeholder="手动输入16位序列号"
@confirm="handleManualSearch"
/>
<button class="search-btn" @tap="handleManualSearch" :disabled="loading">
{{ loading ? '查询中' : '查询' }}
</button>
</view>
</view>
<!-- ======== 最近扫描 ======== -->
<view v-if="lastScanned" class="last-scan">
最近扫描: <text class="sn-text">{{ lastScanned }}</text>
</view>
<!-- ======== 加载中 / 错误 / 结果 ======== -->
<view v-if="loading" class="loading">查询中...</view>
<view v-if="error && !loading" class="error-box">{{ error }}</view>
<view v-if="product && !loading" class="result">
<view class="card">
<view class="card-header">
<text class="card-title">📦 产品信息</text>
<text :class="['status', statusClass(product.status)]">{{ statusLabel(product.status) }}</text>
</view>
<view class="info-grid">
<view class="info-item"><text class="label">序列号</text><text class="value sn">{{ product.serial_number }}</text></view>
<view class="info-item"><text class="label">订单编号</text><text class="value">{{ product.order_no }}</text></view>
<view v-if="product.material_id" class="info-item"><text class="label">物料ID</text><text class="value">{{ product.material_id }}</text></view>
</view>
</view>
<view class="card">
<view class="card-header">
<text class="card-title">📋 当前进度</text>
<text class="task-count">{{ product.top_level_tasks.length }} 个任务</text>
</view>
<view v-if="product.top_level_tasks.length === 0" class="empty-tasks">暂无关联任务</view>
<view v-for="task in product.top_level_tasks" :key="task.id" class="task-item">
<view class="task-info">
<text class="task-name">{{ task.task_name }}</text>
<text class="task-assignee">负责人: {{ task.assignee_id || '未分配' }}</text>
</view>
<text :class="['status-sm', statusClass(task.status)]">{{ statusLabel(task.status) }}</text>
</view>
</view>
</view>
<view v-if="!product && !loading && !error" class="empty">
<text class="empty-icon">📱</text>
<text class="empty-text">扫码或手动输入序列号查询产品进度</text>
</view>
<view class="version">T1.0.0</view>
</view>
</template>
<script setup>
import { ref } from "vue";
import { get } from "../../utils/request";
const serialNumber = ref("");
const lastScanned = ref("");
const loading = ref(false);
const error = ref("");
const product = ref(null);
const STATUS_MAP = { pending: "待处理", in_progress: "进行中", completed: "已完成", cancelled: "已取消" };
function statusLabel(s) { return STATUS_MAP[s] || s; }
function statusClass(s) {
switch (s) {
case "pending": return "status-yellow";
case "in_progress": return "status-blue";
case "completed": return "status-green";
default: return "status-gray";
}
}
async function doQuery(sn) {
if (!sn || sn.length < 8) { error.value = "序列号至少需要 8 位"; return; }
serialNumber.value = sn;
lastScanned.value = sn;
loading.value = true;
error.value = "";
product.value = null;
try { product.value = await get(`/products/scan/${sn}`); }
catch { /* request.js 已弹 toast */ }
finally { loading.value = false; }
}
function handleScanCode() {
uni.scanCode({
onlyFromCamera: true,
scanType: ["qrCode", "barCode"],
success(res) {
const sn = (res.result || "").replace(/[^a-zA-Z0-9]/g, "").slice(0, 16);
doQuery(sn);
},
fail(err) {
if (err.errMsg && err.errMsg.includes("cancel")) return;
uni.showToast({ title: "扫码失败,请重试", icon: "none" });
},
});
}
function handleManualSearch() { doQuery(serialNumber.value.trim()); }
</script>
<style scoped>
.page { min-height: 100vh; padding: 16px; padding-bottom: 80px; }
.scan-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 180px; background: linear-gradient(135deg, #2563EB, #3B82F6); border-radius: 16px; color: #fff; box-shadow: 0 4px 16px rgba(37,99,235,0.3); }
.scan-icon { font-size: 44px; margin-bottom: 6px; }
.scan-text { font-size: 18px; font-weight: 700; }
.scan-hint { font-size: 12px; opacity: 0.8; margin-top: 4px; }
.manual-input { display: flex; gap: 8px; margin-top: 12px; }
.input { flex: 1; height: 44px; padding: 0 12px; border: 1px solid #e5e7eb; border-radius: 10px; font-size: 14px; background: #fff; }
.search-btn { height: 44px; padding: 0 20px; background: #2563EB; color: #fff; border: none; border-radius: 10px; font-size: 14px; font-weight: 600; line-height: 44px; }
.search-btn[disabled] { opacity: 0.6; }
.last-scan { font-size: 12px; color: #9ca3af; margin: 12px 0; }
.sn-text { font-family: monospace; color: #4b5563; }
.loading { text-align: center; padding: 32px 0; color: #6b7280; }
.error-box { padding: 12px; border-radius: 10px; background: #fef2f2; color: #dc2626; font-size: 13px; border: 1px solid #fecaca; }
.card { background: #fff; border-radius: 12px; padding: 14px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.06); }
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.card-title { font-size: 15px; font-weight: 700; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.label { font-size: 12px; color: #9ca3af; }
.value { font-size: 14px; color: #1f2937; font-weight: 600; }
.sn { font-family: monospace; }
.status { font-size: 11px; padding: 2px 10px; border-radius: 20px; font-weight: 600; }
.status-sm { font-size: 11px; padding: 2px 8px; border-radius: 20px; font-weight: 600; flex-shrink: 0; }
.status-yellow { background: #fef3c7; color: #b45309; }
.status-blue { background: #dbeafe; color: #1d4ed8; }
.status-green { background: #dcfce7; color: #15803d; }
.status-gray { background: #f3f4f6; color: #6b7280; }
.task-count { font-size: 12px; color: #9ca3af; }
.empty-tasks { text-align: center; padding: 24px 0; color: #9ca3af; font-size: 13px; }
.task-item { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-top: 1px solid #f3f4f6; }
.task-info { flex: 1; min-width: 0; }
.task-name { font-size: 14px; font-weight: 600; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.task-assignee { font-size: 12px; color: #9ca3af; }
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 60px; color: #9ca3af; }
.empty-icon { font-size: 64px; margin-bottom: 12px; }
.empty-text { font-size: 14px; }
.version { text-align: center; font-size: 11px; color: #d1d5db; padding: 16px 0; }
</style>

View File

@ -1,25 +0,0 @@
<template>
<view class="page">
<view class="header">
<text class="title">我的任务</text>
<text class="subtitle">待处理和进行中的任务</text>
</view>
<view class="empty">
<text class="empty-icon">📋</text>
<text class="empty-text">暂无待办任务</text>
</view>
</view>
</template>
<script setup>
</script>
<style scoped>
.page { min-height: 100vh; padding: 16px; padding-bottom: 80px; }
.header { margin-bottom: 24px; }
.title { font-size: 20px; font-weight: 700; color: #1f2937; display: block; }
.subtitle { font-size: 13px; color: #9ca3af; margin-top: 4px; display: block; }
.empty { display: flex; flex-direction: column; align-items: center; padding-top: 80px; }
.empty-icon { font-size: 64px; margin-bottom: 12px; }
.empty-text { font-size: 14px; color: #9ca3af; }
</style>