From 077ab59c700b85efdd057265bf752ad5942395b2 Mon Sep 17 00:00:00 2001
From: ZN <zhang_12370@163.com>
Date: 星期二, 17 三月 2026 17:36:13 +0800
Subject: [PATCH] feat(quality): 新增质量管理模块的API接口和移动端页面

---
 src/pages/qualityManagement/metricMaintenance/index.vue |  498 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 498 insertions(+), 0 deletions(-)

diff --git a/src/pages/qualityManagement/metricMaintenance/index.vue b/src/pages/qualityManagement/metricMaintenance/index.vue
new file mode 100644
index 0000000..ab59b5d
--- /dev/null
+++ b/src/pages/qualityManagement/metricMaintenance/index.vue
@@ -0,0 +1,498 @@
+<template>
+  <view class="metric-maintenance-page">
+    <PageHeader title="鎸囨爣缁存姢" @back="goBack" />
+    
+    <!-- 鎼滅储涓庣瓫閫� -->
+    <view class="search-section">
+      <up-search
+        placeholder="鏍囧噯缂栧彿/鏍囧噯鍚嶇О"
+        v-model="searchForm.keyword"
+        @search="handleQuery"
+        @custom="handleQuery"
+        @clear="handleQuery"
+        :show-action="true"
+        action-text="鎼滅储"
+        :animation="true"
+        customStyle="margin-bottom: 20rpx"
+      ></up-search>
+      <view class="filter-row">
+        <view class="filter-item" @click="showTypeSelect = true">
+          <text>{{ typeLabel }}</text>
+          <up-icon name="arrow-down" size="14" color="#999"></up-icon>
+        </view>
+        <view class="filter-item" @click="showStatusSelect = true">
+          <text>{{ statusLabel }}</text>
+          <up-icon name="arrow-down" size="14" color="#999"></up-icon>
+        </view>
+      </view>
+    </view>
+
+    <!-- 鍒楄〃鍖哄煙 -->
+    <view class="list-container" v-if="tableData.length > 0">
+      <view v-for="(item, index) in tableData" :key="index" class="list-item" @click="viewDetail(item)">
+        <view class="item-header">
+          <text class="standard-no">{{ item.standardNo }}</text>
+          <up-tag :text="getStatusText(item.state)" :type="getStatusType(item.state)" size="mini"></up-tag>
+        </view>
+        <view class="item-content">
+          <view class="item-row">
+            <text class="item-label">鏍囧噯鍚嶇О锛�</text>
+            <text class="item-value">{{ item.standardName }}</text>
+          </view>
+          <view class="item-row">
+            <text class="item-label">绫诲埆锛�</text>
+            <text class="item-value">{{ getInspectTypeText(item.inspectType) }}</text>
+          </view>
+          <view class="item-row">
+            <text class="item-label">宸ュ簭锛�</text>
+            <text class="item-value">{{ item.processName || '-' }}</text>
+          </view>
+        </view>
+        <view class="item-actions">
+          <up-button type="primary" size="mini" @click.stop="openStandardDialog('edit', item)">缂栬緫</up-button>
+          <up-button v-if="item.state !== 1" type="success" size="mini" @click.stop="handleAudit(item, 1)">鎵瑰噯</up-button>
+          <up-button v-if="item.state === 1" type="warning" size="mini" @click.stop="handleAudit(item, 2)">鎾ら攢</up-button>
+          <up-button type="error" size="mini" @click.stop="handleDelete(item)">鍒犻櫎</up-button>
+        </view>
+      </view>
+      <view class="pagination-container">
+        <up-loadmore :status="loadStatus" @loadmore="getList" />
+      </view>
+    </view>
+    
+    <view v-else class="no-data">
+      <up-empty mode="data" text="鏆傛棤鏁版嵁"></up-empty>
+    </view>
+
+    <!-- 娴姩鏂板鎸夐挳 -->
+    <view class="fab-button" @click="openStandardDialog('add')">
+      <up-icon name="plus" size="24" color="#ffffff"></up-icon>
+    </view>
+
+    <!-- 绫诲埆閫夋嫨鍣� -->
+    <up-action-sheet
+      :actions="typeActions"
+      :show="showTypeSelect"
+      @close="showTypeSelect = false"
+      @select="selectType"
+      title="璇烽�夋嫨绫诲埆"
+    ></up-action-sheet>
+
+    <!-- 鐘舵�侀�夋嫨鍣� -->
+    <up-action-sheet
+      :actions="statusActions"
+      :show="showStatusSelect"
+      @close="showStatusSelect = false"
+      @select="selectStatus"
+      title="璇烽�夋嫨鐘舵��"
+    ></up-action-sheet>
+
+    <!-- 鏍囧噯缂栬緫寮圭獥 -->
+    <up-popup v-model:show="standardDialogVisible" mode="center" round closeable @close="closeStandardDialog">
+      <view class="dialog-content">
+        <view class="dialog-header">
+          <text class="dialog-title">{{ standardOperationType === 'add' ? '鏂板妫�娴嬫爣鍑�' : '淇敼妫�娴嬫爣鍑�' }}</text>
+        </view>
+        <up-form :model="standardForm" ref="standardFormRef" label-width="100" label-position="top">
+          <up-form-item label="鏍囧噯缂栧彿" prop="standardNo" required borderBottom>
+            <up-input v-model="standardForm.standardNo" placeholder="璇疯緭鍏ユ爣鍑嗙紪鍙�" border="surround" />
+          </up-form-item>
+          <up-form-item label="鏍囧噯鍚嶇О" prop="standardName" required borderBottom>
+            <up-input v-model="standardForm.standardName" placeholder="璇疯緭鍏ユ爣鍑嗗悕绉�" border="surround" />
+          </up-form-item>
+          <up-form-item label="绫诲埆" prop="inspectType" required borderBottom>
+            <up-radio-group v-model="standardForm.inspectType">
+              <up-radio label="鍘熸潗鏂�" name="0"></up-radio>
+              <up-radio label="杩囩▼" name="1" customStyle="margin-left: 20rpx"></up-radio>
+              <up-radio label="鍑哄巶" name="2" customStyle="margin-left: 20rpx"></up-radio>
+            </up-radio-group>
+          </up-form-item>
+          <up-form-item label="宸ュ簭" prop="processId" borderBottom>
+            <up-input
+              v-model="processName"
+              placeholder="璇烽�夋嫨宸ュ簭"
+              border="surround"
+              readonly
+              @click="showProcessSelect = true"
+            />
+          </up-form-item>
+          <up-form-item label="澶囨敞" prop="remark" borderBottom>
+            <up-textarea v-model="standardForm.remark" placeholder="璇疯緭鍏ュ娉�" count border="surround" />
+          </up-form-item>
+        </up-form>
+        <view class="dialog-footer">
+          <up-button type="primary" text="纭" @click="submitStandardForm" :loading="submitLoading"></up-button>
+          <up-button text="鍙栨秷" @click="closeStandardDialog" customStyle="margin-top: 20rpx"></up-button>
+        </view>
+      </view>
+    </up-popup>
+
+    <!-- 宸ュ簭閫夋嫨鍣� -->
+    <up-picker
+      :show="showProcessSelect"
+      :columns="[processOptions]"
+      keyName="label"
+      @confirm="confirmProcess"
+      @cancel="showProcessSelect = false"
+    ></up-picker>
+  </view>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted, computed } from 'vue';
+import {
+  qualityTestStandardListPage,
+  qualityTestStandardAdd,
+  qualityTestStandardUpdate,
+  qualityTestStandardDel,
+  qualityTestStandardAudit
+} from '@/api/qualityManagement/metricMaintenance.js';
+import { productProcessListPage } from '@/api/basicData/productProcess.js';
+import { toast, showConfirm } from '@/utils/common';
+
+const searchForm = reactive({
+  keyword: '',
+  inspectType: '',
+  state: ''
+});
+
+const tableData = ref([]);
+const page = reactive({
+  current: 1,
+  size: 20,
+  total: 0
+});
+const loadStatus = ref('loadmore');
+
+const showTypeSelect = ref(false);
+const typeActions = [
+  { name: '鍏ㄩ儴', value: '' },
+  { name: '鍘熸潗鏂欐楠�', value: '0' },
+  { name: '杩囩▼妫�楠�', value: '1' },
+  { name: '鍑哄巶妫�楠�', value: '2' }
+];
+const typeLabel = computed(() => {
+  const action = typeActions.find(a => a.value === searchForm.inspectType);
+  return action ? action.name : '鍏ㄩ儴绫诲埆';
+});
+
+const showStatusSelect = ref(false);
+const statusActions = [
+  { name: '鍏ㄩ儴', value: '' },
+  { name: '鑽夌', value: '0' },
+  { name: '閫氳繃', value: '1' },
+  { name: '鎾ら攢', value: '2' }
+];
+const statusLabel = computed(() => {
+  const action = statusActions.find(a => a.value === searchForm.state);
+  return action ? action.name : '鍏ㄩ儴鐘舵��';
+});
+
+const standardDialogVisible = ref(false);
+const standardOperationType = ref('add');
+const submitLoading = ref(false);
+const standardForm = reactive({
+  id: null,
+  standardNo: '',
+  standardName: '',
+  inspectType: '0',
+  processId: null,
+  remark: '',
+  state: '0'
+});
+
+const processOptions = ref([]);
+const showProcessSelect = ref(false);
+const processName = ref('');
+
+const getInspectTypeText = (type) => {
+  const types = { '0': '鍘熸潗鏂欐楠�', '1': '杩囩▼妫�楠�', '2': '鍑哄巶妫�楠�' };
+  return types[type] || '-';
+};
+
+const getStatusText = (state) => {
+  const states = { '0': '鑽夌', '1': '閫氳繃', '2': '鎾ら攢' };
+  return states[state] || '鏈煡';
+};
+
+const getStatusType = (state) => {
+  const types = { '0': 'info', '1': 'success', '2': 'warning' };
+  return types[state] || 'info';
+};
+
+const getList = () => {
+  if (loadStatus.value === 'loading' || (page.total > 0 && tableData.value.length >= page.total)) return;
+  
+  loadStatus.value = 'loading';
+  const params = {
+    standardNo: searchForm.keyword || null,
+    inspectType: searchForm.inspectType || null,
+    state: searchForm.state || null,
+    current: page.current,
+    size: page.size
+  };
+  
+  qualityTestStandardListPage(params).then(res => {
+    const records = res?.data?.records || [];
+    if (page.current === 1) {
+      tableData.value = records;
+    } else {
+      tableData.value = [...tableData.value, ...records];
+    }
+    page.total = res?.data?.total || 0;
+    
+    if (tableData.value.length >= page.total) {
+      loadStatus.value = 'nomore';
+    } else {
+      loadStatus.value = 'loadmore';
+      page.current++;
+    }
+  }).catch(() => {
+    loadStatus.value = 'loadmore';
+  });
+};
+
+const handleQuery = () => {
+  page.current = 1;
+  page.total = 0;
+  tableData.value = [];
+  loadStatus.value = 'loadmore';
+  getList();
+};
+
+const selectType = (e) => {
+  searchForm.inspectType = e.value;
+  handleQuery();
+};
+
+const selectStatus = (e) => {
+  searchForm.state = e.value;
+  handleQuery();
+};
+
+const viewDetail = (item) => {
+  uni.navigateTo({
+    url: `/pages/qualityManagement/metricMaintenance/detail?id=${item.id}&standardNo=${item.standardNo}&state=${item.state}`
+  });
+};
+
+const openStandardDialog = (type, row) => {
+  standardOperationType.value = type;
+  if (type === 'edit' && row) {
+    Object.assign(standardForm, {
+      id: row.id,
+      standardNo: row.standardNo,
+      standardName: row.standardName,
+      inspectType: String(row.inspectType),
+      processId: row.processId,
+      remark: row.remark,
+      state: String(row.state)
+    });
+    const process = processOptions.value.find(p => p.value === row.processId);
+    processName.value = process ? process.label : '';
+  } else {
+    Object.assign(standardForm, {
+      id: null,
+      standardNo: '',
+      standardName: '',
+      inspectType: '0',
+      processId: null,
+      remark: '',
+      state: '0'
+    });
+    processName.value = '';
+  }
+  standardDialogVisible.value = true;
+};
+
+const closeStandardDialog = () => {
+  standardDialogVisible.value = false;
+};
+
+const submitStandardForm = () => {
+  submitLoading.value = true;
+  const api = standardOperationType.value === 'add' ? qualityTestStandardAdd : qualityTestStandardUpdate;
+  api(standardForm).then(() => {
+    toast('淇濆瓨鎴愬姛');
+    standardDialogVisible.value = false;
+    handleQuery();
+  }).finally(() => {
+    submitLoading.value = false;
+  });
+};
+
+const handleAudit = (row, state) => {
+  const text = state === 1 ? '鎵瑰噯' : '鎾ら攢';
+  showConfirm(`纭${text}璇ユ娴嬫爣鍑嗗悧锛焋).then(res => {
+    if (res.confirm) {
+      qualityTestStandardAudit([{ id: row.id, state }]).then(() => {
+        toast(`${text}鎴愬姛`);
+        handleQuery();
+      });
+    }
+  });
+};
+
+const handleDelete = (row) => {
+  showConfirm('纭鍒犻櫎璇ユ娴嬫爣鍑嗗悧锛�').then(res => {
+    if (res.confirm) {
+      qualityTestStandardDel([row.id]).then(() => {
+        toast('鍒犻櫎鎴愬姛');
+        handleQuery();
+      });
+    }
+  });
+};
+
+const confirmProcess = (e) => {
+  const val = e.value[0];
+  standardForm.processId = val.value;
+  processName.value = val.label;
+  showProcessSelect.value = false;
+};
+
+const goBack = () => {
+  uni.navigateBack();
+};
+
+onMounted(() => {
+  handleQuery();
+  productProcessListPage({ current: 1, size: 1000 }).then(res => {
+    processOptions.value = (res?.data?.records || []).map(p => ({
+      label: p.processName,
+      value: p.id
+    }));
+  });
+});
+</script>
+
+<style lang="scss" scoped>
+.metric-maintenance-page {
+  padding-bottom: 120rpx;
+  background-color: #f5f7fa;
+  min-height: 100vh;
+}
+
+.search-section {
+  padding: 20rpx 30rpx;
+  background-color: #ffffff;
+  position: sticky;
+  top: 0;
+  z-index: 10;
+}
+
+.filter-row {
+  display: flex;
+  justify-content: space-around;
+  padding: 10rpx 0;
+}
+
+.filter-item {
+  display: flex;
+  align-items: center;
+  gap: 10rpx;
+  font-size: 28rpx;
+  color: #606266;
+}
+
+.list-container {
+  padding: 20rpx;
+}
+
+.list-item {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
+}
+
+.item-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.standard-no {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #303133;
+}
+
+.item-content {
+  margin-bottom: 20rpx;
+}
+
+.item-row {
+  display: flex;
+  margin-bottom: 10rpx;
+}
+
+.item-label {
+  color: #909399;
+  width: 160rpx;
+  font-size: 28rpx;
+}
+
+.item-value {
+  flex: 1;
+  color: #303133;
+  font-size: 28rpx;
+}
+
+.item-actions {
+  display: flex;
+  justify-content: flex-end;
+  gap: 20rpx;
+  border-top: 1rpx solid #ebeef5;
+  padding-top: 20rpx;
+}
+
+.pagination-container {
+  padding: 20rpx 0;
+}
+
+.no-data {
+  padding-top: 200rpx;
+}
+
+.fab-button {
+  position: fixed;
+  right: 40rpx;
+  bottom: 60rpx;
+  width: 100rpx;
+  height: 100rpx;
+  background-color: #3c9cff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 16rpx rgba(60, 156, 255, 0.4);
+  z-index: 99;
+}
+
+.dialog-content {
+  width: 650rpx;
+  padding: 40rpx;
+  background-color: #ffffff;
+  border-radius: 24rpx;
+  overflow: hidden;
+  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
+}
+
+.dialog-header {
+  margin-bottom: 30rpx;
+  text-align: center;
+}
+
+.dialog-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #303133;
+}
+
+.dialog-footer {
+  margin-top: 40rpx;
+}
+</style>

--
Gitblit v1.9.3