| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/qualityManagement/nonconformingManagement/form", |
| | | "style": { |
| | | "navigationBarTitleText": "ä¸åæ ¼å管ç", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/qualityManagement/rawMaterial/index", |
| | | "style": { |
| | | "navigationBarTitleText": "åææ", |
| | |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/qualityManagement/rawMaterial/form", |
| | | "style": { |
| | | "navigationBarTitleText": "åææ£", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/qualityManagement/rawMaterial/files", |
| | | "style": { |
| | | "navigationBarTitleText": "é件管ç", |
| | | "navigationStyle": "custom" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/qualityManagement/visualization/qualityDashboard", |
| | | "style": { |
| | | "navigationBarTitleText": "è´¨éçæ¿", |
| | |
| | | } from "@/api/consumablesLogistics/consumablesOutRecord.js"; |
| | | import { |
| | | findAllQualifiedStockOutRecordTypeOptions, |
| | | findAllUnQualifiedStockOutRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | |
| | | const props = defineProps({ |
| | |
| | | }; |
| | | |
| | | const fetchStockRecordTypeOptions = () => { |
| | | const api = |
| | | props.type === "1" |
| | | ? findAllUnQualifiedStockOutRecordTypeOptions |
| | | : findAllQualifiedStockOutRecordTypeOptions; |
| | | api() |
| | | findAllQualifiedStockOutRecordTypeOptions() |
| | | .then((res) => { |
| | | stockRecordTypeOptions.value = res.data || []; |
| | | }) |
| | |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getConsumablesOutRecordPage({ ...searchForm.value, ...page, type: props.type }) |
| | | getConsumablesOutRecordPage({ ...searchForm.value, ...page, type: "0" }) |
| | | .then(res => { |
| | | tableData.value = res?.data?.records || []; |
| | | total.value = res?.data?.total || 0; |
| | |
| | | <template> |
| | | <view class="dispatch-page"> |
| | | <PageHeader title="åºåºå°è´¦" @back="goBack" /> |
| | | <view class="tabs-wrap"> |
| | | <view |
| | | v-for="tab in tabs" |
| | | :key="tab.name" |
| | | class="tab-item" |
| | | :class="{ active: activeTab === tab.name }" |
| | | @click="activeTab = tab.name" |
| | | > |
| | | <text>{{ tab.label }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="search-section"> |
| | | <view class="search-row"> |
| | | <view class="search-input-wrap"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref, toRefs, watch } from "vue"; |
| | | import { reactive, ref, toRefs } from "vue"; |
| | | import { onReachBottom, onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { getConsumablesOutRecordPage, delConsumablesOutRecord } from "@/api/consumablesLogistics/consumablesOutRecord.js"; |
| | | import { findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | import { findAllQualifiedStockOutRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | |
| | | const activeTab = ref("qualified"); |
| | | const stockRecordTypeOptions = ref([]); |
| | | const tabs = [ |
| | | { label: "åæ ¼åºåº", name: "qualified", type: "0" }, |
| | | { label: "ä¸åæ ¼åºåº", name: "unqualified", type: "1" }, |
| | | ]; |
| | | const tableData = ref([]); |
| | | const total = ref(0); |
| | | const loadStatus = ref("loadmore"); |
| | | const page = reactive({ current: 1, size: 20 }); |
| | | const data = reactive({ searchForm: { productName: "" } }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const currentType = () => tabs.find((t) => t.name === activeTab.value)?.type || "0"; |
| | | const currentType = () => "0"; |
| | | |
| | | function getRecordType(recordType) { |
| | | if (recordType == null || recordType === "") return ""; |
| | |
| | | } |
| | | |
| | | function fetchRecordTypeOptions() { |
| | | const api = |
| | | currentType() === "1" |
| | | ? findAllUnQualifiedStockOutRecordTypeOptions |
| | | : findAllQualifiedStockOutRecordTypeOptions; |
| | | api() |
| | | findAllQualifiedStockOutRecordTypeOptions() |
| | | .then((res) => { |
| | | const list = res.data != null ? res.data : res; |
| | | stockRecordTypeOptions.value = Array.isArray(list) ? list : []; |
| | |
| | | getList(); |
| | | }; |
| | | |
| | | watch(activeTab, () => { |
| | | page.current = 1; |
| | | loadStatus.value = "loadmore"; |
| | | stockRecordTypeOptions.value = []; |
| | | getList(); |
| | | }); |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | loadStatus.value = "loadmore"; |
| | |
| | | "dispatchDetailItem", |
| | | JSON.stringify({ |
| | | item, |
| | | type: currentType(), |
| | | type: "0", |
| | | }) |
| | | ); |
| | | } catch (e) {} |
| | |
| | | |
| | | <style lang="scss" scoped> |
| | | .dispatch-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 40rpx; } |
| | | .tabs-wrap { display: flex; background: #fff; padding: 24rpx; gap: 24rpx; } |
| | | .tab-item { flex: 1; text-align: center; padding: 20rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 28rpx; color: #666; } |
| | | .tab-item.active { background: #2979ff; color: #fff; } |
| | | .search-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; } |
| | | .search-row { display: flex; align-items: center; } |
| | | .search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; } |
| | |
| | | .card-body .l { color: #666; } |
| | | .card-body .r { color: #333; } |
| | | .card-body .r.highlight { color: #2979ff; font-weight: 500; } |
| | | .card-actions { display: flex; justify-content: flex-end; margin-top: 12rpx; } |
| | | .btn-delete { color: #f56c6c; font-size: 28rpx; } |
| | | .card-actions { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | margin-top: 16rpx; |
| | | padding-top: 16rpx; |
| | | border-top: 1rpx solid #eee; |
| | | } |
| | | .btn-delete { |
| | | color: #f56c6c; |
| | | font-size: 28rpx; |
| | | padding: 12rpx 36rpx; |
| | | border-radius: 999rpx; |
| | | border: 1rpx solid rgba(245, 108, 108, 0.55); |
| | | background: rgba(245, 108, 108, 0.08); |
| | | } |
| | | .no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; } |
| | | .load-more-wrap { padding: 24rpx 24rpx 8rpx; } |
| | | </style> |
| | |
| | | import { ref } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { findAllQualifiedStockOutRecordTypeOptions, findAllUnQualifiedStockOutRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | import { findAllQualifiedStockOutRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | |
| | | const detail = ref(null); |
| | | const loading = ref(true); |
| | |
| | | } |
| | | |
| | | function fetchRecordTypeOptions(type) { |
| | | const api = |
| | | type === "1" ? findAllUnQualifiedStockOutRecordTypeOptions : findAllQualifiedStockOutRecordTypeOptions; |
| | | api() |
| | | findAllQualifiedStockOutRecordTypeOptions() |
| | | .then((res) => { |
| | | const data = res.data != null ? res.data : res; |
| | | stockRecordTypeOptions.value = Array.isArray(data) ? data : []; |
| | |
| | | try { |
| | | const payload = typeof cached === "string" ? JSON.parse(cached) : cached; |
| | | const item = payload && payload.item != null ? payload.item : payload; |
| | | const type = payload && payload.type != null ? payload.type : "0"; |
| | | detail.value = normalizeDetail({ ...item, index: 1 }); |
| | | fetchRecordTypeOptions(type); |
| | | fetchRecordTypeOptions("0"); |
| | | uni.removeStorageSync("dispatchDetailItem"); |
| | | } catch (e) { |
| | | uni.removeStorageSync("dispatchDetailItem"); |
| | |
| | | } from "@/api/consumablesLogistics/consumablesInRecord.js"; |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | findAllUnQualifiedStockInRecordTypeOptions, |
| | | } from "@/api/basicData/enum.js"; |
| | | |
| | | const props = defineProps({ |
| | |
| | | }; |
| | | |
| | | const fetchStockRecordTypeOptions = () => { |
| | | const api = |
| | | props.type === "1" |
| | | ? findAllUnQualifiedStockInRecordTypeOptions |
| | | : findAllQualifiedStockInRecordTypeOptions; |
| | | api() |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then((res) => { |
| | | stockRecordTypeOptions.value = res.data || []; |
| | | }) |
| | |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | getConsumablesInRecordListPage({ ...searchForm.value, ...page, type: props.type }) |
| | | getConsumablesInRecordListPage({ ...searchForm.value, ...page, type: "0" }) |
| | | .then(res => { |
| | | tableData.value = res?.data?.records || []; |
| | | total.value = res?.data?.total || 0; |
| | |
| | | <template> |
| | | <view class="receipt-page"> |
| | | <PageHeader title="å
¥åºç®¡ç" @back="goBack" /> |
| | | <view class="tabs-wrap"> |
| | | <view |
| | | v-for="tab in tabs" |
| | | :key="tab.name" |
| | | class="tab-item" |
| | | :class="{ active: activeTab === tab.name }" |
| | | @click="activeTab = tab.name" |
| | | > |
| | | <text>{{ tab.label }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="search-section"> |
| | | <view class="search-row"> |
| | | <view class="search-input-wrap"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { reactive, ref, toRefs, watch } from "vue"; |
| | | import { reactive, ref, toRefs } from "vue"; |
| | | import { onReachBottom, onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import request from "@/utils/request"; |
| | | import { findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | import { findAllQualifiedStockInRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | |
| | | const activeTab = ref("qualified"); |
| | | const stockRecordTypeOptions = ref([]); |
| | | const tabs = [ |
| | | { label: "åæ ¼å
¥åº", name: "qualified", type: "0" }, |
| | | { label: "ä¸åæ ¼å
¥åº", name: "unqualified", type: "1" }, |
| | | ]; |
| | | const tableData = ref([]); |
| | | const total = ref(0); |
| | | const loadStatus = ref("loadmore"); |
| | | const page = reactive({ current: 1, size: 4 }); |
| | | const data = reactive({ searchForm: { productName: "" } }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const currentType = () => tabs.find((t) => t.name === activeTab.value)?.type || "0"; |
| | | const currentType = () => "0"; |
| | | |
| | | function getRecordType(recordType) { |
| | | if (recordType == null || recordType === "") return ""; |
| | |
| | | } |
| | | |
| | | function fetchRecordTypeOptions() { |
| | | const api = |
| | | currentType() === "1" |
| | | ? findAllUnQualifiedStockInRecordTypeOptions |
| | | : findAllQualifiedStockInRecordTypeOptions; |
| | | api() |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then((res) => { |
| | | const data = res.data != null ? res.data : res; |
| | | stockRecordTypeOptions.value = Array.isArray(data) ? data : []; |
| | |
| | | getList(); |
| | | }; |
| | | |
| | | watch(activeTab, () => { |
| | | page.current = 1; |
| | | loadStatus.value = "loadmore"; |
| | | stockRecordTypeOptions.value = []; |
| | | getList(); |
| | | }); |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | loadStatus.value = "loadmore"; |
| | |
| | | "receiptDetailItem", |
| | | JSON.stringify({ |
| | | item, |
| | | type: currentType(), |
| | | type: "0", |
| | | }) |
| | | ); |
| | | } catch (e) {} |
| | |
| | | |
| | | <style lang="scss" scoped> |
| | | .receipt-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 40rpx; } |
| | | .tabs-wrap { display: flex; background: #fff; padding: 24rpx; gap: 24rpx; } |
| | | .tab-item { flex: 1; text-align: center; padding: 20rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 28rpx; color: #666; } |
| | | .tab-item.active { background: #2979ff; color: #fff; } |
| | | .search-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; } |
| | | .search-row { display: flex; align-items: center; } |
| | | .search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; } |
| | |
| | | .card-body .l { color: #666; } |
| | | .card-body .r { color: #333; } |
| | | .card-body .r.highlight { color: #2979ff; font-weight: 500; } |
| | | .card-actions { display: flex; justify-content: flex-end; margin-top: 12rpx; } |
| | | .btn-delete { color: #f56c6c; font-size: 28rpx; } |
| | | .card-actions { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | margin-top: 16rpx; |
| | | padding-top: 16rpx; |
| | | border-top: 1rpx solid #eee; |
| | | } |
| | | .btn-delete { |
| | | color: #f56c6c; |
| | | font-size: 28rpx; |
| | | padding: 12rpx 36rpx; |
| | | border-radius: 999rpx; |
| | | border: 1rpx solid rgba(245, 108, 108, 0.55); |
| | | background: rgba(245, 108, 108, 0.08); |
| | | } |
| | | .no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; } |
| | | .load-more-wrap { padding: 24rpx 24rpx 8rpx; } |
| | | </style> |
| | |
| | | import { ref } from "vue"; |
| | | import { onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { findAllQualifiedStockInRecordTypeOptions, findAllUnQualifiedStockInRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | import { findAllQualifiedStockInRecordTypeOptions } from "@/api/basicData/enum.js"; |
| | | |
| | | const detail = ref(null); |
| | | const loading = ref(true); |
| | |
| | | } |
| | | |
| | | function fetchRecordTypeOptions(type) { |
| | | const api = |
| | | type === "1" ? findAllUnQualifiedStockInRecordTypeOptions : findAllQualifiedStockInRecordTypeOptions; |
| | | api() |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then((res) => { |
| | | const data = res.data != null ? res.data : res; |
| | | stockRecordTypeOptions.value = Array.isArray(data) ? data : []; |
| | |
| | | try { |
| | | const payload = typeof cached === "string" ? JSON.parse(cached) : cached; |
| | | const item = payload && payload.item != null ? payload.item : payload; |
| | | const type = payload && payload.type != null ? payload.type : "0"; |
| | | detail.value = normalizeDetail({ ...item, index: 1 }); |
| | | fetchRecordTypeOptions(type); |
| | | fetchRecordTypeOptions("0"); |
| | | uni.removeStorageSync("receiptDetailItem"); |
| | | } catch (e) { |
| | | uni.removeStorageSync("receiptDetailItem"); |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-if="isQualified" class="form-section"> |
| | | <view class="form-section"> |
| | | <view class="section-title">è¿ç£
ä¿¡æ¯</view> |
| | | <view class="form-row"> |
| | | <text class="form-label">车çå·</text> |
| | |
| | | import dayjs from "dayjs"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { createConsumablesIn } from "@/api/consumablesLogistics/consumablesIn.js"; |
| | | import { createConsumablesUnInventory } from "@/api/consumablesLogistics/consumablesUninventory.js"; |
| | | import { productModelList } from "@/api/basicData/productModel.js"; |
| | | |
| | | const form = reactive({ |
| | |
| | | remark: "", |
| | | }); |
| | | |
| | | const type = ref("0"); |
| | | const isQualified = computed(() => type.value === "0"); |
| | | const type = ref("0"); // åºå®åæ ¼åºå |
| | | const isQualified = computed(() => true); |
| | | |
| | | const showProductPopup = ref(false); |
| | | const productQuery = reactive({ |
| | |
| | | const weighingDateValue = ref(Date.now()); |
| | | |
| | | onLoad((options) => { |
| | | if (options && options.type != null) { |
| | | type.value = options.type; |
| | | } |
| | | type.value = "0"; |
| | | }); |
| | | |
| | | const openProductSelector = () => { |
| | |
| | | weighingOperator: form.weighingOperator, |
| | | remark: form.remark, |
| | | }; |
| | | const api = isQualified.value ? createConsumablesIn : createConsumablesUnInventory; |
| | | api(payload) |
| | | createConsumablesIn(payload) |
| | | .then(() => { |
| | | uni.showToast({ title: "æ°å¢æå", icon: "success" }); |
| | | setTimeout(() => { |
| | |
| | | <view class="stock-mgmt-page"> |
| | | <PageHeader title="åºå管ç" @back="goBack" /> |
| | | |
| | | <view class="tabs-wrap"> |
| | | <view |
| | | v-for="tab in tabs" |
| | | :key="tab.name" |
| | | class="tab-item" |
| | | :class="{ active: activeTab === tab.name }" |
| | | @click="activeTab = tab.name" |
| | | > |
| | | <text>{{ tab.label }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="search-section"> |
| | | <view class="search-row"> |
| | | <view class="search-input-wrap"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, reactive, ref, toRefs, watch } from "vue"; |
| | | import { computed, reactive, ref, toRefs } from "vue"; |
| | | import { onReachBottom, onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { frozenConsumablesIn, getConsumablesInListPage, thawConsumablesIn } from "@/api/consumablesLogistics/consumablesIn.js"; |
| | | import { frozenConsumablesUninventory, getConsumablesUninventoryListPage, thawConsumablesUninventory } from "@/api/consumablesLogistics/consumablesUninventory.js"; |
| | | |
| | | const activeTab = ref("qualified"); |
| | | const tabs = [ |
| | | { label: "åæ ¼åºå", name: "qualified" }, |
| | | { label: "ä¸åæ ¼åºå", name: "unqualified" }, |
| | | ]; |
| | | const tableData = ref([]); |
| | | const total = ref(0); |
| | | const loadStatus = ref("loadmore"); |
| | |
| | | }); |
| | | const { searchForm, quantityForm } = toRefs(data); |
| | | |
| | | const isQualified = () => activeTab.value === "qualified"; |
| | | const getList = () => { |
| | | const isFirstPage = page.current === 1; |
| | | if (isFirstPage) { |
| | | uni.showLoading({ title: "å è½½ä¸...", mask: true }); |
| | | } |
| | | const params = { ...page, productName: searchForm.value.productName }; |
| | | const api = isQualified() ? getConsumablesInListPage : getConsumablesUninventoryListPage; |
| | | api(params) |
| | | getConsumablesInListPage(params) |
| | | .then((res) => { |
| | | uni.hideLoading(); |
| | | const records = res.data?.records || []; |
| | |
| | | getList(); |
| | | }; |
| | | |
| | | watch(activeTab, () => { |
| | | page.current = 1; |
| | | loadStatus.value = "loadmore"; |
| | | getList(); |
| | | }); |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | loadStatus.value = "loadmore"; |
| | |
| | | }; |
| | | |
| | | const goAdd = () => { |
| | | const type = isQualified() ? "0" : "1"; |
| | | uni.navigateTo({ |
| | | url: `/pages/consumablesLogistics/stockManagement/add?type=${type}`, |
| | | url: `/pages/consumablesLogistics/stockManagement/add?type=0`, |
| | | }); |
| | | }; |
| | | |
| | |
| | | "stockSubtractRecord", |
| | | JSON.stringify({ |
| | | item: row, |
| | | type: isQualified() ? "0" : "1", |
| | | type: "0", |
| | | }) |
| | | ); |
| | | } catch (e) {} |
| | | const typeParam = isQualified() ? "0" : "1"; |
| | | uni.navigateTo({ |
| | | url: `/pages/consumablesLogistics/stockManagement/subtract?type=${typeParam}&id=${row.id}`, |
| | | url: `/pages/consumablesLogistics/stockManagement/subtract?type=0&id=${row.id}`, |
| | | }); |
| | | }; |
| | | |
| | |
| | | const base = { id, lockedQuantity: num }; |
| | | let promise; |
| | | if (quantityOp.value === "frozen") { |
| | | promise = isQualified() ? frozenConsumablesIn(base) : frozenConsumablesUninventory(base); |
| | | promise = frozenConsumablesIn(base); |
| | | } else { |
| | | promise = isQualified() ? thawConsumablesIn(base) : thawConsumablesUninventory(base); |
| | | promise = thawConsumablesIn(base); |
| | | } |
| | | promise |
| | | .then(() => { |
| | |
| | | |
| | | <style lang="scss" scoped> |
| | | .stock-mgmt-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 120rpx; } |
| | | .tabs-wrap { display: flex; background: #fff; padding: 24rpx; gap: 24rpx; } |
| | | .tab-item { flex: 1; text-align: center; padding: 20rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 28rpx; color: #666; } |
| | | .tab-item.active { background: #2979ff; color: #fff; } |
| | | .search-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; } |
| | | .search-row { display: flex; align-items: center; } |
| | | .search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; } |
| | |
| | | <text class="form-label required">åºåºæ°é</text> |
| | | <up-input v-model="form.stockOutNum" type="number" :placeholder="'æå¤§' + stockRecord.unLockedQuantity" /> |
| | | </view> |
| | | <view class="form-row" v-if="isQualified"> |
| | | <view class="form-row"> |
| | | <text class="form-label">车çå·</text> |
| | | <up-input v-model="form.licensePlateNo" placeholder="请è¾å
¥è½¦çå·" /> |
| | | </view> |
| | | <view class="form-row" v-if="isQualified"> |
| | | <view class="form-row"> |
| | | <text class="form-label">æ¯é(å¨)</text> |
| | | <up-input v-model="form.grossWeight" type="number" placeholder="请è¾å
¥æ¯é" /> |
| | | </view> |
| | | <view class="form-row" v-if="isQualified"> |
| | | <view class="form-row"> |
| | | <text class="form-label">ç®é(å¨)</text> |
| | | <up-input v-model="form.tareWeight" type="number" placeholder="请è¾å
¥ç®é" /> |
| | | </view> |
| | | <view class="form-row" v-if="isQualified"> |
| | | <view class="form-row"> |
| | | <text class="form-label">åé(å¨)</text> |
| | | <up-input v-model="form.netWeight" type="number" disabled placeholder="èªå¨è®¡ç®" /> |
| | | </view> |
| | | <view class="form-row" v-if="isQualified"> |
| | | <view class="form-row"> |
| | | <text class="form-label">è¿ç£
æ¥æ</text> |
| | | <view class="selector-trigger" @click="openWeighingDatePicker"> |
| | | <text class="selector-text" :class="{ placeholder: !form.weighingDate }"> |
| | |
| | | <up-icon name="calendar" size="16" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="form-row" v-if="isQualified"> |
| | | <view class="form-row"> |
| | | <text class="form-label">è¿ç£
å</text> |
| | | <up-input v-model="form.weighingOperator" placeholder="请è¾å
¥è¿ç£
å" /> |
| | | </view> |
| | |
| | | import dayjs from "dayjs"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { subtractConsumablesIn } from "@/api/consumablesLogistics/consumablesIn.js"; |
| | | import { subtractConsumablesUnInventory } from "@/api/consumablesLogistics/consumablesUninventory.js"; |
| | | |
| | | const type = ref("0"); |
| | | const isQualified = computed(() => type.value === "0"); |
| | | const isQualified = computed(() => true); |
| | | |
| | | const stockRecord = reactive({ |
| | | id: "", |
| | |
| | | const weighingDateValue = ref(Date.now()); |
| | | |
| | | onLoad((options) => { |
| | | if (options && options.type != null) { |
| | | type.value = options.type; |
| | | } |
| | | type.value = "0"; |
| | | const cached = uni.getStorageSync("stockSubtractRecord"); |
| | | if (cached) { |
| | | try { |
| | |
| | | return; |
| | | } |
| | | } |
| | | const api = isQualified.value ? subtractConsumablesIn : subtractConsumablesUnInventory; |
| | | api({ |
| | | subtractConsumablesIn({ |
| | | id: stockRecord.id, |
| | | stockOutNum: outNum, |
| | | licensePlateNo: form.licensePlateNo, |
| | |
| | | const detail = ref(null); |
| | | const loading = ref(true); |
| | | |
| | | function normalizeDetail(raw, type) { |
| | | function normalizeDetail(raw) { |
| | | if (!raw) return null; |
| | | const d = typeof raw === "object" ? raw : {}; |
| | | return { |
| | |
| | | lockedQuantity: d.lockedQuantity, |
| | | unLockedQuantity: d.unLockedQuantity ?? (d.qualitity - (d.lockedQuantity || 0)), |
| | | updateTime: d.updateTime, |
| | | typeLabel: type === "1" ? "ä¸åæ ¼åºå" : "åæ ¼åºå", |
| | | typeLabel: "åæ ¼åºå", |
| | | }; |
| | | } |
| | | |
| | |
| | | try { |
| | | const payload = typeof cached === "string" ? JSON.parse(cached) : cached; |
| | | const item = payload && payload.item != null ? payload.item : payload; |
| | | const type = payload && payload.type != null ? payload.type : "0"; |
| | | detail.value = normalizeDetail({ ...item, index: 1 }, type); |
| | | detail.value = normalizeDetail({ ...item, index: 1 }); |
| | | uni.removeStorageSync("stockDetailItem"); |
| | | } catch (e) { |
| | | uni.removeStorageSync("stockDetailItem"); |
| | |
| | | |
| | | // è´¨é管ç |
| | | const qualityItems = reactive([ |
| | | { |
| | | icon: "/static/images/icon/caigoutaizhang@2x.png", |
| | | label: "åæææ£éª", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/caigoutaizhang@2x.png", |
| | | label: "è¿ç¨æ£éª", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/caigoutaizhang@2x.png", |
| | | label: "åºåæ£éª", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/caigoutaizhang@2x.png", |
| | | // label: "åæææ£éª", |
| | | // }, |
| | | // { |
| | | // icon: "/static/images/icon/caigoutaizhang@2x.png", |
| | | // label: "è¿ç¨æ£éª", |
| | | // }, |
| | | // { |
| | | // icon: "/static/images/icon/caigoutaizhang@2x.png", |
| | | // label: "åºåæ£éª", |
| | | // }, |
| | | ]); |
| | | // åååå
¬åè½æ°æ® |
| | | const collaborationItems = reactive([ |
| | |
| | | url: "/pages/qualityManagement/nonconformingManagement/index", |
| | | }); |
| | | break; |
| | | case "åææ": |
| | | case "åææ£": |
| | | uni.navigateTo({ |
| | | url: "/pages/qualityManagement/rawMaterial/index", |
| | | }); |
| | |
| | | }); |
| | | purchaseItems.splice(0, purchaseItems.length, ...filteredPurchase); |
| | | |
| | | // è¿æ»¤è´¨é管çèå |
| | | // è´¨é管çèåï¼åºå®åªå±ç¤º 3 个å
¥å£ |
| | | const originalQuality = [ |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "åæææ£éª" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "è¿ç¨æ£éª" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "åºåæ£éª" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "æ£æµé¡¹ç»´æ¤" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "ææ ç»´æ¤" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "ææ ç»å®" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "åææ£" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "ä¸åæ ¼å管ç" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "åææ" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "è¿ææéè´§" }, |
| | | { icon: "/static/images/icon/caigoutaizhang@2x.png", label: "è´¨éçæ¿" }, |
| | | ]; |
| | | const hasAllowedTitleLike = label => { |
| | | if (allowedMenuTitles.has(label)) return true; |
| | | return Array.from(allowedMenuTitles).some( |
| | | title => |
| | | typeof title === "string" && |
| | | (title.includes(label) || label.includes(title)) |
| | | ); |
| | | }; |
| | | const filteredQuality = originalQuality.filter(item => { |
| | | return hasAllowedTitleLike(item.label); |
| | | }); |
| | | const hasQualityModulePermission = Array.from(allowedMenuTitles).some( |
| | | title => |
| | | typeof title === "string" && |
| | | (title.includes("è´¨é") || title.includes("æ£éª")) |
| | | ); |
| | | const finalQualityItems = filteredQuality.length |
| | | ? filteredQuality |
| | | : hasQualityModulePermission |
| | | ? originalQuality |
| | | : []; |
| | | qualityItems.splice(0, qualityItems.length, ...finalQualityItems); |
| | | qualityItems.splice(0, qualityItems.length, ...originalQuality); |
| | | |
| | | // è¿æ»¤å®å
¨ç产èå |
| | | const originalSafety = [ |
| | |
| | | <view class="dispatch-page"> |
| | | <PageHeader title="åºåºå°è´¦" @back="goBack" /> |
| | | |
| | | <!-- æ ç¾ï¼åæ ¼åºåº / ä¸åæ ¼åºåº --> |
| | | <view class="tabs-wrap"> |
| | | <view |
| | | v-for="tab in tabs" |
| | | :key="tab.name" |
| | | class="tab-item" |
| | | :class="{ active: activeTab === tab.name }" |
| | | @click="activeTab = tab.name" |
| | | > |
| | | <text>{{ tab.label }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-row"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, watch } from 'vue' |
| | | import { ref, reactive, toRefs } from 'vue' |
| | | import { onShow, onReachBottom } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { getStockOutPage, delStockOut } from '@/api/inventoryManagement/stockOutRecord.js' |
| | | import { |
| | | findAllQualifiedStockOutRecordTypeOptions, |
| | | findAllUnQualifiedStockOutRecordTypeOptions |
| | | findAllQualifiedStockOutRecordTypeOptions |
| | | } from '@/api/basicData/enum.js' |
| | | |
| | | const activeTab = ref('qualified') |
| | | const stockRecordTypeOptions = ref([]) |
| | | const tabs = [ |
| | | { label: 'åæ ¼åºåº', name: 'qualified', type: '0' }, |
| | | { label: 'ä¸åæ ¼åºåº', name: 'unqualified', type: '1' } |
| | | ] |
| | | const currentType = () => '0' |
| | | const tableData = ref([]) |
| | | const total = ref(0) |
| | | const loadStatus = ref('loadmore') |
| | |
| | | }) |
| | | const { searchForm } = toRefs(data) |
| | | |
| | | const currentType = () => tabs.find(t => t.name === activeTab.value)?.type || '0' |
| | | |
| | | function getRecordType(recordType) { |
| | | if (recordType == null || recordType === '') return '' |
| | | return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || '' |
| | | } |
| | | |
| | | function fetchRecordTypeOptions() { |
| | | const api = currentType() === '1' |
| | | ? findAllUnQualifiedStockOutRecordTypeOptions |
| | | : findAllQualifiedStockOutRecordTypeOptions |
| | | api() |
| | | findAllQualifiedStockOutRecordTypeOptions() |
| | | .then(res => { |
| | | const list = res.data != null ? res.data : res |
| | | stockRecordTypeOptions.value = Array.isArray(list) ? list : [] |
| | |
| | | getList() |
| | | } |
| | | |
| | | watch(activeTab, () => { |
| | | page.current = 1 |
| | | loadStatus.value = 'loadmore' |
| | | stockRecordTypeOptions.value = [] |
| | | getList() |
| | | }) |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | loadStatus.value = 'loadmore' |
| | |
| | | try { |
| | | uni.setStorageSync('dispatchDetailItem', JSON.stringify({ |
| | | item, |
| | | type: currentType() |
| | | type: '0' |
| | | })) |
| | | } catch (e) {} |
| | | uni.navigateTo({ |
| | |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | padding-bottom: 40rpx; |
| | | } |
| | | .tabs-wrap { |
| | | display: flex; |
| | | background: #fff; |
| | | padding: 24rpx; |
| | | gap: 24rpx; |
| | | } |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 20rpx; |
| | | border-radius: 12rpx; |
| | | background: #f0f0f0; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | } |
| | | .tab-item.active { |
| | | background: #2979ff; |
| | | color: #fff; |
| | | } |
| | | .search-section { |
| | | background: #fff; |
| | |
| | | import { onLoad } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { |
| | | findAllQualifiedStockOutRecordTypeOptions, |
| | | findAllUnQualifiedStockOutRecordTypeOptions |
| | | findAllQualifiedStockOutRecordTypeOptions |
| | | } from '@/api/basicData/enum.js' |
| | | |
| | | const detail = ref(null) |
| | |
| | | return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || '' |
| | | } |
| | | |
| | | function fetchRecordTypeOptions(type) { |
| | | const api = type === '1' |
| | | ? findAllUnQualifiedStockOutRecordTypeOptions |
| | | : findAllQualifiedStockOutRecordTypeOptions |
| | | api() |
| | | function fetchRecordTypeOptions() { |
| | | findAllQualifiedStockOutRecordTypeOptions() |
| | | .then(res => { |
| | | const data = res.data != null ? res.data : res |
| | | stockRecordTypeOptions.value = Array.isArray(data) ? data : [] |
| | |
| | | try { |
| | | const payload = typeof cached === 'string' ? JSON.parse(cached) : cached |
| | | const item = payload && payload.item != null ? payload.item : payload |
| | | const type = payload && payload.type != null ? payload.type : '0' |
| | | detail.value = normalizeDetail({ ...item, index: 1 }) |
| | | fetchRecordTypeOptions(type) |
| | | fetchRecordTypeOptions() |
| | | uni.removeStorageSync('dispatchDetailItem') |
| | | } catch (e) { |
| | | uni.removeStorageSync('dispatchDetailItem') |
| | |
| | | <view class="receipt-page"> |
| | | <PageHeader title="å
¥åºç®¡ç" @back="goBack" /> |
| | | |
| | | <!-- æ ç¾ï¼åæ ¼å
¥åº / ä¸åæ ¼å
¥åº --> |
| | | <view class="tabs-wrap"> |
| | | <view |
| | | v-for="tab in tabs" |
| | | :key="tab.name" |
| | | class="tab-item" |
| | | :class="{ active: activeTab === tab.name }" |
| | | @click="activeTab = tab.name" |
| | | > |
| | | <text>{{ tab.label }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-row"> |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å表ï¼åæ ¼/ä¸åæ ¼å
±ç¨æ¥å£ type åºåï¼ --> |
| | | <!-- å表 --> |
| | | <view class="list-section" v-if="activeTab !== 'custom'"> |
| | | <view v-if="tableData.length > 0"> |
| | | <view |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, watch } from 'vue' |
| | | import { ref, reactive, toRefs } from 'vue' |
| | | import { onShow, onReachBottom } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { |
| | |
| | | batchDeleteStockInRecords |
| | | } from '@/api/inventoryManagement/stockInRecord.js' |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | findAllUnQualifiedStockInRecordTypeOptions |
| | | findAllQualifiedStockInRecordTypeOptions |
| | | } from '@/api/basicData/enum.js' |
| | | |
| | | const activeTab = ref('qualified') |
| | | const stockRecordTypeOptions = ref([]) |
| | | const tabs = [ |
| | | { label: 'åæ ¼å
¥åº', name: 'qualified', type: '0' }, |
| | | { label: 'ä¸åæ ¼å
¥åº', name: 'unqualified', type: '1' } |
| | | ] |
| | | const currentType = () => '0' |
| | | |
| | | const tableData = ref([]) |
| | | const total = ref(0) |
| | |
| | | }) |
| | | const { searchForm } = toRefs(data) |
| | | |
| | | const currentType = () => tabs.find(t => t.name === activeTab.value)?.type || '0' |
| | | |
| | | function getRecordType(recordType) { |
| | | if (recordType == null || recordType === '') return '' |
| | | return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || '' |
| | | } |
| | | |
| | | function fetchRecordTypeOptions() { |
| | | const api = currentType() === '1' |
| | | ? findAllUnQualifiedStockInRecordTypeOptions |
| | | : findAllQualifiedStockInRecordTypeOptions |
| | | api() |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | const data = res.data != null ? res.data : res |
| | | stockRecordTypeOptions.value = Array.isArray(data) ? data : [] |
| | |
| | | } |
| | | |
| | | const getList = () => { |
| | | if (activeTab.value === 'custom') return |
| | | const isFirstPage = page.current === 1 |
| | | if (isFirstPage) { |
| | | uni.showLoading({ title: 'å è½½ä¸...', mask: true }) |
| | |
| | | getList() |
| | | } |
| | | |
| | | watch(activeTab, () => { |
| | | page.current = 1 |
| | | loadStatus.value = 'loadmore' |
| | | stockRecordTypeOptions.value = [] |
| | | getList() |
| | | }) |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | loadStatus.value = 'loadmore' |
| | |
| | | try { |
| | | uni.setStorageSync('receiptDetailItem', JSON.stringify({ |
| | | item, |
| | | type: currentType() |
| | | type: '0' |
| | | })) |
| | | } catch (e) {} |
| | | uni.navigateTo({ |
| | |
| | | const goBack = () => uni.navigateBack() |
| | | |
| | | onShow(() => { |
| | | if (activeTab.value !== 'custom') getList() |
| | | getList() |
| | | }) |
| | | |
| | | onReachBottom(() => { |
| | |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | padding-bottom: 40rpx; |
| | | } |
| | | .tabs-wrap { |
| | | display: flex; |
| | | background: #fff; |
| | | padding: 24rpx; |
| | | gap: 24rpx; |
| | | } |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 20rpx; |
| | | border-radius: 12rpx; |
| | | background: #f0f0f0; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | } |
| | | .tab-item.active { |
| | | background: #2979ff; |
| | | color: #fff; |
| | | } |
| | | .search-section { |
| | | background: #fff; |
| | |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | width: 100%; |
| | | text-align: center; |
| | | } |
| | | .btn-delete { |
| | | font-size: 28rpx; |
| | | color: #f56c6c; |
| | | padding: 12rpx 32rpx; |
| | | margin: 0 auto; |
| | | display: inline-flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .no-data { |
| | | text-align: center; |
| | |
| | | import { onLoad } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { |
| | | findAllQualifiedStockInRecordTypeOptions, |
| | | findAllUnQualifiedStockInRecordTypeOptions |
| | | findAllQualifiedStockInRecordTypeOptions |
| | | } from '@/api/basicData/enum.js' |
| | | |
| | | const detail = ref(null) |
| | |
| | | return stockRecordTypeOptions.value.find(item => item.value === recordType)?.label || '' |
| | | } |
| | | |
| | | function fetchRecordTypeOptions(type) { |
| | | const api = type === '1' |
| | | ? findAllUnQualifiedStockInRecordTypeOptions |
| | | : findAllQualifiedStockInRecordTypeOptions |
| | | api() |
| | | function fetchRecordTypeOptions() { |
| | | findAllQualifiedStockInRecordTypeOptions() |
| | | .then(res => { |
| | | const data = res.data != null ? res.data : res |
| | | stockRecordTypeOptions.value = Array.isArray(data) ? data : [] |
| | |
| | | try { |
| | | const payload = typeof cached === 'string' ? JSON.parse(cached) : cached |
| | | const item = payload && payload.item != null ? payload.item : payload |
| | | const type = payload && payload.type != null ? payload.type : '0' |
| | | detail.value = normalizeDetail({ ...item, index: 1 }) |
| | | fetchRecordTypeOptions(type) |
| | | fetchRecordTypeOptions() |
| | | uni.removeStorageSync('receiptDetailItem') |
| | | } catch (e) { |
| | | uni.removeStorageSync('receiptDetailItem') |
| | |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- åæ ¼åºåæ¶æ¾ç¤ºè¿ç£
ç¸å
³å段 --> |
| | | <view v-if="isQualified" class="form-section"> |
| | | <!-- è¿ç£
ç¸å
³å段 --> |
| | | <view class="form-section"> |
| | | <view class="section-title">è¿ç£
ä¿¡æ¯</view> |
| | | <view class="form-row"> |
| | | <text class="form-label">车çå·</text> |
| | |
| | | import dayjs from 'dayjs' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { createStockInventory } from '@/api/inventoryManagement/stockInventory.js' |
| | | import { createStockUnInventory } from '@/api/inventoryManagement/stockUninventory.js' |
| | | import { productModelList } from '@/api/basicData/productModel.js' |
| | | |
| | | const form = reactive({ |
| | |
| | | remark: '' |
| | | }) |
| | | |
| | | const type = ref('0') // 0 åæ ¼åºåï¼1 ä¸åæ ¼åºå |
| | | const isQualified = computed(() => type.value === '0') |
| | | const type = ref('0') // åºå®åæ ¼åºå |
| | | const isQualified = computed(() => true) |
| | | |
| | | const showProductPopup = ref(false) |
| | | const productQuery = reactive({ |
| | |
| | | const weighingDateValue = ref(Date.now()) |
| | | |
| | | onLoad((options) => { |
| | | if (options && options.type != null) { |
| | | type.value = options.type |
| | | } |
| | | type.value = '0' |
| | | }) |
| | | |
| | | const openProductSelector = () => { |
| | |
| | | weighingOperator: form.weighingOperator, |
| | | remark: form.remark |
| | | } |
| | | const api = isQualified.value ? createStockInventory : createStockUnInventory |
| | | api(payload) |
| | | createStockInventory(payload) |
| | | .then(() => { |
| | | uni.showToast({ title: 'æ°å¢æå', icon: 'success' }) |
| | | setTimeout(() => { |
| | |
| | | <view class="stock-mgmt-page"> |
| | | <PageHeader title="åºå管ç" @back="goBack" /> |
| | | |
| | | <!-- æ ç¾ï¼åæ ¼åºå / ä¸åæ ¼åºå --> |
| | | <view class="tabs-wrap"> |
| | | <view |
| | | v-for="tab in tabs" |
| | | :key="tab.name" |
| | | class="tab-item" |
| | | :class="{ active: activeTab === tab.name }" |
| | | @click="activeTab = tab.name" |
| | | > |
| | | <text>{{ tab.label }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-row"> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, watch, computed } from 'vue' |
| | | import { ref, reactive, toRefs, computed } from 'vue' |
| | | import { onShow, onReachBottom } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { |
| | |
| | | frozenStockInventory, |
| | | thawStockInventory |
| | | } from '@/api/inventoryManagement/stockInventory.js' |
| | | import { |
| | | getStockUninventoryListPage, |
| | | createStockUnInventory, |
| | | subtractStockUnInventory, |
| | | frozenStockUninventory, |
| | | thawStockUninventory |
| | | } from '@/api/inventoryManagement/stockUninventory.js' |
| | | |
| | | const activeTab = ref('qualified') |
| | | const tabs = [ |
| | | { label: 'åæ ¼åºå', name: 'qualified' }, |
| | | { label: 'ä¸åæ ¼åºå', name: 'unqualified' } |
| | | ] |
| | | const tableData = ref([]) |
| | | const total = ref(0) |
| | | const loadStatus = ref('loadmore') // loadmore | loading | nomore | error |
| | |
| | | }) |
| | | const { searchForm, quantityForm } = toRefs(data) |
| | | |
| | | const isQualified = () => activeTab.value === 'qualified' |
| | | const getList = () => { |
| | | const isFirstPage = page.current === 1 |
| | | if (isFirstPage) { |
| | | uni.showLoading({ title: 'å è½½ä¸...', mask: true }) |
| | | } |
| | | const params = { ...page, productName: searchForm.value.productName } |
| | | const api = isQualified() ? getStockInventoryListPage : getStockUninventoryListPage |
| | | api(params) |
| | | getStockInventoryListPage(params) |
| | | .then(res => { |
| | | uni.hideLoading() |
| | | const records = res.data?.records || [] |
| | |
| | | getList() |
| | | } |
| | | |
| | | watch(activeTab, () => { |
| | | page.current = 1 |
| | | loadStatus.value = 'loadmore' |
| | | getList() |
| | | }) |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | loadStatus.value = 'loadmore' |
| | |
| | | } |
| | | |
| | | const goAdd = () => { |
| | | const type = isQualified() ? '0' : '1' |
| | | uni.navigateTo({ |
| | | url: `/pages/inventoryManagement/stockManagement/add?type=${type}` |
| | | url: `/pages/inventoryManagement/stockManagement/add?type=0` |
| | | }) |
| | | } |
| | | |
| | |
| | | try { |
| | | uni.setStorageSync('stockSubtractRecord', JSON.stringify({ |
| | | item: row, |
| | | type: isQualified() ? '0' : '1' |
| | | type: '0' |
| | | })) |
| | | } catch (e) {} |
| | | const typeParam = isQualified() ? '0' : '1' |
| | | uni.navigateTo({ |
| | | url: `/pages/inventoryManagement/stockManagement/subtract?type=${typeParam}&id=${row.id}` |
| | | url: `/pages/inventoryManagement/stockManagement/subtract?type=0&id=${row.id}` |
| | | }) |
| | | } |
| | | const openFrozen = (row) => { |
| | |
| | | const base = { id, lockedQuantity: num } |
| | | let promise |
| | | if (quantityOp.value === 'frozen') { |
| | | promise = isQualified() ? frozenStockInventory(base) : frozenStockUninventory(base) |
| | | promise = frozenStockInventory(base) |
| | | } else { |
| | | promise = isQualified() ? thawStockInventory(base) : thawStockUninventory(base) |
| | | promise = thawStockInventory(base) |
| | | } |
| | | promise.then(() => { |
| | | uni.showToast({ title: 'æä½æå', icon: 'success' }) |
| | |
| | | try { |
| | | uni.setStorageSync('stockDetailItem', JSON.stringify({ |
| | | item, |
| | | type: isQualified() ? '0' : '1' |
| | | type: '0' |
| | | })) |
| | | } catch (e) {} |
| | | if (!item.id) { |
| | |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | padding-bottom: 120rpx; |
| | | } |
| | | .tabs-wrap { |
| | | display: flex; |
| | | background: #fff; |
| | | padding: 24rpx; |
| | | gap: 24rpx; |
| | | } |
| | | .tab-item { |
| | | flex: 1; |
| | | text-align: center; |
| | | padding: 20rpx; |
| | | border-radius: 12rpx; |
| | | background: #f0f0f0; |
| | | font-size: 28rpx; |
| | | color: #666; |
| | | } |
| | | .tab-item.active { |
| | | background: #2979ff; |
| | | color: #fff; |
| | | } |
| | | .search-section { |
| | | background: #fff; |
| | |
| | | import dayjs from 'dayjs' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { subtractStockInventory } from '@/api/inventoryManagement/stockInventory.js' |
| | | import { subtractStockUnInventory } from '@/api/inventoryManagement/stockUninventory.js' |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | |
| | | remark: '' |
| | | }) |
| | | |
| | | const type = ref('0') // 0 åæ ¼åºåï¼1 ä¸åæ ¼åºå |
| | | const type = ref('0') // åºå®åæ ¼åºå |
| | | const showWeighingDatePicker = ref(false) |
| | | const weighingDateValue = ref(Date.now()) |
| | | |
| | |
| | | }) |
| | | |
| | | onLoad((options) => { |
| | | if (options && options.type != null) { |
| | | type.value = options.type |
| | | } |
| | | type.value = '0' |
| | | const cached = uni.getStorageSync('stockSubtractRecord') |
| | | if (cached) { |
| | | try { |
| | |
| | | return |
| | | } |
| | | const payload = { ...form } |
| | | const api = type.value === '0' ? subtractStockInventory : subtractStockUnInventory |
| | | api(payload) |
| | | subtractStockInventory(payload) |
| | | .then(() => { |
| | | uni.showToast({ title: 'åºåºæå', icon: 'success' }) |
| | | setTimeout(() => { |
| | |
| | | <view class="dialog-header"> |
| | | <text class="dialog-title">{{ operationType === 'add' ? 'æ°å¢æ£æµé¡¹ç®' : 'ä¿®æ¹æ£æµé¡¹ç®' }}</text> |
| | | </view> |
| | | <up-form :model="form" ref="formRef" label-width="100" label-position="top"> |
| | | <up-form :model="form" :rules="rules" ref="formRef" label-width="100" label-position="top"> |
| | | <up-form-item label="æ£æµé¡¹ç®" prop="name" required borderBottom> |
| | | <up-input v-model="form.name" placeholder="请è¾å
¥æ£æµé¡¹ç®åç§°" border="surround" /> |
| | | </up-form-item> |
| | |
| | | dialogVisible.value = false; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | formRef.value.validate().then(res => { |
| | | submitLoading.value = true; |
| | | qualityInspectItemSave(form).then(() => { |
| | | const submitForm = async () => { |
| | | if (!formRef.value) return |
| | | const valid = await formRef.value.validate().catch(() => false) |
| | | if (!valid) return |
| | | submitLoading.value = true; |
| | | qualityInspectItemSave(form) |
| | | .then(() => { |
| | | toast(operationType.value === 'add' ? 'æ°å¢æå' : 'ä¿®æ¹æå'); |
| | | dialogVisible.value = false; |
| | | handleQuery(); |
| | | }).finally(() => { |
| | | }) |
| | | .finally(() => { |
| | | submitLoading.value = false; |
| | | }); |
| | | }).catch(errors => { |
| | | console.log('éªè¯å¤±è´¥', errors); |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = (row) => { |
| | | showConfirm('确认å é¤è¯¥æ£æµé¡¹ç®åï¼').then(res => { |
| | | if (res.confirm) { |
| | | qualityInspectItemDelete({ id: row.id }).then(() => { |
| | | // å¯¹é½ PC 端ï¼å 餿¥å£æ¥æ¶ id æ°ç» |
| | | qualityInspectItemDelete([row.id]).then(() => { |
| | | toast('å 餿å'); |
| | | handleQuery(); |
| | | }); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="nonconforming-form-page"> |
| | | <PageHeader :title="pageTitle" @back="goBack" /> |
| | | |
| | | <scroll-view scroll-y class="content-scroll"> |
| | | <view class="form-section"> |
| | | <view class="form-row"> |
| | | <text class="form-label required">产ååç§°</text> |
| | | <view class="selector-trigger" @click="openProductSelector" :class="{ disabled: isEdit }"> |
| | | <text class="selector-text" :class="{ placeholder: !form.productName }"> |
| | | {{ form.productName || 'è¯·éæ©' }} |
| | | </text> |
| | | <up-icon name="arrow-right" size="16" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label required">è§æ ¼åå·</text> |
| | | <view class="selector-trigger" @click="openModelSelector" :class="{ disabled: !form.productId || isEdit }"> |
| | | <text class="selector-text" :class="{ placeholder: !form.model }"> |
| | | {{ form.model || 'è¯·éæ©' }} |
| | | </text> |
| | | <up-icon name="arrow-right" size="16" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label">åä½</text> |
| | | <up-input v-model="form.unit" disabled placeholder="èªå¨å¸¦åº" /> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ¹å·</text> |
| | | <up-input v-model="form.batchNo" placeholder="请è¾å
¥" /> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ£éªç±»å</text> |
| | | <view class="selector-trigger" @click="showTypeSelect = true"> |
| | | <text class="selector-text" :class="{ placeholder: form.checkType === undefined || form.checkType === '' }"> |
| | | {{ checkTypeLabel || 'è¯·éæ©' }} |
| | | </text> |
| | | <up-icon name="arrow-down" size="14" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ£éªå</text> |
| | | <view class="selector-trigger" @click="showUserSelect = true"> |
| | | <text class="selector-text" :class="{ placeholder: !form.checkName }"> |
| | | {{ form.checkName || 'è¯·éæ©' }} |
| | | </text> |
| | | <up-icon name="arrow-down" size="14" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ£æµæ¥æ</text> |
| | | <view class="selector-trigger" @click="openCheckTimePicker"> |
| | | <text class="selector-text" :class="{ placeholder: !form.checkTime }"> |
| | | {{ form.checkTime || 'è¯·éæ©' }} |
| | | </text> |
| | | <up-icon name="calendar" size="16" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label required">ä¸åæ ¼ç°è±¡</text> |
| | | <up-textarea v-model="form.defectivePhenomena" placeholder="请è¾å
¥" count border="surround" /> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="form-section"> |
| | | <view class="section-title">é»è®¤å¤çä¿¡æ¯</view> |
| | | <view class="form-row"> |
| | | <text class="form-label required">å¤çç»æ</text> |
| | | <up-input :modelValue="dealResultLabel || 'æ¥åº'" disabled /> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label">å¤ç人</text> |
| | | <up-input v-model="form.dealName" placeholder="éå¡«" /> |
| | | </view> |
| | | <view class="form-row"> |
| | | <text class="form-label">å¤çæ¥æ</text> |
| | | <view class="selector-trigger" @click="openDealTimePicker"> |
| | | <text class="selector-text" :class="{ placeholder: !form.dealTime }"> |
| | | {{ form.dealTime || 'è¯·éæ©' }} |
| | | </text> |
| | | <up-icon name="calendar" size="16" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="bottom-bar"> |
| | | <view class="btn-submit" @click="handleSubmit" :class="{ disabled: submitting }"> |
| | | {{ submitting ? 'æäº¤ä¸...' : 'æäº¤' }} |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 产åéæ© --> |
| | | <up-popup :show="showProductPopup" mode="bottom" @close="showProductPopup = false"> |
| | | <view class="popup"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">éæ©äº§å</text> |
| | | </view> |
| | | <scroll-view scroll-y class="popup-list"> |
| | | <view |
| | | v-for="(item, idx) in productOptions" |
| | | :key="item.value || idx" |
| | | class="popup-item" |
| | | @click="selectProduct(item)" |
| | | > |
| | | <text class="popup-item-title">{{ item.label }}</text> |
| | | </view> |
| | | <view v-if="!productLoading && productOptions.length === 0" class="no-data">ææ æ°æ®</view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- åå·éæ© --> |
| | | <up-popup :show="showModelPopup" mode="bottom" @close="showModelPopup = false"> |
| | | <view class="popup"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">éæ©è§æ ¼åå·</text> |
| | | </view> |
| | | <scroll-view scroll-y class="popup-list"> |
| | | <view |
| | | v-for="(item, idx) in modelOptions" |
| | | :key="item.id || idx" |
| | | class="popup-item" |
| | | @click="selectModel(item)" |
| | | > |
| | | <view class="popup-item-row"> |
| | | <text class="popup-item-title">{{ item.model }}</text> |
| | | <text class="popup-item-sub">{{ item.unit }}</text> |
| | | </view> |
| | | </view> |
| | | <view v-if="!modelLoading && modelOptions.length === 0" class="no-data">ææ æ°æ®</view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- æ£éªç±»å --> |
| | | <up-action-sheet |
| | | :actions="checkTypeActions" |
| | | :show="showTypeSelect" |
| | | @close="showTypeSelect = false" |
| | | @select="selectCheckType" |
| | | title="è¯·éæ©æ£éªç±»å" |
| | | /> |
| | | |
| | | <!-- æ£éªå --> |
| | | <up-action-sheet |
| | | :actions="userActions" |
| | | :show="showUserSelect" |
| | | @close="showUserSelect = false" |
| | | @select="selectUser" |
| | | title="è¯·éæ©æ£éªå" |
| | | /> |
| | | |
| | | <!-- æ¥æéæ©å¨ï¼æ£æµæ¥æ --> |
| | | <up-datetime-picker |
| | | :show="showCheckTimePicker" |
| | | v-model="checkTimeValue" |
| | | mode="date" |
| | | @confirm="confirmCheckTime" |
| | | @cancel="showCheckTimePicker = false" |
| | | /> |
| | | <!-- æ¥æéæ©å¨ï¼å¤çæ¥æ --> |
| | | <up-datetime-picker |
| | | :show="showDealTimePicker" |
| | | v-model="dealTimeValue" |
| | | mode="date" |
| | | @confirm="confirmDealTime" |
| | | @cancel="showDealTimePicker = false" |
| | | /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, reactive, ref } from 'vue' |
| | | import { onLoad } from '@dcloudio/uni-app' |
| | | import dayjs from 'dayjs' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { useDict } from '@/utils/dict' |
| | | import { toast } from '@/utils/common' |
| | | import { productTreeList, modelList } from '@/api/basicData/product.js' |
| | | import { userListNoPage } from '@/api/system/user.js' |
| | | import { |
| | | getQualityUnqualifiedInfo, |
| | | qualityUnqualifiedAdd, |
| | | qualityUnqualifiedUpdate |
| | | } from '@/api/qualityManagement/nonconformingManagement.js' |
| | | |
| | | const pageType = ref('add') // add | edit |
| | | const id = ref('') |
| | | const submitting = ref(false) |
| | | |
| | | const isEdit = computed(() => pageType.value === 'edit') |
| | | const pageTitle = computed(() => (isEdit.value ? 'ç¼è¾ä¸åæ ¼ç®¡ç' : 'æ°å¢ä¸åæ ¼ç®¡ç')) |
| | | |
| | | const { rejection_handling } = useDict('rejection_handling') |
| | | const dealResultLabel = computed(() => { |
| | | const list = rejection_handling?.value || [] |
| | | const v = form.dealResult |
| | | return (list || []).find(it => String(it.value) === String(v))?.label || '' |
| | | }) |
| | | const getScrapDealResultValue = () => { |
| | | const list = rejection_handling?.value || [] |
| | | const scrap = (list || []).find(it => String(it?.label ?? '') === 'æ¥åº') |
| | | return scrap?.value ?? '' |
| | | } |
| | | |
| | | const form = reactive({ |
| | | id: undefined, |
| | | productId: '', |
| | | productName: '', |
| | | productModelId: '', |
| | | model: '', |
| | | unit: '', |
| | | batchNo: '', |
| | | checkType: undefined, |
| | | checkName: '', |
| | | checkTime: '', |
| | | defectivePhenomena: '', |
| | | dealResult: '', |
| | | dealName: '', |
| | | dealTime: '' |
| | | }) |
| | | |
| | | // 鿩卿°æ® |
| | | const showProductPopup = ref(false) |
| | | const productLoading = ref(false) |
| | | const productOptions = ref([]) |
| | | |
| | | const showModelPopup = ref(false) |
| | | const modelLoading = ref(false) |
| | | const modelOptions = ref([]) |
| | | |
| | | const showTypeSelect = ref(false) |
| | | const checkTypeActions = [ |
| | | { name: 'å
¥åæ£', value: 0 }, |
| | | { name: 'è½¦é´æ£', value: 1 }, |
| | | { name: 'åºåæ£', value: 2 } |
| | | ] |
| | | const checkTypeLabel = computed(() => { |
| | | const v = form.checkType |
| | | return checkTypeActions.find(it => String(it.value) === String(v))?.name || '' |
| | | }) |
| | | |
| | | const showUserSelect = ref(false) |
| | | const userActions = ref([]) |
| | | |
| | | const showCheckTimePicker = ref(false) |
| | | const checkTimeValue = ref(Date.now()) |
| | | const showDealTimePicker = ref(false) |
| | | const dealTimeValue = ref(Date.now()) |
| | | |
| | | const loadProducts = async () => { |
| | | productLoading.value = true |
| | | try { |
| | | const res = await productTreeList() |
| | | const list = |
| | | (Array.isArray(res) ? res : null) || |
| | | (Array.isArray(res?.data) ? res.data : null) || |
| | | (Array.isArray(res?.records) ? res.records : null) || |
| | | (Array.isArray(res?.data?.records) ? res.data.records : null) || |
| | | [] |
| | | // ä»
åå¶åèç¹ï¼å¯é产åï¼ï¼å¹¶è½¬ä¸º action å表 |
| | | const flat = [] |
| | | const walk = (nodes) => { |
| | | ;(nodes || []).forEach(n => { |
| | | const children = Array.isArray(n?.children) ? n.children : [] |
| | | if (children.length > 0) walk(children) |
| | | else { |
| | | const value = n?.value ?? n?.id |
| | | const label = n?.label || n?.productName || n?.name || '' |
| | | flat.push({ label, value }) |
| | | } |
| | | }) |
| | | } |
| | | walk(list) |
| | | productOptions.value = flat.filter(it => it.value) |
| | | } finally { |
| | | productLoading.value = false |
| | | } |
| | | } |
| | | |
| | | const openProductSelector = async () => { |
| | | if (isEdit.value) return |
| | | showProductPopup.value = true |
| | | if (productOptions.value.length === 0) { |
| | | await loadProducts() |
| | | } |
| | | } |
| | | const selectProduct = (item) => { |
| | | if (isEdit.value) return |
| | | form.productId = item.value |
| | | form.productName = item.label |
| | | form.productModelId = '' |
| | | form.model = '' |
| | | form.unit = '' |
| | | modelOptions.value = [] |
| | | showProductPopup.value = false |
| | | } |
| | | |
| | | const loadModels = async () => { |
| | | if (!form.productId) return |
| | | modelLoading.value = true |
| | | try { |
| | | const res = await modelList({ id: form.productId }) |
| | | modelOptions.value = Array.isArray(res) ? res : (res?.data || []) |
| | | } finally { |
| | | modelLoading.value = false |
| | | } |
| | | } |
| | | const openModelSelector = async () => { |
| | | if (isEdit.value) return |
| | | if (!form.productId) { |
| | | toast('请å
éæ©äº§ååç§°') |
| | | return |
| | | } |
| | | showModelPopup.value = true |
| | | if (modelOptions.value.length === 0) { |
| | | await loadModels() |
| | | } |
| | | } |
| | | const selectModel = (item) => { |
| | | if (isEdit.value) return |
| | | form.productModelId = item?.id |
| | | form.model = item?.model || '' |
| | | form.unit = item?.unit || '' |
| | | showModelPopup.value = false |
| | | } |
| | | |
| | | const selectCheckType = (e) => { |
| | | form.checkType = e.value |
| | | showTypeSelect.value = false |
| | | } |
| | | |
| | | const loadUsers = async () => { |
| | | const res = await userListNoPage() |
| | | const list = res?.data || [] |
| | | userActions.value = (list || []).map(u => ({ name: u.nickName, value: u.nickName })).filter(it => it.value) |
| | | } |
| | | const selectUser = (e) => { |
| | | form.checkName = e.value |
| | | showUserSelect.value = false |
| | | } |
| | | |
| | | const openCheckTimePicker = () => { |
| | | checkTimeValue.value = form.checkTime ? dayjs(form.checkTime, 'YYYY-MM-DD').valueOf() : Date.now() |
| | | showCheckTimePicker.value = true |
| | | } |
| | | const confirmCheckTime = (e) => { |
| | | const ts = e?.value ?? checkTimeValue.value |
| | | form.checkTime = dayjs(ts).format('YYYY-MM-DD') |
| | | showCheckTimePicker.value = false |
| | | } |
| | | |
| | | const openDealTimePicker = () => { |
| | | dealTimeValue.value = form.dealTime ? dayjs(form.dealTime, 'YYYY-MM-DD').valueOf() : Date.now() |
| | | showDealTimePicker.value = true |
| | | } |
| | | const confirmDealTime = (e) => { |
| | | const ts = e?.value ?? dealTimeValue.value |
| | | form.dealTime = dayjs(ts).format('YYYY-MM-DD') |
| | | showDealTimePicker.value = false |
| | | } |
| | | |
| | | const loadDetail = async () => { |
| | | if (!id.value) return |
| | | const res = await getQualityUnqualifiedInfo(id.value) |
| | | const d = res?.data || {} |
| | | Object.assign(form, { |
| | | id: d.id, |
| | | productId: d.productId, |
| | | productName: d.productName, |
| | | productModelId: d.productModelId, |
| | | model: d.model, |
| | | unit: d.unit, |
| | | batchNo: d.batchNo, |
| | | // å
¼å®¹å端è¿ååæ®µï¼ä¼å
checkTypeï¼å
¶æ¬¡ inspectType |
| | | checkType: d.checkType ?? d.inspectType, |
| | | checkName: d.checkName, |
| | | checkTime: d.checkTime, |
| | | defectivePhenomena: d.defectivePhenomena, |
| | | dealResult: d.dealResult, |
| | | dealName: d.dealName, |
| | | dealTime: d.dealTime |
| | | }) |
| | | } |
| | | |
| | | const validate = () => { |
| | | if (!form.productId) return toast('è¯·éæ©äº§ååç§°'), false |
| | | if (!form.productModelId) return toast('è¯·éæ©è§æ ¼åå·'), false |
| | | if (!form.batchNo) return toast('请è¾å
¥æ¹å·'), false |
| | | if (form.checkType === undefined || form.checkType === '') return toast('è¯·éæ©æ£éªç±»å'), false |
| | | if (!form.checkName) return toast('è¯·éæ©æ£éªå'), false |
| | | if (!form.checkTime) return toast('è¯·éæ©æ£æµæ¥æ'), false |
| | | if (!form.defectivePhenomena) return toast('请è¾å
¥ä¸åæ ¼ç°è±¡'), false |
| | | return true |
| | | } |
| | | |
| | | const handleSubmit = async () => { |
| | | if (submitting.value) return |
| | | if (!validate()) return |
| | | submitting.value = true |
| | | try { |
| | | // å¤çç»æé»è®¤æ¥åº |
| | | if (!form.dealResult) form.dealResult = getScrapDealResultValue() |
| | | const payload = { ...form } |
| | | delete payload.inspectState |
| | | const api = isEdit.value ? qualityUnqualifiedUpdate : qualityUnqualifiedAdd |
| | | await api(payload) |
| | | toast('æäº¤æå') |
| | | setTimeout(() => uni.navigateBack(), 400) |
| | | } catch (e) { |
| | | toast('æäº¤å¤±è´¥') |
| | | } finally { |
| | | submitting.value = false |
| | | } |
| | | } |
| | | |
| | | const goBack = () => uni.navigateBack() |
| | | |
| | | onLoad(async (options) => { |
| | | pageType.value = options?.type || 'add' |
| | | id.value = options?.id || '' |
| | | form.dealResult = getScrapDealResultValue() |
| | | await loadUsers() |
| | | if (isEdit.value) { |
| | | await loadDetail() |
| | | } |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .nonconforming-form-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 120rpx; } |
| | | .content-scroll { height: calc(100vh - 100rpx); } |
| | | .form-section { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; } |
| | | .section-title { font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; } |
| | | .form-row { margin-bottom: 24rpx; } |
| | | .form-label { display: block; font-size: 26rpx; color: #666; margin-bottom: 12rpx; } |
| | | .form-label.required:before { content: "*"; color: #f56c6c; margin-right: 6rpx; } |
| | | .selector-trigger { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; background: #f5f5f5; border-radius: 12rpx; } |
| | | .selector-trigger.disabled { opacity: 0.6; } |
| | | .selector-text { font-size: 28rpx; color: #333; } |
| | | .selector-text.placeholder { color: #999; } |
| | | .bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.04); } |
| | | .btn-submit { height: 88rpx; border-radius: 999rpx; background: #2979ff; color: #fff; font-size: 30rpx; display: flex; align-items: center; justify-content: center; } |
| | | .btn-submit.disabled { opacity: 0.6; } |
| | | |
| | | .popup { background: #fff; border-top-left-radius: 16rpx; border-top-right-radius: 16rpx; overflow: hidden; } |
| | | .popup-header { padding: 24rpx; border-bottom: 1rpx solid #eee; } |
| | | .popup-title { font-size: 30rpx; font-weight: 500; color: #333; } |
| | | .popup-list { max-height: 60vh; padding: 12rpx 0; } |
| | | .popup-item { padding: 22rpx 24rpx; border-bottom: 1rpx solid #f0f0f0; } |
| | | .popup-item-title { font-size: 28rpx; color: #333; } |
| | | .popup-item-row { display: flex; justify-content: space-between; gap: 16rpx; } |
| | | .popup-item-sub { font-size: 24rpx; color: #999; } |
| | | .no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; } |
| | | </style> |
| | | |
| | |
| | | <view class="nonconforming-management-page"> |
| | | <PageHeader title="ä¸åæ ¼å管ç" @back="goBack" /> |
| | | |
| | | <!-- æç´¢ä¸çé --> |
| | | <!-- æç´¢ä¸çéï¼æ ·å¼åç
§ä»å¨ç©æµæ¨¡åï¼ --> |
| | | <view class="search-section"> |
| | | <up-search |
| | | placeholder="请è¾å
¥äº§ååç§°æç´¢" |
| | | v-model="searchForm.productName" |
| | | @search="handleQuery" |
| | | @custom="handleQuery" |
| | | @clear="handleQuery" |
| | | :show-action="true" |
| | | action-text="æç´¢" |
| | | :animation="true" |
| | | customStyle="margin-bottom: 20rpx" |
| | | ></up-search> |
| | | <view class="search-row"> |
| | | <view class="search-input-wrap"> |
| | | <up-input v-model="searchForm.productName" placeholder="产ååç§°" clearable /> |
| | | </view> |
| | | <view class="btn-search" @click="handleQuery"> |
| | | <view class="btn-search-inner"> |
| | | <up-icon name="search" size="22" color="#fff"></up-icon> |
| | | <text>æç´¢</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <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 class="filter-item" @click="openDateRange"> |
| | | <text>{{ dateRangeLabel }}</text> |
| | | <up-icon name="calendar" 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"> |
| | | <view class="item-header"> |
| | | <text class="product-name">{{ item.productName }}</text> |
| | | <up-tag :text="getStatusText(item.inspectState)" :type="getStatusType(item.inspectState)" size="mini"></up-tag> |
| | | </view> |
| | | <view class="item-content"> |
| | | <view class="item-row"> |
| | | <text class="item-label">ç±»å«ï¼</text> |
| | | <text class="item-value">{{ getInspectTypeText(item.inspectType) }}</text> |
| | | <view class="list-section"> |
| | | <view v-if="tableData.length > 0"> |
| | | <view v-for="(item, index) in tableData" :key="item.id || index" class="card-item"> |
| | | <view class="card-click" @click="openForm('edit', item)"> |
| | | <view class="card-header"> |
| | | <view class="header-main"> |
| | | <text class="product-name">{{ item.productName }}</text> |
| | | </view> |
| | | <view class="header-sub"> |
| | | <text class="sub-title">{{ item.model || '-' }}</text> |
| | | <up-tag :text="getInspectTypeText(item.checkType ?? item.inspectType)" type="primary" size="mini" /> |
| | | </view> |
| | | </view> |
| | | <up-divider /> |
| | | <view class="card-body"> |
| | | <view class="row"><text class="l">æ£æµæ¥æ</text><text class="r">{{ item.checkTime || '-' }}</text></view> |
| | | <view class="row"><text class="l">æ¹å·</text><text class="r">{{ item.batchNo || '-' }}</text></view> |
| | | <view class="row"><text class="l">æ£éªå</text><text class="r">{{ item.checkName || '-' }}</text></view> |
| | | <view class="row"><text class="l">ä¸åæ ¼ç°è±¡</text><text class="r text-error">{{ item.defectivePhenomena || '-' }}</text></view> |
| | | <view class="row" v-if="item.inspectState == 1"><text class="l">å¤çç»æ</text><text class="r text-success">{{ getDealResultLabel(item.dealResult) || item.dealResult || '-' }}</text></view> |
| | | <view class="row" v-if="item.inspectState == 1"><text class="l">å¤ç人</text><text class="r">{{ item.dealName || '-' }}</text></view> |
| | | <view class="row" v-if="item.inspectState == 1"><text class="l">å¤çæ¥æ</text><text class="r">{{ item.dealTime || '-' }}</text></view> |
| | | </view> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">æ£æµæ¥æï¼</text> |
| | | <text class="item-value">{{ item.checkTime || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">è§æ ¼åå·ï¼</text> |
| | | <text class="item-value">{{ item.model || '-' }}</text> |
| | | </view> |
| | | <view class="item-row"> |
| | | <text class="item-label">ä¸åæ ¼ç°è±¡ï¼</text> |
| | | <text class="item-value text-error">{{ item.defectivePhenomena || '-' }}</text> |
| | | </view> |
| | | <view class="item-row" v-if="item.inspectState === 1"> |
| | | <text class="item-label">å¤çç»æï¼</text> |
| | | <text class="item-value text-success">{{ item.dealResult || '-' }}</text> |
| | | <view class="card-actions"> |
| | | <view class="btn-link btn-link-primary" v-if="item.inspectState == 0" @click.stop="openDealDialog(item)">å¤ç</view> |
| | | <view class="btn-link btn-link-plain" v-if="item.inspectState == 0" @click.stop="openForm('edit', item)">ç¼è¾</view> |
| | | <view class="btn-link btn-link-warn" @click.stop="handleDelete(item)">å é¤</view> |
| | | </view> |
| | | </view> |
| | | <view class="item-actions"> |
| | | <up-button v-if="item.inspectState === 0" type="primary" size="mini" @click.stop="openDealDialog(item)">å¤ç</up-button> |
| | | <up-button type="error" size="mini" @click.stop="handleDelete(item)">å é¤</up-button> |
| | | <view class="load-more-wrap"> |
| | | <u-loadmore :status="loadStatus" @loadmore="loadMore" /> |
| | | </view> |
| | | </view> |
| | | <view class="pagination-container"> |
| | | <up-loadmore :status="loadStatus" @loadmore="getList" /> |
| | | </view> |
| | | <view v-else class="no-data">ææ æ°æ®</view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <up-empty mode="data" text="ææ æ°æ®"></up-empty> |
| | | </view> |
| | | |
| | | <!-- ç±»åéæ©å¨ --> |
| | | <up-action-sheet |
| | | :actions="typeActions" |
| | |
| | | @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-form :model="dealForm" ref="dealFormRef" label-width="100" label-position="top"> |
| | | <view class="info-summary"> |
| | | <text class="summary-text">产åï¼{{ currentItem?.productName }}</text> |
| | | <text class="summary-text">ä¸åæ ¼ç°è±¡ï¼{{ currentItem?.defectivePhenomena }}</text> |
| | | <text class="summary-text">æ£æµæ¥æï¼{{ currentItem?.checkTime || '-' }}</text> |
| | | </view> |
| | | <up-form-item label="ä¸åæ ¼ç°è±¡" prop="defectivePhenomena" required borderBottom> |
| | | <up-textarea v-model="dealForm.defectivePhenomena" placeholder="请è¾å
¥ä¸åæ ¼ç°è±¡" count border="surround" /> |
| | | </up-form-item> |
| | | <up-form-item label="å¤çç»æ" prop="dealResult" required borderBottom> |
| | | <up-textarea v-model="dealForm.dealResult" placeholder="请è¾å
¥å¤çç»æ" count border="surround" /> |
| | | <view class="selector-trigger" @click="showDealResultSelect = true"> |
| | | <text class="selector-text" :class="{ placeholder: !dealResultLabel }">{{ dealResultLabel || 'è¯·éæ©å¤çç»æ' }}</text> |
| | | <up-icon name="arrow-down" size="14" color="#999"></up-icon> |
| | | </view> |
| | | </up-form-item> |
| | | <up-form-item label="å¤ç人" prop="dealName" required borderBottom> |
| | | <up-input v-model="dealForm.dealName" placeholder="请è¾å
¥å¤ç人" border="surround" /> |
| | | </up-form-item> |
| | | <up-form-item label="å¤çæ¥æ" prop="dealTime" required borderBottom> |
| | | <up-input |
| | |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- å¤çç»æéæ©å¨ --> |
| | | <up-action-sheet |
| | | :actions="dealResultActions" |
| | | :show="showDealResultSelect" |
| | | @close="showDealResultSelect = false" |
| | | @select="selectDealResult" |
| | | title="è¯·éæ©å¤çç»æ" |
| | | /> |
| | | |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-datetime-picker |
| | | :show="showDatePicker" |
| | |
| | | @confirm="confirmDate" |
| | | @cancel="showDatePicker = false" |
| | | ></up-datetime-picker> |
| | | |
| | | <!-- å½å
¥æ¥æèå´éæ©ï¼å¼å§ --> |
| | | <up-datetime-picker |
| | | :show="showEntryStartPicker" |
| | | v-model="entryStartValue" |
| | | mode="date" |
| | | @confirm="confirmEntryStart" |
| | | @cancel="showEntryStartPicker = false" |
| | | /> |
| | | <!-- å½å
¥æ¥æèå´éæ©ï¼ç»æ --> |
| | | <up-datetime-picker |
| | | :show="showEntryEndPicker" |
| | | v-model="entryEndValue" |
| | | mode="date" |
| | | @confirm="confirmEntryEnd" |
| | | @cancel="showEntryEndPicker = false" |
| | | /> |
| | | |
| | | <!-- å³ä¸è§æ°å¢æé® --> |
| | | <view class="fab-button" @click="openForm('add')"> |
| | | <up-icon name="plus" size="24" color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from 'vue'; |
| | | import { ref, reactive, computed } from 'vue'; |
| | | import { |
| | | qualityUnqualifiedListPage, |
| | | qualityUnqualifiedDeal, |
| | | qualityUnqualifiedDel |
| | | } from '@/api/qualityManagement/nonconformingManagement.js'; |
| | | import { toast, showConfirm } from '@/utils/common'; |
| | | import { useDict } from '@/utils/dict' |
| | | import dayjs from 'dayjs'; |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import { onReachBottom, onShow } from '@dcloudio/uni-app' |
| | | |
| | | const searchForm = reactive({ |
| | | productName: '', |
| | | inspectType: '', |
| | | inspectState: '' |
| | | checkType: '', |
| | | entryDateStart: undefined, |
| | | entryDateEnd: undefined |
| | | }); |
| | | |
| | | const tableData = ref([]); |
| | |
| | | { name: 'åºåæ£', value: '2' } |
| | | ]; |
| | | const typeLabel = computed(() => { |
| | | const action = typeActions.find(a => a.value === searchForm.inspectType); |
| | | const action = typeActions.find(a => a.value === String(searchForm.checkType ?? '')); |
| | | return action ? action.name : 'å
¨é¨ç±»å«'; |
| | | }); |
| | | |
| | | const showStatusSelect = ref(false); |
| | | const statusActions = [ |
| | | { name: 'å
¨é¨', value: '' }, |
| | | { name: 'å¾
å¤ç', value: '0' }, |
| | | { name: 'å·²å¤ç', value: '1' } |
| | | ]; |
| | | const statusLabel = computed(() => { |
| | | const action = statusActions.find(a => a.value === searchForm.inspectState); |
| | | return action ? action.name : 'å
¨é¨ç¶æ'; |
| | | }); |
| | | const dateRangeLabel = computed(() => { |
| | | if (searchForm.entryDateStart && searchForm.entryDateEnd) return `${searchForm.entryDateStart}~${searchForm.entryDateEnd}` |
| | | if (searchForm.entryDateStart) return `${searchForm.entryDateStart}~` |
| | | if (searchForm.entryDateEnd) return `~${searchForm.entryDateEnd}` |
| | | return 'æ£æµæ¥æ' |
| | | }) |
| | | |
| | | const dealDialogVisible = ref(false); |
| | | const submitLoading = ref(false); |
| | | const currentItem = ref(null); |
| | | const dealForm = reactive({ |
| | | id: null, |
| | | defectivePhenomena: '', |
| | | dealResult: '', |
| | | dealName: '', |
| | | dealTime: dayjs().format('YYYY-MM-DD') |
| | | }); |
| | | |
| | | const { rejection_handling } = useDict('rejection_handling') |
| | | const showDealResultSelect = ref(false) |
| | | const dealResultActions = computed(() => { |
| | | const list = rejection_handling?.value || [] |
| | | return (list || []).map(it => ({ name: it.label, value: it.value })) |
| | | }) |
| | | const dealResultLabel = computed(() => { |
| | | const list = rejection_handling?.value || [] |
| | | const v = dealForm.dealResult |
| | | return (list || []).find(it => String(it.value) === String(v))?.label || '' |
| | | }) |
| | | function getDealResultLabel(value) { |
| | | const list = rejection_handling?.value || [] |
| | | return (list || []).find(it => String(it.value) === String(value))?.label || '' |
| | | } |
| | | |
| | | const showDatePicker = ref(false); |
| | | const dateValue = ref(Number(new Date())); |
| | | |
| | | const showEntryStartPicker = ref(false) |
| | | const showEntryEndPicker = ref(false) |
| | | const entryStartValue = ref(Date.now()) |
| | | const entryEndValue = ref(Date.now()) |
| | | |
| | | const getInspectTypeText = (type) => { |
| | | const types = { '0': 'å
¥åæ£', '1': 'è½¦é´æ£', '2': 'åºåæ£' }; |
| | | return types[type] || '-'; |
| | | }; |
| | | |
| | | const getStatusText = (state) => { |
| | | return state === 1 ? 'å·²å¤ç' : 'å¾
å¤ç'; |
| | | }; |
| | | |
| | | const getStatusType = (state) => { |
| | | return state === 1 ? 'success' : 'warning'; |
| | | return types[String(type ?? '')] || '-'; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (loadStatus.value === 'loading' || (page.total > 0 && tableData.value.length >= page.total)) return; |
| | | |
| | | loadStatus.value = 'loading'; |
| | | const isFirstPage = page.current === 1 |
| | | if (loadStatus.value === 'loading' || (!isFirstPage && page.total > 0 && tableData.value.length >= page.total)) return |
| | | |
| | | loadStatus.value = 'loading' |
| | | const params = { |
| | | productName: searchForm.productName || null, |
| | | inspectType: searchForm.inspectType || null, |
| | | inspectState: searchForm.inspectState || null, |
| | | checkType: searchForm.checkType === '' ? null : searchForm.checkType, |
| | | entryDateStart: searchForm.entryDateStart, |
| | | entryDateEnd: searchForm.entryDateEnd, |
| | | current: page.current, |
| | | size: page.size |
| | | }; |
| | |
| | | loadStatus.value = 'nomore'; |
| | | } else { |
| | | loadStatus.value = 'loadmore'; |
| | | page.current++; |
| | | } |
| | | }).catch(() => { |
| | | loadStatus.value = 'loadmore'; |
| | | loadStatus.value = 'error'; |
| | | }); |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (loadStatus.value === 'nomore' || loadStatus.value === 'loading') return |
| | | loadStatus.value = 'loading' |
| | | page.current++ |
| | | getList() |
| | | } |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | |
| | | }; |
| | | |
| | | const selectType = (e) => { |
| | | searchForm.inspectType = e.value; |
| | | searchForm.checkType = e.value; |
| | | handleQuery(); |
| | | }; |
| | | |
| | | const selectStatus = (e) => { |
| | | searchForm.inspectState = e.value; |
| | | handleQuery(); |
| | | }; |
| | | const openDateRange = () => { |
| | | entryStartValue.value = searchForm.entryDateStart ? dayjs(searchForm.entryDateStart, 'YYYY-MM-DD').valueOf() : Date.now() |
| | | showEntryStartPicker.value = true |
| | | } |
| | | const confirmEntryStart = (e) => { |
| | | const ts = e?.value ?? entryStartValue.value |
| | | searchForm.entryDateStart = dayjs(ts).format('YYYY-MM-DD') |
| | | showEntryStartPicker.value = false |
| | | entryEndValue.value = searchForm.entryDateEnd ? dayjs(searchForm.entryDateEnd, 'YYYY-MM-DD').valueOf() : Date.now() |
| | | showEntryEndPicker.value = true |
| | | } |
| | | const confirmEntryEnd = (e) => { |
| | | const ts = e?.value ?? entryEndValue.value |
| | | searchForm.entryDateEnd = dayjs(ts).format('YYYY-MM-DD') |
| | | showEntryEndPicker.value = false |
| | | handleQuery() |
| | | } |
| | | |
| | | const openDealDialog = (item) => { |
| | | currentItem.value = item; |
| | | dealForm.id = item.id; |
| | | dealForm.dealResult = ''; |
| | | dealForm.defectivePhenomena = item.defectivePhenomena || '' |
| | | dealForm.dealResult = item.dealResult || ''; |
| | | dealForm.dealName = item.dealName || '' |
| | | dealForm.dealTime = dayjs().format('YYYY-MM-DD'); |
| | | dealDialogVisible.value = true; |
| | | }; |
| | | |
| | | const selectDealResult = (e) => { |
| | | dealForm.dealResult = e.value |
| | | showDealResultSelect.value = false |
| | | } |
| | | |
| | | const submitDeal = () => { |
| | | if (!dealForm.dealResult) { |
| | | toast('请è¾å
¥å¤çç»æ'); |
| | | if (!dealForm.defectivePhenomena) { |
| | | toast('请è¾å
¥ä¸åæ ¼ç°è±¡') |
| | | return; |
| | | } |
| | | if (!dealForm.dealResult) { |
| | | toast('è¯·éæ©å¤çç»æ') |
| | | return |
| | | } |
| | | if (!dealForm.dealName) { |
| | | toast('请è¾å
¥å¤ç人') |
| | | return |
| | | } |
| | | submitLoading.value = true; |
| | | qualityUnqualifiedDeal(dealForm).then(() => { |
| | |
| | | showDatePicker.value = false; |
| | | }; |
| | | |
| | | const openForm = (type, row) => { |
| | | if (type !== 'add' && row?.inspectState == 1) { |
| | | toast('å·²å¤ççæ°æ®ä¸è½åç¼è¾') |
| | | return |
| | | } |
| | | const id = row?.id |
| | | uni.navigateTo({ |
| | | url: `/pages/qualityManagement/nonconformingManagement/form?type=${type}${id ? `&id=${id}` : ''}` |
| | | }) |
| | | } |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | handleQuery(); |
| | | }); |
| | | onShow(() => { |
| | | handleQuery() |
| | | }) |
| | | |
| | | onReachBottom(() => { |
| | | loadMore() |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .nonconforming-management-page { |
| | | padding-bottom: 20rpx; |
| | | background-color: #f5f7fa; |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | padding-bottom: 120rpx; |
| | | } |
| | | |
| | | .search-section { |
| | | padding: 20rpx 30rpx; |
| | | background-color: #ffffff; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 10; |
| | | background: #fff; |
| | | margin: 24rpx; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | } |
| | | |
| | | .search-row { display: flex; align-items: center; margin-bottom: 20rpx; } |
| | | .search-input-wrap { flex: 1; margin-right: 20rpx; min-width: 0; } |
| | | .btn-search { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 180rpx; |
| | | min-height: 72rpx; |
| | | flex-shrink: 0; |
| | | padding: 20rpx 24rpx; |
| | | background: #2979ff; |
| | | color: #fff; |
| | | border-radius: 12rpx; |
| | | font-size: 28rpx; |
| | | box-sizing: border-box; |
| | | text-align: center; |
| | | } |
| | | .btn-search-inner { display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 8rpx; } |
| | | |
| | | .filter-row { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | padding: 10rpx 0; |
| | | gap: 20rpx; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | |
| | | .product-name { |
| | | 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: 180rpx; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .item-value { |
| | | flex: 1; |
| | | color: #303133; |
| | | font-size: 28rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 18rpx 20rpx; |
| | | background: #f5f5f5; |
| | | border-radius: 12rpx; |
| | | font-size: 26rpx; |
| | | color: #666; |
| | | } |
| | | |
| | | .list-section { padding: 0 24rpx; } |
| | | .card-item { |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | padding: 16rpx 20rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06); |
| | | } |
| | | .card-header { padding: 2rpx 0 6rpx; } |
| | | .header-main { display: flex; justify-content: space-between; align-items: center; gap: 16rpx; } |
| | | .product-name { font-size: 30rpx; font-weight: 500; color: #333; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| | | .header-sub { display: flex; justify-content: space-between; gap: 16rpx; margin-top: 6rpx; } |
| | | .sub-title { font-size: 24rpx; color: #999; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
| | | .sub-right { font-size: 24rpx; color: #999; flex-shrink: 0; } |
| | | .card-body .row { display: flex; justify-content: space-between; padding: 6rpx 0; font-size: 26rpx; } |
| | | .card-body .l { color: #666; width: 180rpx; flex-shrink: 0; } |
| | | .card-body .r { color: #333; flex: 1; text-align: right; word-break: break-all; } |
| | | .card-actions { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | justify-content: flex-end; |
| | | align-items: center; |
| | | margin-top: 12rpx; |
| | | padding-top: 14rpx; |
| | | border-top: 1rpx solid #eee; |
| | | } |
| | | .btn-link { |
| | | font-size: 28rpx; |
| | | padding: 10rpx 22rpx; |
| | | border-radius: 999rpx; |
| | | border: 1rpx solid transparent; |
| | | } |
| | | .btn-link-primary { color: #2979ff; border-color: rgba(41, 121, 255, 0.4); background: rgba(41, 121, 255, 0.08); } |
| | | .btn-link-plain { color: #606266; border-color: rgba(96, 98, 102, 0.35); background: rgba(96, 98, 102, 0.06); } |
| | | .btn-link-warn { color: #f56c6c; border-color: rgba(245, 108, 108, 0.55); background: rgba(245, 108, 108, 0.08); } |
| | | |
| | | .text-error { |
| | | color: #f56c6c; |
| | |
| | | color: #67c23a; |
| | | } |
| | | |
| | | .item-actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 20rpx; |
| | | border-top: 1rpx solid #ebeef5; |
| | | padding-top: 20rpx; |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | .no-data { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; } |
| | | .load-more-wrap { padding: 24rpx 24rpx 8rpx; } |
| | | |
| | | .dialog-content { |
| | | width: 650rpx; |
| | |
| | | .dialog-footer { |
| | | margin-top: 40rpx; |
| | | } |
| | | |
| | | .selector-trigger { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; background: #f5f5f5; border-radius: 12rpx; } |
| | | .selector-text { font-size: 28rpx; color: #333; } |
| | | .selector-text.placeholder { color: #999; } |
| | | |
| | | .fab-button { |
| | | position: fixed; |
| | | right: 36rpx; |
| | | bottom: 72rpx; |
| | | width: 104rpx; |
| | | height: 104rpx; |
| | | border-radius: 52rpx; |
| | | background: #2979ff; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | box-shadow: 0 10rpx 26rpx rgba(41, 121, 255, 0.35); |
| | | z-index: 20; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="file-page"> |
| | | <PageHeader title="é件管ç" @back="goBack" /> |
| | | |
| | | <view class="file-list"> |
| | | <view v-if="files.length > 0"> |
| | | <view v-for="(f, idx) in files" :key="f.id || idx" class="file-item"> |
| | | <view class="file-info"> |
| | | <text class="file-name">{{ f.name }}</text> |
| | | </view> |
| | | <view class="file-actions"> |
| | | <view class="btn-link" @click="previewFile(f)">é¢è§</view> |
| | | <view class="btn-link danger" @click="confirmDelete(f)">å é¤</view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="empty">ææ éä»¶</view> |
| | | </view> |
| | | |
| | | <view class="upload-bar"> |
| | | <view class="btn-upload" @click="chooseFile">ä¸ä¼ éä»¶</view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue' |
| | | import { onLoad, onShow } from '@dcloudio/uni-app' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import config from '@/config' |
| | | import { getToken } from '@/utils/auth' |
| | | import { qualityInspectFileAdd, qualityInspectFileDel, qualityInspectFileListPage } from '@/api/qualityManagement/qualityInspectFile.js' |
| | | |
| | | const inspectId = ref('') |
| | | const files = ref([]) |
| | | |
| | | const getList = () => { |
| | | if (!inspectId.value) return |
| | | qualityInspectFileListPage({ inspectId: inspectId.value, current: 1, size: 200 }) |
| | | .then(res => { |
| | | files.value = res?.data?.records || [] |
| | | }) |
| | | .catch(() => { files.value = [] }) |
| | | } |
| | | |
| | | const chooseFile = () => { |
| | | if (!inspectId.value) { |
| | | uni.showToast({ title: 'ç¼ºå°æ£éªè®°å½ID', icon: 'none' }) |
| | | return |
| | | } |
| | | uni.chooseFile({ |
| | | count: 1, |
| | | success: (res) => { |
| | | const path = res?.tempFiles?.[0]?.path |
| | | const name = res?.tempFiles?.[0]?.name |
| | | if (!path) return |
| | | uploadOne(path, name) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const uploadOne = (filePath, originalName) => { |
| | | const token = getToken() |
| | | if (!token) { |
| | | uni.showToast({ title: 'æªç»å½', icon: 'none' }) |
| | | return |
| | | } |
| | | uni.showLoading({ title: 'ä¸ä¼ ä¸...', mask: true }) |
| | | uni.uploadFile({ |
| | | url: config.baseUrl + '/file/upload', |
| | | filePath, |
| | | name: 'file', |
| | | header: { Authorization: 'Bearer ' + token }, |
| | | success: (res) => { |
| | | uni.hideLoading() |
| | | try { |
| | | const resp = JSON.parse(res.data || '{}') |
| | | if (resp.code !== 200) throw new Error('upload fail') |
| | | const fileRow = { |
| | | inspectId: inspectId.value, |
| | | name: resp.data?.originalName || originalName || 'éä»¶', |
| | | url: resp.data?.tempPath |
| | | } |
| | | qualityInspectFileAdd(fileRow).then(() => { |
| | | uni.showToast({ title: 'ä¸ä¼ æå', icon: 'success' }) |
| | | getList() |
| | | }) |
| | | } catch (e) { |
| | | uni.showToast({ title: 'ä¸ä¼ 失败', icon: 'none' }) |
| | | } |
| | | }, |
| | | fail: () => { |
| | | uni.hideLoading() |
| | | uni.showToast({ title: 'ä¸ä¼ 失败', icon: 'none' }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | const previewFile = (f) => { |
| | | const url = f?.url |
| | | if (!url) return |
| | | // H5/APP ç»ä¸ç¨å¤é¨æå¼ |
| | | uni.navigateTo({ |
| | | url: `/pages/inspectionUpload/filePreview?url=${encodeURIComponent(url)}` |
| | | }) |
| | | } |
| | | |
| | | const confirmDelete = (f) => { |
| | | if (!f?.id) return |
| | | uni.showModal({ |
| | | title: 'å é¤', |
| | | content: '确认å é¤è¯¥éä»¶ï¼', |
| | | success: (r) => { |
| | | if (!r.confirm) return |
| | | qualityInspectFileDel([f.id]).then(() => { |
| | | uni.showToast({ title: 'å 餿å', icon: 'success' }) |
| | | getList() |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | onLoad((options) => { |
| | | inspectId.value = options?.id || '' |
| | | if (!inspectId.value) { |
| | | const cached = uni.getStorageSync('rawMaterialFilesCtx') |
| | | if (cached) { |
| | | try { |
| | | const payload = typeof cached === 'string' ? JSON.parse(cached) : cached |
| | | inspectId.value = payload?.id || '' |
| | | uni.removeStorageSync('rawMaterialFilesCtx') |
| | | } catch (e) { |
| | | uni.removeStorageSync('rawMaterialFilesCtx') |
| | | } |
| | | } |
| | | } |
| | | }) |
| | | |
| | | onShow(() => { |
| | | getList() |
| | | }) |
| | | |
| | | const goBack = () => uni.navigateBack() |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .file-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 120rpx; } |
| | | .file-list { margin: 24rpx; background: #fff; border-radius: 16rpx; padding: 12rpx 24rpx; } |
| | | .file-item { padding: 20rpx 0; border-bottom: 1rpx solid #eee; display: flex; justify-content: space-between; align-items: center; gap: 16rpx; } |
| | | .file-item:last-child { border-bottom: 0; } |
| | | .file-name { font-size: 28rpx; color: #333; } |
| | | .file-actions { display: flex; gap: 20rpx; } |
| | | .btn-link { color: #2979ff; font-size: 26rpx; } |
| | | .btn-link.danger { color: #f56c6c; } |
| | | .empty { text-align: center; padding: 60rpx 0; color: #999; font-size: 28rpx; } |
| | | .upload-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.04); } |
| | | .btn-upload { height: 88rpx; border-radius: 999rpx; background: #2979ff; color: #fff; font-size: 30rpx; display: flex; align-items: center; justify-content: center; } |
| | | </style> |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="rm-form-page"> |
| | | <PageHeader :title="operationType === 'add' ? 'æ°å¢åææ£' : 'ç¼è¾åææ£'" @back="goBack" /> |
| | | |
| | | <scroll-view scroll-y class="content-scroll"> |
| | | <view class="section-card"> |
| | | <view class="section-title">åºç¡ä¿¡æ¯</view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label required">产ååç§°</text> |
| | | <view class="selector-trigger" @click="openProductSelector" :class="{ disabled: operationType === 'edit' }"> |
| | | <text class="selector-text" :class="{ placeholder: !form.productName }"> |
| | | {{ form.productName || 'è¯·éæ©äº§å' }} |
| | | </text> |
| | | <up-icon name="arrow-right" size="16" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label required">è§æ ¼åå·</text> |
| | | <up-input v-model="form.model" disabled placeholder="è¯·éæ©äº§ååèªå¨å¸¦åº" /> |
| | | </view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label">åä½</text> |
| | | <up-input v-model="form.unit" disabled placeholder="èªå¨å¸¦åº" /> |
| | | </view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ¹å·</text> |
| | | <up-input v-model="form.batchNo" placeholder="请è¾å
¥æ¹å·" /> |
| | | </view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ£éªç±»å</text> |
| | | <view class="selector-trigger" @click="showCheckTypeSheet = true"> |
| | | <text class="selector-text" :class="{ placeholder: form.checkType === '' || form.checkType == null }"> |
| | | {{ checkTypeLabel }} |
| | | </text> |
| | | <up-icon name="arrow-down" size="14" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ£æµç»æ</text> |
| | | <view class="selector-trigger" @click="showCheckResultSheet = true"> |
| | | <text class="selector-text" :class="{ placeholder: form.checkResult === '' || form.checkResult == null }"> |
| | | {{ checkResultLabel }} |
| | | </text> |
| | | <up-icon name="arrow-down" size="14" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ£éªå</text> |
| | | <up-input v-model="form.checkUserName" placeholder="请è¾å
¥æ£éªå" /> |
| | | </view> |
| | | |
| | | <view class="form-row"> |
| | | <text class="form-label required">æ£æµæ¥æ</text> |
| | | <view class="selector-trigger" @click="openCheckDatePicker"> |
| | | <text class="selector-text" :class="{ placeholder: !form.checkTime }"> |
| | | {{ form.checkTime || 'è¯·éæ©æ£æµæ¥æ' }} |
| | | </text> |
| | | <up-icon name="calendar" size="16" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="section-card"> |
| | | <view class="section-title row-between"> |
| | | <text>æ£æµé¡¹ç®</text> |
| | | <view class="btn-inline" @click="openItemSelector">æ·»å æ£æµé¡¹ç®</view> |
| | | </view> |
| | | |
| | | <view v-if="inspectItems.length > 0"> |
| | | <view v-for="(it, idx) in inspectItems" :key="it.id || idx" class="item-card"> |
| | | <view class="item-head"> |
| | | <text class="item-name">{{ it.name }}</text> |
| | | <view class="item-del" @click="removeItem(it.id)">å é¤</view> |
| | | </view> |
| | | <view class="item-row"><text class="l">åä½</text><text class="r">{{ it.unit || '-' }}</text></view> |
| | | <view class="item-row"><text class="l">æ åå¼</text><text class="r">{{ it.standardValue || '-' }}</text></view> |
| | | <view class="item-row"><text class="l">å
æ§å¼</text><text class="r">{{ it.internalControl || '-' }}</text></view> |
| | | <view class="item-row input-row"> |
| | | <text class="l">åéªå¼</text> |
| | | <up-input v-model="it.testValue" placeholder="请è¾å
¥" class="test-value-input" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="no-data">è¯·æ·»å æ£æµé¡¹ç®</view> |
| | | </view> |
| | | </scroll-view> |
| | | |
| | | <view class="bottom-bar"> |
| | | <view class="btn-submit" :class="{ disabled: submitLoading }" @click="handleSubmit"> |
| | | {{ submitLoading ? 'æäº¤ä¸...' : 'ä¿å' }} |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 产å鿩弹çªï¼å¤ç¨ pageModel æ¥å£ï¼ --> |
| | | <up-popup :show="showProductPopup" mode="bottom" @close="showProductPopup = false"> |
| | | <view class="popup-wrap"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">éæ©äº§å</text> |
| | | </view> |
| | | <view class="popup-search"> |
| | | <up-input v-model="productQuery.productName" placeholder="产å大类" clearable /> |
| | | <up-input v-model="productQuery.model" placeholder="åå·åç§°" clearable /> |
| | | <view class="popup-search-btn" @click="loadProductList">æç´¢</view> |
| | | </view> |
| | | <scroll-view scroll-y class="popup-list"> |
| | | <view v-for="row in productList" :key="row.id" class="popup-item" @click="selectProduct(row)"> |
| | | <view class="popup-item-top"> |
| | | <text class="popup-item-name">{{ row.productName }}</text> |
| | | <text class="popup-item-unit">{{ row.unit }}</text> |
| | | </view> |
| | | <view class="popup-item-sub">åå·ï¼{{ row.model }}</view> |
| | | </view> |
| | | <view v-if="!productLoading && productList.length === 0" class="no-data">ææ æ°æ®</view> |
| | | </scroll-view> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- æ£æµé¡¹ç®éæ©å¼¹çªï¼ç®åï¼ä»æ£æµé¡¹ç»´æ¤è¡¨ééï¼ --> |
| | | <up-popup :show="showItemPopup" mode="bottom" @close="showItemPopup = false"> |
| | | <view class="popup-wrap"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">éæ©æ£æµé¡¹ç®</text> |
| | | </view> |
| | | <view class="popup-search"> |
| | | <up-input v-model="itemQuery.name" placeholder="æ£æµé¡¹ç®åç§°" clearable /> |
| | | <view class="popup-search-btn" @click="loadItemList">æç´¢</view> |
| | | </view> |
| | | <scroll-view scroll-y class="popup-list"> |
| | | <view |
| | | v-for="row in itemList" |
| | | :key="row.id" |
| | | class="popup-item" |
| | | :class="{ selected: isItemSelected(row.id) }" |
| | | @click="toggleItem(row)" |
| | | > |
| | | <view class="popup-item-top"> |
| | | <text class="popup-item-name">{{ row.name }}</text> |
| | | <view class="right-wrap"> |
| | | <text class="popup-item-unit">{{ row.unit }}</text> |
| | | <up-icon |
| | | v-if="isItemSelected(row.id)" |
| | | name="checkbox-mark" |
| | | size="18" |
| | | color="#2979ff" |
| | | ></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="popup-item-sub">æ åå¼ï¼{{ row.standardValue || '-' }}ï½å
æ§å¼ï¼{{ row.internalControl || '-' }}</view> |
| | | </view> |
| | | <view v-if="!itemLoading && itemList.length === 0" class="no-data">ææ æ°æ®</view> |
| | | </scroll-view> |
| | | <view class="popup-footer"> |
| | | <view class="btn-cancel" @click="showItemPopup = false">åæ¶</view> |
| | | <view class="btn-ok" @click="confirmItems">ç¡®å®</view> |
| | | </view> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- éæ©å¨ï¼æ£éªç±»å/ç»æ --> |
| | | <up-action-sheet :actions="checkTypeActions" :show="showCheckTypeSheet" @close="showCheckTypeSheet = false" @select="onSelectCheckType" title="æ£éªç±»å" /> |
| | | <up-action-sheet :actions="checkResultActions" :show="showCheckResultSheet" @close="showCheckResultSheet = false" @select="onSelectCheckResult" title="æ£æµç»æ" /> |
| | | |
| | | <!-- æ¥æéæ© --> |
| | | <up-popup :show="showCheckDatePicker" mode="bottom" @close="showCheckDatePicker = false"> |
| | | <up-datetime-picker :show="true" v-model="checkDateValue" mode="date" @confirm="onCheckDateConfirm" @cancel="showCheckDatePicker = false" /> |
| | | </up-popup> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, reactive, ref } from 'vue' |
| | | import { onLoad } from '@dcloudio/uni-app' |
| | | import dayjs from 'dayjs' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import { productModelList } from '@/api/basicData/productModel.js' |
| | | import { qualityInspectItemListPage } from '@/api/qualityManagement/inspectItem.js' |
| | | import { createRawMaterial, findRawMaterialDetail, updateRawMaterial } from '@/api/qualityManagement/rawMaterial.js' |
| | | |
| | | const userStore = useUserStore() |
| | | |
| | | const operationType = ref('add') |
| | | const submitLoading = ref(false) |
| | | |
| | | const form = reactive({ |
| | | id: null, |
| | | productId: '', |
| | | productModelId: '', |
| | | productName: '', |
| | | model: '', |
| | | unit: '', |
| | | batchNo: '', |
| | | checkType: '', |
| | | checkResult: '', |
| | | checkUserName: userStore?.nickName || '', |
| | | checkTime: '' |
| | | }) |
| | | |
| | | const inspectItems = ref([]) // qualityInspectItem |
| | | |
| | | // æ£éªç±»å/ç»æ |
| | | const checkTypeActions = [ |
| | | { name: 'å
¥åæ£', value: 0 }, |
| | | { name: 'è½¦é´æ£', value: 1 }, |
| | | { name: 'åºåæ£', value: 2 } |
| | | ] |
| | | const checkResultActions = [ |
| | | { name: 'åæ ¼', value: 1 }, |
| | | { name: 'ä¸åæ ¼', value: 0 } |
| | | ] |
| | | const showCheckTypeSheet = ref(false) |
| | | const showCheckResultSheet = ref(false) |
| | | const checkTypeLabel = computed(() => checkTypeActions.find(a => a.value === form.checkType)?.name || 'è¯·éæ©') |
| | | const checkResultLabel = computed(() => checkResultActions.find(a => a.value === form.checkResult)?.name || 'è¯·éæ©') |
| | | const onSelectCheckType = (e) => { form.checkType = e.value; showCheckTypeSheet.value = false } |
| | | const onSelectCheckResult = (e) => { form.checkResult = e.value; showCheckResultSheet.value = false } |
| | | |
| | | // æ¥æéæ© |
| | | const showCheckDatePicker = ref(false) |
| | | const checkDateValue = ref(Date.now()) |
| | | const openCheckDatePicker = () => { |
| | | checkDateValue.value = form.checkTime ? dayjs(form.checkTime, 'YYYY-MM-DD').valueOf() : Date.now() |
| | | showCheckDatePicker.value = true |
| | | } |
| | | const onCheckDateConfirm = (e) => { |
| | | form.checkTime = dayjs(e.value).format('YYYY-MM-DD') |
| | | showCheckDatePicker.value = false |
| | | } |
| | | |
| | | // 产åéæ© |
| | | const showProductPopup = ref(false) |
| | | const productQuery = reactive({ productName: '', model: '' }) |
| | | const productList = ref([]) |
| | | const productLoading = ref(false) |
| | | const openProductSelector = () => { |
| | | if (operationType.value === 'edit') return |
| | | showProductPopup.value = true |
| | | if (productList.value.length === 0) loadProductList() |
| | | } |
| | | const loadProductList = () => { |
| | | productLoading.value = true |
| | | productModelList({ productName: productQuery.productName || '', model: productQuery.model || '', current: 1, size: 20 }) |
| | | .then(res => { |
| | | const data = res?.records || res?.data?.records || [] |
| | | productList.value = Array.isArray(data) ? data : [] |
| | | }) |
| | | .finally(() => { productLoading.value = false }) |
| | | } |
| | | const selectProduct = (row) => { |
| | | form.productId = row.productId |
| | | form.productModelId = row.id |
| | | form.productName = row.productName |
| | | form.model = row.model |
| | | form.unit = row.unit |
| | | showProductPopup.value = false |
| | | } |
| | | |
| | | // æ£æµé¡¹ç®éæ© |
| | | const showItemPopup = ref(false) |
| | | const itemQuery = reactive({ name: '' }) |
| | | const itemList = ref([]) |
| | | const itemLoading = ref(false) |
| | | const selectedItemIds = ref(new Set()) |
| | | const isItemSelected = (id) => selectedItemIds.value.has(id) |
| | | const openItemSelector = () => { |
| | | showItemPopup.value = true |
| | | selectedItemIds.value = new Set(inspectItems.value.map(i => i.id)) |
| | | if (itemList.value.length === 0) loadItemList() |
| | | } |
| | | const loadItemList = () => { |
| | | itemLoading.value = true |
| | | qualityInspectItemListPage({ name: itemQuery.name || null, current: 1, size: 50 }) |
| | | .then(res => { |
| | | const records = res?.data?.records || [] |
| | | itemList.value = Array.isArray(records) ? records : [] |
| | | }) |
| | | .finally(() => { itemLoading.value = false }) |
| | | } |
| | | const toggleItem = (row) => { |
| | | const set = selectedItemIds.value |
| | | if (set.has(row.id)) set.delete(row.id) |
| | | else set.add(row.id) |
| | | // ç®åæç¤ºéä¸ç¶æï¼å¯æéå iconï¼ |
| | | } |
| | | const confirmItems = () => { |
| | | const set = selectedItemIds.value |
| | | const existingMap = new Map(inspectItems.value.map(i => [i.id, i])) |
| | | const next = [] |
| | | for (const id of set) { |
| | | const exist = existingMap.get(id) |
| | | if (exist) next.push(exist) |
| | | else { |
| | | const row = itemList.value.find(r => r.id === id) |
| | | if (row) next.push({ ...row, testValue: '' }) |
| | | } |
| | | } |
| | | inspectItems.value = next |
| | | showItemPopup.value = false |
| | | } |
| | | const removeItem = (id) => { |
| | | inspectItems.value = inspectItems.value.filter(i => i.id !== id) |
| | | } |
| | | |
| | | const validate = () => { |
| | | if (!form.productModelId) return 'è¯·éæ©äº§å' |
| | | if (!form.batchNo) return '请è¾å
¥æ¹å·' |
| | | if (form.checkType === '' || form.checkType == null) return 'è¯·éæ©æ£éªç±»å' |
| | | if (form.checkResult === '' || form.checkResult == null) return 'è¯·éæ©æ£æµç»æ' |
| | | if (!form.checkUserName) return '请è¾å
¥æ£éªå' |
| | | if (!form.checkTime) return 'è¯·éæ©æ£æµæ¥æ' |
| | | if (!inspectItems.value.length) return 'è¯·æ·»å æ£æµé¡¹ç®' |
| | | return '' |
| | | } |
| | | |
| | | const handleSubmit = () => { |
| | | if (submitLoading.value) return |
| | | const msg = validate() |
| | | if (msg) { |
| | | uni.showToast({ title: msg, icon: 'none' }) |
| | | return |
| | | } |
| | | submitLoading.value = true |
| | | const payload = { ...form, qualityInspectItem: inspectItems.value } |
| | | const api = operationType.value === 'add' ? createRawMaterial : updateRawMaterial |
| | | api(payload) |
| | | .then(() => { |
| | | uni.showToast({ title: 'ä¿åæå', icon: 'success' }) |
| | | setTimeout(() => uni.navigateBack(), 400) |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: 'ä¿å失败', icon: 'none' }) |
| | | }) |
| | | .finally(() => { submitLoading.value = false }) |
| | | } |
| | | |
| | | onLoad((options) => { |
| | | operationType.value = options?.type || 'add' |
| | | const id = options?.id |
| | | if (operationType.value === 'edit' && id) { |
| | | findRawMaterialDetail(id).then(res => { |
| | | const d = res?.data || {} |
| | | form.id = d.id |
| | | form.productId = d.productId |
| | | form.productModelId = d.productModelId |
| | | form.productName = d.productName |
| | | form.model = d.model |
| | | form.unit = d.unit |
| | | form.batchNo = d.batchNo |
| | | form.checkType = d.checkType |
| | | form.checkResult = d.checkResult |
| | | form.checkUserName = d.checkUserName || form.checkUserName |
| | | form.checkTime = d.checkTime |
| | | inspectItems.value = Array.isArray(d.qualityInspectItem) ? d.qualityInspectItem : [] |
| | | }) |
| | | } |
| | | }) |
| | | |
| | | const goBack = () => uni.navigateBack() |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .rm-form-page { min-height: 100vh; background: #f5f5f5; padding-bottom: 110rpx; } |
| | | .content-scroll { height: calc(100vh - 110rpx); } |
| | | .section-card { background: #fff; margin: 24rpx; padding: 24rpx; border-radius: 16rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); } |
| | | .section-title { font-size: 28rpx; font-weight: 500; color: #333; margin-bottom: 12rpx; display: flex; align-items: center; } |
| | | .row-between { justify-content: space-between; } |
| | | .btn-inline { font-size: 26rpx; color: #2979ff; } |
| | | .form-row { margin-bottom: 22rpx; } |
| | | .form-label { display: block; font-size: 26rpx; color: #666; margin-bottom: 12rpx; } |
| | | .form-label.required:before { content: "*"; color: #f56c6c; margin-right: 6rpx; } |
| | | .selector-trigger { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 24rpx; background: #f5f5f5; border-radius: 12rpx; } |
| | | .selector-trigger.disabled { opacity: 0.6; } |
| | | .selector-text { font-size: 28rpx; color: #333; } |
| | | .selector-text.placeholder { color: #999; } |
| | | .no-data { text-align: center; padding: 40rpx 0; color: #999; font-size: 26rpx; } |
| | | .item-card { padding: 18rpx 0; border-top: 1rpx solid #eee; } |
| | | .item-head { display: flex; justify-content: space-between; align-items: center; padding: 6rpx 0 10rpx; } |
| | | .item-name { font-size: 28rpx; font-weight: 500; color: #333; } |
| | | .item-del { font-size: 26rpx; color: #f56c6c; } |
| | | .item-row { display: flex; justify-content: space-between; align-items: center; padding: 6rpx 0; font-size: 26rpx; } |
| | | .item-row .l { color: #666; } |
| | | .item-row .r { color: #333; } |
| | | .input-row { align-items: center; } |
| | | .test-value-input { |
| | | flex: 0 0 220rpx; |
| | | } |
| | | :deep(.test-value-input .u-input), |
| | | :deep(.test-value-input .up-input) { |
| | | width: 100%; |
| | | } |
| | | .bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); background: #fff; box-shadow: 0 -4rpx 16rpx rgba(0,0,0,0.04); } |
| | | .btn-submit { height: 88rpx; border-radius: 999rpx; background: #2979ff; color: #fff; font-size: 30rpx; display: flex; align-items: center; justify-content: center; } |
| | | .btn-submit.disabled { opacity: 0.6; } |
| | | .popup-wrap { background: #fff; border-radius: 24rpx 24rpx 0 0; padding-bottom: env(safe-area-inset-bottom); } |
| | | .popup-header { padding: 24rpx; border-bottom: 1rpx solid #eee; text-align: center; } |
| | | .popup-title { font-size: 30rpx; font-weight: 500; color: #333; } |
| | | .popup-search { padding: 16rpx 24rpx; display: grid; grid-template-columns: 1fr 1fr 140rpx; gap: 16rpx; align-items: center; } |
| | | .popup-search-btn { height: 72rpx; border-radius: 12rpx; background: #2979ff; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 28rpx; } |
| | | .popup-list { max-height: 60vh; padding: 0 24rpx 24rpx; } |
| | | .popup-item { padding: 18rpx 0; border-bottom: 1rpx solid #f0f0f0; } |
| | | .popup-item.selected { background: rgba(41, 121, 255, 0.06); } |
| | | .popup-item-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6rpx; } |
| | | .right-wrap { display: inline-flex; align-items: center; gap: 12rpx; } |
| | | .popup-item-name { font-size: 28rpx; color: #333; } |
| | | .popup-item-unit { font-size: 24rpx; color: #999; } |
| | | .popup-item-sub { font-size: 24rpx; color: #666; } |
| | | .popup-footer { display: flex; gap: 24rpx; padding: 16rpx 24rpx 24rpx; } |
| | | .btn-cancel { flex: 1; text-align: center; padding: 24rpx; background: #f0f0f0; border-radius: 12rpx; } |
| | | .btn-ok { flex: 1; text-align: center; padding: 24rpx; background: #2979ff; color: #fff; border-radius: 12rpx; } |
| | | </style> |
| | | |
| | |
| | | <view v-for="(item, index) in tableData" :key="index" class="list-item"> |
| | | <view class="item-header"> |
| | | <text class="product-name">{{ item.productName }}</text> |
| | | <up-tag :text="item.inspectState ? 'å·²æäº¤' : 'æªæäº¤'" :type="item.inspectState ? 'success' : 'warning'" size="mini"></up-tag> |
| | | <up-tag |
| | | :text="item.inspectState == 1 ? 'å·²æäº¤' : 'æªæäº¤'" |
| | | :type="item.inspectState == 1 ? 'success' : 'warning'" |
| | | size="mini" |
| | | ></up-tag> |
| | | </view> |
| | | <view class="item-content"> |
| | | <view class="item-row"> |
| | |
| | | </view> |
| | | </view> |
| | | <view class="item-actions"> |
| | | <up-button v-if="!item.inspectState" type="primary" size="mini" @click.stop="openForm('edit', item)">ç¼è¾</up-button> |
| | | <up-button v-if="!item.inspectState" type="success" size="mini" @click.stop="handleConfirmSubmit(item)">æäº¤</up-button> |
| | | <up-button v-if="item.inspectState != 1" type="primary" size="mini" @click.stop="openForm('edit', item)">ç¼è¾</up-button> |
| | | <up-button type="info" size="mini" @click.stop="openFiles(item)">éä»¶</up-button> |
| | | <up-button v-if="item.inspectState != 1" type="success" size="mini" @click.stop="handleConfirmSubmit(item)">æäº¤</up-button> |
| | | <up-button type="error" size="mini" @click.stop="handleDelete(item)">å é¤</up-button> |
| | | </view> |
| | | </view> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted, computed } from 'vue'; |
| | | import { ref, reactive, computed } from 'vue'; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import { |
| | | findRawMaterialListPage, |
| | | submitRawMaterial, |
| | |
| | | }; |
| | | |
| | | const openForm = (type, item) => { |
| | | // Mobile usually navigates to a new page for add/edit if complex |
| | | // Here we'll just show a toast for now as the actual form components are many |
| | | toast('åè½å¼åä¸ï¼è¯·å¨PC端æä½'); |
| | | const id = item?.id |
| | | uni.navigateTo({ |
| | | url: `/pages/qualityManagement/rawMaterial/form?type=${type}${id ? `&id=${id}` : ''}` |
| | | }) |
| | | }; |
| | | |
| | | const handleConfirmSubmit = (row) => { |
| | |
| | | const handleDelete = (row) => { |
| | | showConfirm('确认å é¤è¯¥è®°å½åï¼').then(res => { |
| | | if (res.confirm) { |
| | | deleteRawMaterial({ id: row.id }).then(() => { |
| | | // å¯¹é½ PC 端ï¼å 餿¥å£æ¥æ¶ id æ°ç» |
| | | deleteRawMaterial([row.id]).then(() => { |
| | | toast('å 餿å'); |
| | | handleQuery(); |
| | | }); |
| | |
| | | }); |
| | | }; |
| | | |
| | | const openFiles = (row) => { |
| | | if (!row?.id) { |
| | | toast('请å
ä¿åååä¸ä¼ éä»¶') |
| | | return |
| | | } |
| | | try { |
| | | uni.setStorageSync('rawMaterialFilesCtx', JSON.stringify({ id: row.id })) |
| | | } catch (e) {} |
| | | uni.navigateTo({ |
| | | url: `/pages/qualityManagement/rawMaterial/files?id=${row.id}` |
| | | }) |
| | | } |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | onShow(() => { |
| | | handleQuery(); |
| | | }); |
| | | </script> |
| | |
| | | .item-actions { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 20rpx; |
| | | gap: 16rpx; |
| | | border-top: 1rpx solid #ebeef5; |
| | | padding-top: 20rpx; |
| | | } |
| | | .item-actions :deep(.u-button), |
| | | .item-actions :deep(.up-button) { |
| | | min-width: 140rpx; |
| | | height: 64rpx; |
| | | line-height: 64rpx; |
| | | font-size: 26rpx; |
| | | border-radius: 999rpx; |
| | | padding: 0 24rpx; |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |