Browse Source

feat: 采购核价审批

liujt 1 week ago
parent
commit
68a182f6d3

+ 72 - 0
api/purchasingManage/inquiryManage.js

@@ -0,0 +1,72 @@
+import {
+	get,
+	putJ,
+	postJ
+} from "@/utils/request";
+import Vue from "vue";
+
+/**
+ * 获取信息列表
+ */
+export async function getTableList(params) {
+	const res = await get(Vue.prototype.apiUrl + `/eom/purchaseinquiry/page`, params);
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 获取信息详情
+ */
+export async function getpurchaseinquiry(id) {
+	const res = await get(Vue.prototype.apiUrl + `/eom/purchaseinquiry/getById/${id}`, {});
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 更新信息
+ */
+export async function UpdateInformation(data) {
+	const res = await putJ(Vue.prototype.apiUrl + `/eom/purchaseinquiry/update`, data);
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 设置中标单位
+ */
+export async function chooseWinner(data) {
+	const res = await postJ(Vue.prototype.apiUrl + `/eom/purchaseinquiry/chooseWinner`, data);
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 获取生成合同的数据
+ */
+export async function generateContract(data) {
+	const res = await postJ(Vue.prototype.apiUrl + `/eom/purchaseinquiry/generateContract`, data);
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 流程作废
+ */
+export async function cancel(data) {
+	const res = await putJ(Vue.prototype.apiUrl + `/bpm/purchaseInquiryApprove/notPass`, data);
+	if (res.code == 0) {
+		return Promise.resolve(res.data);
+	}
+	return Promise.reject(new Error(res.message));
+}

+ 38 - 0
api/purchasingManage/purchasePlanManage.js

@@ -0,0 +1,38 @@
+import {
+	get,
+	postJ
+} from "@/utils/request";
+import Vue from "vue";
+
+/**
+ * 获取信息列表
+ */
+export async function getTableList(params) {
+	const res = await get(Vue.prototype.apiUrl + `/eom/purchaseplan/page`, params);
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 获取计划详情
+ */
+export async function getplanDetail(id) {
+	const res = await get(Vue.prototype.apiUrl + `/eom/purchaseplan/getById/${id}`, {});
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 新增/保存采购计划
+ */
+export async function savePurchasePlan(data) {
+	const res = await postJ(Vue.prototype.apiUrl + `/eom/purchaseplan/save`, data);
+	if (res.code == 0) {
+		return res.data;
+	}
+	return Promise.reject(new Error(res.message));
+}

+ 4 - 0
enum/dict.js

@@ -15,6 +15,8 @@ export default {
   记录规则类型: 'record_sheet',
   表计类型: 'meter_type',
   不拆物料层规格: 'material_layer',
+  结算方式: 'settlement_mode',
+  质保期单位: 'date_unit',
 
 };
 export const numberList = [
@@ -400,3 +402,5 @@ export const businessStatus = [
   { code: 4, label: '保养' },
   { code: 5, label: '巡点检' }
 ];
+
+export const orderSourceType = ['3', '4', '5', '6', '7'];

+ 16 - 0
pages.json

@@ -236,6 +236,22 @@
 				"navigationBarTextStyle": "white"
 			}
 		},
+		{
+			"path": "pages/home/wt/components/pricingManage/inquiryManageList",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationStyle": "custom",
+				"navigationBarTextStyle": "white"
+			}
+		},
+		{
+			"path": "pages/home/wt/components/pricingManage/supplierManageList",
+			"style": {
+				"navigationBarTitleText": "",
+				"navigationStyle": "custom",
+				"navigationBarTextStyle": "white"
+			}
+		},
 		{
 			"path": "pages/home/wt/components/purchaseContract/processTask",
 			"style": {

+ 308 - 0
pages/home/wt/components/pricingManage/inquiryManageList.vue

@@ -0,0 +1,308 @@
+<template>
+	<view class="mainBox">
+		<uni-nav-bar fixed="true" statusBar="true" left-icon="back" title="选择采购计划" @clickLeft="handleClose">
+		</uni-nav-bar>
+		<view class="searchBox">
+			<input v-model="searchVal" placeholder="请输入计划编码" class="searchInput" @confirm="doSearch" />
+			<u-button type="icon-shixiangxinzeng" size="30" @click="doSearch" class="searchBtn">
+				<view class="iconfont icon-sousuo"></view>
+				<view class="text">搜索</view>
+			</u-button>
+		</view>
+		<view class="wrapper">
+			<scroll-view scroll-y class="listContent" @scrolltolower="scrolltolower">
+				<view v-for="(item, index) in listData" :key="index">
+					<view class="listBox" @click="selectItem(item, index)">
+						<view class="listBox-sel">
+							<view class="radio" :class="{ active: selectedIndex === index }">
+								<view v-if="selectedIndex === index" class="radio-dot"></view>
+							</view>
+						</view>
+						<view class="listBox-con">
+							<view class="listBox-top">
+								<view class="listBox-code">{{ item.planCode }}</view>
+								<view class="listBox-status">{{ getStatus(item.status) }}</view>
+							</view>
+							<view class="listBox-middle">
+								<view v-if="item.requirementCode">需求编码:{{ item.requirementCode }}</view>
+								<view>需求部门:{{ item.requireDeptName }}</view>
+							</view>
+							<view class="listBox-bottom">
+								<view>需求人:{{ item.requireUserName }}</view>
+								<view>负责人:{{ item.responsibleName }}</view>
+								<view>明细条数:{{ item.detailCount }}</view>
+								<view>完成日期:{{ item.finishDate || '-' }}</view>
+							</view>
+						</view>
+					</view>
+				</view>
+				<u-loadmore v-if="listData.length" :status="loadStatus" />
+				<u-empty class="noDate" style="margin-top: 20vh" v-if="!listData.length && !loading"></u-empty>
+			</scroll-view>
+		</view>
+
+		<view class="footer">
+			<u-button type="success" size="small" :disabled="selectedIndex === null" @click="confirmSelect">
+				选择
+			</u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { getTableList } from '@/api/purchasingManage/purchasePlanManage'
+
+	export default {
+		data() {
+			return {
+				page: 1,
+				size: 20,
+				isEnd: false,
+				loading: false,
+				searchVal: '',
+				listData: [],
+				selectedIndex: null,
+				loadStatus: 'loadmore',
+				initPlanCode: ''
+			}
+		},
+		onLoad(option) {
+			if (option && option.planCode) {
+				this.initPlanCode = option.planCode
+			}
+			this.getList()
+		},
+		methods: {
+			scrolltolower() {
+				if (this.isEnd || this.loading) return
+				this.page++
+				this.getList()
+			},
+			doSearch() {
+				this.page = 1
+				this.listData = []
+				this.selectedIndex = null
+				this.getList()
+			},
+			async getList() {
+				this.loading = true
+				this.loadStatus = 'loading'
+				try {
+					const result = await getTableList({
+						pageNum: this.page,
+						size: this.size,
+						status: 2,
+						planCode: this.searchVal || undefined
+					})
+					const newList = result.list || result.records || []
+					if (this.page === 1) {
+						this.listData = newList
+					} else {
+						this.listData = this.listData.concat(newList)
+					}
+					const total = result.count || result.total || 0
+					this.isEnd = this.listData.length >= total
+					this.loadStatus = this.isEnd ? 'nomore' : 'loadmore'
+
+					// 回显已选数据
+					if (this.initPlanCode) {
+						const idx = this.listData.findIndex(item => item.planCode === this.initPlanCode)
+						if (idx !== -1) this.selectedIndex = idx
+					}
+				} catch (e) {
+					console.error(e)
+					this.loadStatus = 'nomore'
+				}
+				this.loading = false
+			},
+			selectItem(item, index) {
+				this.selectedIndex = index
+			},
+			getStatus(status) {
+				const map = { 0: '未提交', 1: '审核中', 2: '审核通过', 3: '审核不通过' }
+				return map[status] || ''
+			},
+			confirmSelect() {
+				if (this.selectedIndex === null) {
+					uni.showToast({ title: '请选择一条数据', icon: 'none' })
+					return
+				}
+				const current = this.listData[this.selectedIndex]
+
+				console.log('confirmSelect~~', current)
+				// 通过事件总线传递给 taskForm
+				uni.$emit('changeInquiryManageList', current)
+				uni.navigateBack()
+			},
+			handleClose() {
+				uni.navigateBack()
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.mainBox {
+		height: 100vh;
+		display: flex;
+		flex-direction: column;
+		background: #f5f5f5;
+
+		.wrapper {
+			flex: 1;
+			overflow: hidden;
+		}
+
+		.searchBox {
+			padding: 10rpx 0;
+			box-sizing: border-box;
+			background-color: #dedede;
+			height: 90rpx;
+			width: 100%;
+			display: flex;
+			justify-content: space-around;
+			align-items: center;
+
+			input {
+				height: 70rpx;
+				width: 65%;
+				background: #f9f9f9;
+				margin: 0 10rpx;
+				padding: 0 20rpx;
+				box-sizing: border-box;
+				border-radius: 8rpx;
+				font-size: 28rpx;
+			}
+
+			.searchBtn {
+				height: 70rpx;
+				background: #f9f9f9;
+				color: #676767;
+				font-size: 28rpx;
+				padding: 0 30rpx;
+				box-sizing: border-box;
+				outline: none;
+				border: none;
+				width: 240rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+
+				.icon-sousuo {
+					font-size: 22px;
+				}
+
+				.text {
+					font-size: 26rpx;
+					margin-left: 10rpx;
+				}
+			}
+		}
+
+		.listContent {
+			height: 100%;
+			padding: 0 20rpx;
+
+			.listBox {
+				display: flex;
+				padding: 24rpx 0;
+				border-bottom: 2rpx solid #e5e5e5;
+				background: #fff;
+				margin-bottom: 2rpx;
+
+				.listBox-sel {
+					width: 80rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					flex-shrink: 0;
+
+					.radio {
+						width: 36rpx;
+						height: 36rpx;
+						border-radius: 50%;
+						border: 3rpx solid #ccc;
+						display: flex;
+						align-items: center;
+						justify-content: center;
+
+						.radio-dot {
+							width: 20rpx;
+							height: 20rpx;
+							border-radius: 50%;
+							background: #157A2C;
+						}
+
+						&.active {
+							border-color: #157A2C;
+						}
+					}
+				}
+
+				.listBox-con {
+					flex: 1;
+					padding-right: 18rpx;
+
+					.listBox-top {
+						display: flex;
+						justify-content: space-between;
+						align-items: center;
+						padding-bottom: 8rpx;
+
+						.listBox-code {
+							font-size: 28rpx;
+							font-weight: bold;
+							color: #333;
+						}
+
+						.listBox-status {
+							font-size: 22rpx;
+							color: #157A2C;
+							background: #e8f5e9;
+							padding: 4rpx 12rpx;
+							border-radius: 4rpx;
+						}
+					}
+
+					.listBox-middle {
+						font-size: 24rpx;
+						color: #666;
+						padding-bottom: 6rpx;
+						display: flex;
+						flex-wrap: wrap;
+
+						>view {
+							margin-right: 24rpx;
+						}
+					}
+
+					.listBox-bottom {
+						display: flex;
+						flex-wrap: wrap;
+						font-size: 22rpx;
+						color: #999;
+
+						>view {
+							width: 50%;
+							overflow: hidden;
+							white-space: nowrap;
+							text-overflow: ellipsis;
+							padding: 3rpx 0;
+						}
+					}
+				}
+			}
+		}
+
+		.footer {
+			height: 100rpx;
+			display: flex;
+			justify-content: flex-end;
+			align-items: center;
+			border-top: 1rpx solid #eeecec;
+			background-color: #fff;
+			padding: 0 30rpx;
+			flex-shrink: 0;
+		}
+	}
+</style>

+ 481 - 0
pages/home/wt/components/pricingManage/inquiryTable.vue

@@ -0,0 +1,481 @@
+<template>
+	<view>
+		<view class="mainBox" v-for="(supplier, sIndex) in supplierList" :key="supplier.supplierId || sIndex">
+			<!-- 供应商头部 -->
+			<view class="supplier-header">
+			<view class="supplier-name">
+				<text class="label">供应商:</text>
+				<text class="value">{{ supplier.supplierName || '-' }}</text>
+			</view>
+			<view class="supplier-summary">
+				<view class="summary-item">
+					<text class="label">总价:</text>
+					<text class="value price">{{ supplier.totalPrice || '-' }}</text>
+				</view>
+				<view class="summary-item">
+					<text class="label">优惠后:</text>
+					<text v-if="isView" class="value price">{{ supplier.preferentialPrice || '-' }}</text>
+					<u--input v-else type="number" border="surround" v-model="supplier.preferentialPrice"
+						placeholder="请输入优惠金额" style="width: 200rpx; display: inline-block;"></u--input>
+				</view>
+				<view class="summary-item">
+					<text class="label">结算方式:</text>
+					<text v-if="isView" class="value">{{ supplier.settlementModeName || '-' }}</text>
+					<uni-data-picker v-else v-model="supplier.settlementMode" placeholder="请选择"
+						:localdata="settlementModeList" @change="onSettlementChange(supplier)"></uni-data-picker>
+				</view>
+				<view class="summary-item">
+					<text class="label">交货日期:</text>
+					<text v-if="isView" class="value">{{ supplier.deliveryDate || '-' }}</text>
+					<uni-datetime-picker v-else type="date" v-model="supplier.deliveryDate"
+						@change="onDeliveryDateChange(supplier)"></uni-datetime-picker>
+				</view>
+			</view>
+			<view class="supplier-summary">
+				<view class="summary-item">
+					<text class="label">附件:</text>
+				</view>
+				<fileMain :type="isView ? 'view' : ''" :value="supplier.files"
+					@input="val => onSupplierFilesChange(sIndex, val)"></fileMain>
+			</view>
+		</view>
+
+		<!-- 报价明细 -->
+		<view v-for="(item, index) in (supplier.resultList || [])" :key="index">
+			<myCard :item="item" :btnList="btnList" :index="index + 1" :columns="columns"
+				@del="delProduct(sIndex, index)">
+				<!-- 名称 -->
+				<view slot="productName">
+					<text style="font-weight: 600;">{{ item.productName || '-' }}</text>
+				</view>
+				<!-- 是否中标 -->
+				<view slot="isWinner">
+					<u-tag v-if="isView && item.isWinner == 1" text="中标" size="mini" type="success"></u-tag>
+					<u-tag v-else-if="isView" text="未中标" size="mini" type="warning"></u-tag>
+					<uni-data-picker v-else v-model="item.isWinner" placeholder="请选择"
+						:localdata="isWinnerList"></uni-data-picker>
+				</view>
+				<!-- 数量 -->
+				<view slot="purchaseCount">
+					<view v-if="isView">{{ (item.purchaseCount || item.totalCount || '-') + ' ' + (item.purchaseUnit || item.measuringUnit || '') }}</view>
+					<view v-else>
+						<u-row>
+							<u-col span="6">
+								<u--input type="number" placeholder="数量" border="surround" v-model="item.purchaseCount"
+									@input="changeCount(supplier, sIndex, index)"></u--input>
+							</u-col>
+							<u-col span="6">
+								<uni-data-picker v-model="item.purchaseUnitId" placeholder="单位"
+									:localdata="item.packageDispositionList || []"
+                  :map="{
+                    text: 'conversionUnit',
+                    value: 'id'
+                  }"
+									@change="changeCount(supplier, sIndex, index)"></uni-data-picker>
+							</u-col>
+						</u-row>
+					</view>
+				</view>
+				<!-- 单价 -->
+				<view slot="singlePrice">
+					<text v-if="isView">{{ item.singlePrice ? item.singlePrice + ' 元' : '-' }}</text>
+					<u--input v-else type="number" placeholder="请输入单价" border="surround" v-model="item.singlePrice"
+						@input="changeCount(supplier, sIndex, index)"></u--input>
+				</view>
+				<!-- 金额 -->
+				<view slot="totalPrice">
+					<text v-if="item.totalPrice">{{ item.totalPrice }} 元</text>
+					<text v-else>-</text>
+				</view>
+				<!-- 税率(必填) -->
+				<view slot="taxRate">
+					<text v-if="isView">{{ item.taxRate || item.taxRate === 0 ? item.taxRate + '%' : '-' }}</text>
+					<u--input v-else type="number" placeholder="请输入税率" border="surround" v-model="item.taxRate"
+						@input="calcItemTotal(supplier, index)"></u--input>
+				</view>
+				<!-- 供应商产品名称 -->
+				<view slot="supplierProductName">
+					<text v-if="isView">{{ item.supplierProductName || item.supplierProductCode || '-' }}</text>
+					<u--input v-else placeholder="请输入供应商产品名称" border="surround" v-model="item.supplierProductName"></u--input>
+				</view>
+				<!-- 供应商产品编码 -->
+				<view slot="supplierProductCode">
+					<text v-if="isView">{{ item.supplierProductCode || '-' }}</text>
+					<u--input v-else placeholder="供应商产品编码" border="surround" v-model="item.supplierProductCode"></u--input>
+				</view>
+				<!-- 不含税单价 -->
+				<view slot="notaxSinglePrice">
+					<text>{{ item.notaxSinglePrice ? item.notaxSinglePrice + ' 元' : '-' }}</text>
+				</view>
+				<!-- 型号 -->
+				<view slot="modelType">
+					<text v-if="isView">{{ item.modelType || '-' }}</text>
+					<u--input v-else placeholder="型号" border="surround" v-model="item.modelType"></u--input>
+				</view>
+				<!-- 规格 -->
+				<view slot="specification">
+					<text v-if="isView">{{ item.specification || '-' }}</text>
+					<u--input v-else placeholder="规格" border="surround" v-model="item.specification"></u--input>
+				</view>
+				<!-- 产地(多选) -->
+				<view slot="provenance">
+					<text v-if="isView">{{ getProvenanceLabel(item.provenance) || '-' }}</text>
+					<u--input v-else placeholder="点击选择产地" border="surround"
+						:value="getProvenanceLabel(item.provenance)" @click.native="openProvenancePicker(sIndex, index)"></u--input>
+				</view>
+				<!-- 交期 -->
+				<view slot="deliveryDays">
+					<text v-if="isView">{{ item.deliveryDays || '-' }}</text>
+					<u--input v-else type="number" placeholder="交期(天)" border="surround" v-model="item.deliveryDays"></u--input>
+				</view>
+				<!-- 有效期 -->
+				<view slot="guaranteePeriod">
+					<text v-if="isView">{{ item.guaranteePeriod || '-' }}</text>
+					<u--input v-else type="number" placeholder="有效期" border="surround" v-model="item.guaranteePeriod"></u--input>
+				</view>
+				<!-- 有效期单位 -->
+				<view slot="guaranteePeriodUnitCode">
+					<text v-if="isView">{{ getGuaranteeUnitLabel(item.guaranteePeriodUnitCode) || '-' }}</text>
+					<uni-data-picker v-else v-model="item.guaranteePeriodUnitCode" placeholder="请选择" :localdata="guaranteeUnitList" @change="onGuaranteeUnitChange(item)"></uni-data-picker>
+				</view>
+				<!-- 备注 -->
+				<view slot="remark">
+					<text v-if="isView">{{ item.remark || '-' }}</text>
+					<u--input v-else placeholder="备注" border="surround" v-model="item.remark"></u--input>
+				</view>
+				<!-- 附件 -->
+				<view slot="files">
+					<fileMain :value="item.files || []" :type="isView ? 'view' : ''" v-if="item.files && item.files.length"></fileMain>
+					<text v-else>-</text>
+				</view>
+			</myCard>
+		</view>
+		<u-gap height="20" bgColor="#f0f0f0"></u-gap>
+		</view>
+		<ba-tree-picker ref="provenancePicker" :multiple="true" @select-change="provenanceConfirm"
+			title="选择产地" :localdata="provenanceList" valueKey="value" textKey="text" childrenKey="children" />
+	</view>
+</template>
+
+<script>
+	import myCard from '@/pages/purchasingManage/components/myCard.vue'
+	import fileMain from "@/pages/doc/index.vue"
+	import { changeCountNew as productChangeCount } from '@/utils/setProduct.js'
+  import dictMixns from "@/mixins/dictMixins";
+  import { mapGetters, mapActions } from 'vuex'
+  import baTreePicker from '@/components/ba-tree-picker/ba-tree-picker.vue'
+
+	export default {
+    mixins: [dictMixns],
+		components: {
+			myCard,
+			fileMain,
+      baTreePicker
+		},
+		props: {
+			supplierList: {
+				type: Array,
+				default: () => []
+			},
+			status: {
+				type: String,
+				default: 'edit'
+			}
+		},
+		data() {
+			return {
+				isWinnerList: [
+					{ text: '是', value: 1 },
+					{ text: '否', value: 0 }
+				],
+				settlementModeList: [],
+				provenanceList: [],
+				guaranteeUnitList: [],
+        curProvenanceSIndex: -1,
+        curProvenancePIndex: -1
+			}
+		},
+		computed: {
+      ...mapGetters(['dict', 'getDict', 'getDictValue']),
+			isView() {
+				return this.status === 'Detail'
+			},
+			btnList() {
+				return this.isView ? [] : [{
+					name: '删除',
+					apiName: 'del',
+					btnType: 'error',
+					type: '2'
+				}]
+			},
+			columns() {
+				return [
+					[{
+						label: '产品名称:', prop: 'productName', slot: 'productName',
+						type: 'title', className: 'perce100'
+					}],
+					[{ label: '编码:', prop: 'productCode' }, {
+						label: '是否中标:', prop: 'isWinner', slot: 'isWinner',
+						isRequired: !this.isView
+					}],
+					[{
+						label: '数量:', prop: 'purchaseCount', slot: 'purchaseCount',
+						isRequired: !this.isView
+					}, {
+						label: '采购单价:', prop: 'singlePrice', slot: 'singlePrice',
+						isRequired: !this.isView
+					}],
+					[{ label: '采购金额:', prop: 'totalPrice', slot: 'totalPrice' }, {
+						label: '税率:', prop: 'taxRate', slot: 'taxRate',
+						isRequired: !this.isView
+					}],
+					[{ label: '不含税单价:', prop: 'notaxSinglePrice', slot: 'notaxSinglePrice', className: 'perce100' }],
+					[{
+						label: '供应商产品:', prop: 'supplierProductName', slot: 'supplierProductName',
+						className: 'perce100', isRequired: !this.isView
+					}],
+					[{
+						label: '供应商产品编码:', prop: 'supplierProductCode', slot: 'supplierProductCode',
+						className: 'perce100'
+					}],
+					[{ label: '型号:', prop: 'modelType', slot: 'modelType' }, { label: '规格:', prop: 'specification', slot: 'specification' }],
+					[{ label: '产地:', prop: 'provenance', slot: 'provenance', className: 'perce100' }],
+					[{ label: '需求数量:', prop: 'reqTotalCount' }, { label: '库存:', prop: 'availableCountBase' }],
+					[{ label: '最低订购量:', prop: 'minimumOrderQuantity' }, { label: '包装规格:', prop: 'packingSpecification' }],
+					[{ label: '交期(天):', prop: 'deliveryDays', slot: 'deliveryDays' }, {
+						label: '有效期:', prop: 'guaranteePeriod', slot: 'guaranteePeriod'
+					}],
+					[{
+						label: '有效期单位:', prop: 'guaranteePeriodUnitCode', slot: 'guaranteePeriodUnitCode',
+						className: 'perce100'
+					}],
+					[{ label: '工序:', prop: 'taskName' }, { label: '批次号:', prop: 'batchNo' }],
+					[{ label: '备注:', prop: 'remark', slot: 'remark', className: 'perce100' }],
+					// [{ label: '附件:', prop: 'files', slot: 'files', className: 'perce100' }]
+				]
+			}
+		},
+		async created() {
+			if (!this.isView) {
+				await this.requestDict('结算方式')
+        await this.requestDict('产地')
+        await this.requestDict('质保期单位')
+				this.settlementModeList = this.dict?.['settlement_mode']?.map(item => ({
+					text: item.dictValue,
+					value: item.dictCode
+				})) || []
+				this.provenanceList = this.dict?.['purchase_origin']?.map(item => ({
+					text: item.dictValue,
+					value: item.dictCode
+				})) || []
+
+				this.guaranteeUnitList = this.dict?.['date_unit']?.map(item => ({
+					text: item.dictValue,
+					value: item.dictCode
+				})) || []
+
+        this.setDeliveryDays()
+			}
+		},
+		methods: {
+			// 改变数量/单价 → 重算金额
+			changeCount(supplier, sIndex, pIndex) {
+        console.log(supplier, sIndex, pIndex)
+				const item = supplier.resultList[pIndex]
+				if (!item) return
+				// 包装单位换算
+				const changed = productChangeCount(item, {
+					countKey: 'purchaseCount',
+					unitKey: 'purchaseUnit',
+					unitIdKey: 'purchaseUnitId'
+				})
+				this.$set(supplier.resultList, pIndex, changed)
+				// 计算金额
+				this.calcItemTotal(supplier, pIndex)
+				// 更新总价
+				this.calcSupplierTotal(supplier)
+			},
+			// 单项金额 = 数量 * 单价
+			calcItemTotal(supplier, pIndex) {
+        console.log('calcItemTotal', supplier, pIndex)
+				const item = supplier.resultList[pIndex]
+				if (item.singlePrice && item.purchaseCount) {
+					item.totalPrice = +(item.singlePrice * item.purchaseCount).toFixed(2)
+				}
+				// 不含税单价(税率在报价明细中,每个产品独立)
+				if (item.singlePrice && item.taxRate) {
+					item.notaxSinglePrice = +(item.singlePrice / (1 + item.taxRate / 100)).toFixed(2)
+				}
+			},
+			// 供应商总价 = 所有报价项金额之和
+			calcSupplierTotal(supplier) {
+				let total = 0
+				supplier.resultList.forEach(item => {
+					total += item.totalPrice || 0
+				})
+				supplier.totalPrice = +total.toFixed(2)
+        supplier.preferentialPrice = supplier.totalPrice
+			},
+			onSettlementChange(supplier) {
+				const found = this.settlementModeList.find(i => i.value === supplier.settlementMode)
+				if (found) supplier.settlementModeName = found.text
+			},
+			onDeliveryDateChange(supplier) {
+				supplier.resultList.forEach((item, index) => {
+					if (supplier.deliveryDate && item.expectReceiveDate) {
+						const deliver = new Date(supplier.deliveryDate).getTime()
+						const expect = new Date(item.expectReceiveDate).getTime()
+						if (deliver > expect) {
+							uni.showToast({ title: '交货日期大于到货日期', icon: 'none' })
+						}
+					}
+				})
+			},
+			onSupplierFilesChange(sIndex, val) {
+				const supplier = this.supplierList[sIndex]
+				if (supplier) this.$set(supplier, 'files', val)
+			},
+			getProvenanceLabel(val) {
+				if (!val || !val.length) return ''
+				return val.map(v => {
+					const found = this.provenanceList.find(p => p.value == v)
+					return found ? found.text : v
+				}).join(', ')
+			},
+			getGuaranteeUnitLabel(val) {
+				const found = this.guaranteeUnitList.find(g => g.value == val)
+				return found ? found.text : val || ''
+			},
+			onGuaranteeUnitChange(item) {
+				const found = this.guaranteeUnitList.find(g => g.value == item.guaranteePeriodUnitCode)
+				if (found) item.guaranteePeriodUnitName = found.text
+			},
+			openProvenancePicker(sIndex, pIndex) {
+				this.curProvenanceSIndex = sIndex
+				this.curProvenancePIndex = pIndex
+				this.$refs.provenancePicker._show()
+			},
+			provenanceConfirm(data, name, allList) {
+				if (this.curProvenanceSIndex === -1 || this.curProvenancePIndex === -1) return
+				const supplier = this.supplierList[this.curProvenanceSIndex]
+				const item = supplier?.resultList[this.curProvenancePIndex]
+				if (!item) return
+				const selectedValues = (allList || []).map(s => s.id)
+        console.log(selectedValues, allList)
+				this.$set(item, 'provenance', selectedValues)
+				this.curProvenanceSIndex = -1
+				this.curProvenancePIndex = -1
+			},
+			delProduct(sIndex, pIndex) {
+				const supplier = this.supplierList[sIndex]
+				if (!supplier) return
+				supplier.resultList.splice(pIndex, 1)
+			},
+			// 获取表格数据(供父组件保存时调用)
+			getTableValue() {
+				return this.supplierList
+			},
+      setDeliveryDays() {
+        this.supplierList.forEach(supplier => {
+          supplier.resultList.forEach((item, index) => {
+          let day =
+            supplier.deliveryDate &&
+            (new Date(supplier.deliveryDate).getTime() -
+              new Date().getTime()) /
+              1000 /
+              60 /
+              60 /
+              24;
+
+          this.$set(
+            supplier.resultList[index],
+            'deliveryDays',
+            Math.ceil(day) || 1
+          );
+        });
+        })
+        this.$forceUpdate();
+      },
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.mainBox {
+		background: #fff;
+		margin-bottom: 10rpx;
+	}
+
+	.supplier-header {
+		padding: 20rpx 30rpx;
+		background: #f8fdf8;
+		border-bottom: 2rpx solid #e5e5e5;
+
+		.supplier-name {
+			font-size: 30rpx;
+			font-weight: 600;
+			color: #333;
+			margin-bottom: 12rpx;
+
+			.label {
+				color: #999;
+				font-weight: 400;
+			}
+
+			.value {
+				color: #157A2C;
+				font-weight: 600;
+			}
+		}
+
+		.supplier-summary {
+			display: flex;
+			flex-wrap: wrap;
+			align-items: center;
+
+			.summary-item {
+				margin-right: 30rpx;
+				margin-bottom: 6rpx;
+				display: flex;
+				align-items: center;
+
+				.label {
+					font-size: 24rpx;
+					color: #999;
+				}
+
+				.value {
+					font-size: 24rpx;
+					color: #333;
+
+					&.price {
+						color: #E6A23C;
+						font-weight: 600;
+					}
+				}
+			}
+		}
+	}
+
+	.price-tag {
+		color: #E6A23C;
+		font-weight: 600;
+	}
+
+	/deep/.u-input {
+		padding: 0 !important;
+		height: 44rpx !important;
+		font-size: 26rpx !important;
+	}
+
+	/deep/.uni-date-editor--x .uni-date__icon-clear {
+		border: none !important;
+	}
+
+	/deep/.uni-date__x-input,
+	/deep/.uni-date-x {
+		padding: 0 !important;
+		height: 44rpx !important;
+		font-size: 26rpx !important;
+	}
+</style>

+ 263 - 0
pages/home/wt/components/pricingManage/inventoryTable.vue

@@ -0,0 +1,263 @@
+<template>
+	<view class="mainBox">
+		<view v-for="(item, index) in productList" :key="index">
+			<myCard :item="item" :index="index + 1" :columns="columns">
+				<!-- 产品名称(标题行) -->
+				<view slot="productName">
+					<text style="font-weight: 600;">{{ item.productName || '-' }}</text>
+				</view>
+				<!-- 供应商选择 -->
+				<view slot="supplierIds">
+					<view v-if="isView" class="supplier-view">
+						<text class="supplier-text">{{ getSupplierNames(item) || '点击选择供应商' }}</text>
+						<text class="arrow">›</text>
+					</view>
+					<view v-else>
+						<u-row gutter="10">
+							<u-col span="8">
+								<view class="supplier-tags" @click="openPicker(item, index)">
+									<text v-if="!item.supplierIds || !item.supplierIds.length" class="placeholder">选择供应商</text>
+									<!-- <u-tag v-for="sId in (item.supplierIds || [])" :key="sId" :text="getSupplierNameById(item, sId)" size="mini" type="success" :closeable="true" @close="removeSupplierId(item, index, sId)"></u-tag> -->
+                  <text v-else>{{ getSupplierNames(item) || '-' }}</text>
+								</view>
+							</u-col>
+							<u-col span="4">
+								<view class="supplier-tags" @click="openPicker(item, index)">
+									<u-tag text="选择" size="mini" type="primary" @click="openPicker(item, index)"></u-tag>
+								</view>
+							</u-col>
+						</u-row>
+					</view>
+				</view>
+				<!-- 核价状态 -->
+				<view slot="isInquiry">
+					<text>{{ getInquiryStatus(item.isInquiry) || '-' }}</text>
+				</view>
+				<!-- 物品级别 -->
+				<view slot="goodsLevel">
+					<text>{{ getGoodsLevel(item.goodsLevel) || '-' }}</text>
+				</view>
+				<!-- 属性类型 -->
+				<view slot="produceType">
+					<text>{{ getProduceType(item.produceType) || '-' }}</text>
+				</view>
+				<!-- 产地 -->
+				<view slot="provenance">
+					<text>{{ getProvenance(item.provenance) || '-' }}</text>
+				</view>
+				<!-- 附件 -->
+				<view slot="files">
+					<fileMain :value="item.files || []" type="view" v-if="item.files && item.files.length"></fileMain>
+					<text v-else>-</text>
+				</view>
+				<!-- 备注 -->
+				<view slot="remark">
+					<text>{{ item.remark || '-' }}</text>
+				</view>
+			</myCard>
+		</view>
+		<u-gap height="20" bgColor="#f0f0f0"></u-gap>
+		<u-empty v-if="!productList.length" text="暂无物品" mode="list"></u-empty>
+
+    <!-- 选择物品类型 -->
+		<ba-tree-picker ref="treePicker" :multiple="true" @select-change="suplierConfirm" title="选择供应商"
+			:localdata="supplierOptions" valueKey="id" textKey="name" childrenKey="child" />
+	</view>
+</template>
+
+<script>
+	import myCard from '@/pages/purchasingManage/components/myCard.vue'
+	import fileMain from "@/pages/doc/index.vue"
+	import { levelList, lbjtList } from '@/enum/dict.js'
+
+	export default {
+		components: {
+			myCard,
+			fileMain
+		},
+		props: {
+			productList: {
+				type: Array,
+				default: () => []
+			},
+			status: {
+				type: String,
+				default: 'edit'
+			},
+			acceptUnpack: {
+				default: ''
+			}
+		},
+		data() {
+			return {
+        supplierOptions: [],
+        currentIndex: -1,
+				selection: [],
+        // 核价状态
+        inquiryStatusList: [
+              { value: 0, text: '未核价' },
+              { value: 1, text: '部分核价' },
+              { value: 2, text: '全部核价完成' }
+            ],
+			}
+		},
+		computed: {
+			isView() {
+				return this.status === 'Detail'
+			},
+			btnList() {
+				if (this.isView) return []
+				return [
+					{
+						name: '生成报价单',
+						apiName: 'supplierSelect',
+						btnType: 'primary',
+						type: '2',
+						judge: [{ fn: (row) => !this.isView && row.supplierIds && row.supplierIds.length > 0 }]
+					},
+					{
+						name: '删除',
+						apiName: 'del',
+						btnType: 'error',
+						type: '2',
+						judge: [{ fn: () => !this.isView && !!this.acceptUnpack }]
+					}
+				]
+			},
+			columns() {
+				return [
+					[{ label: '名称:', prop: 'productName', slot: 'productName', type: 'title', className: 'perce100' }],
+					[{ label: '编码:', prop: 'productCode' }, { label: '分类:', prop: 'productCategoryName' }],
+					[{ label: '核价状态:', prop: 'isInquiry', slot: 'isInquiry' }, { label: '物品级别:', prop: 'goodsLevel', slot: 'goodsLevel' }],
+					[{ label: '供应商:', prop: 'supplierIds', slot: 'supplierIds', className: 'perce100' }],
+					[{ label: '数量:', prop: 'purchaseCount', formatter: (item) => (item.purchaseCount || item.totalCount || '-') + ' ' + (item.purchaseUnit || item.measuringUnit || '') },
+					 { label: '库存:', prop: 'availableCountBase', formatter: (item) => (item.availableCountBase || '-') + ' ' + (item.measuringUnit || '') }],
+					[{ label: '型号:', prop: 'modelType' }, { label: '规格:', prop: 'specification' }],
+					[{ label: '牌号:', prop: 'productBrand' }, { label: '图号/件号:', prop: 'imgCode' }],
+					[{ label: '包装规格:', prop: 'packingSpecification' }, { label: '重量:', prop: 'totalWeight' }],
+					[{ label: '属性类型:', prop: 'produceType', slot: 'produceType' }, { label: '产地:', prop: 'provenance', slot: 'provenance' }],
+					[{ label: '工序:', prop: 'taskName' }, { label: '批次号:', prop: 'batchNo' }],
+					[{ label: '到货日期:', prop: 'expectReceiveDate' }, { label: '附件:', prop: 'files', slot: 'files' }],
+					[{ label: '备注:', prop: 'remark', slot: 'remark', className: 'perce100' }]
+				]
+			}
+		},
+		methods: {
+      openPicker(item, index) {
+        this.supplierOptions = item.supplierList || []
+        this.currentIndex = index
+				this.$refs.treePicker._show()
+			},
+			suplierConfirm(data, name, allList) {
+				if (this.currentIndex === -1) return
+				const item = this.productList[this.currentIndex]
+				if (!item) return
+				this.$set(item, 'supplierIds', (allList || []).map(s => s.id))
+			},
+			// 获取核价状态文本
+      getInquiryStatus(val) {
+        const found = this.inquiryStatusList.find(item => item.value == val)
+        return found ? found.text : ''
+      },
+			getGoodsLevel(val) {
+				const found = levelList.find(item => item.value == val)
+				return found ? found.label || found.text : ''
+			},
+			getProduceType(val) {
+				if (val && val.length) {
+					return val.map(item => lbjtList[item] || item).toString()
+				}
+				return ''
+			},
+			getProvenance(val) {
+				if (val && val.length) {
+					return val.join(',')
+				}
+				return ''
+			},
+			getSupplierNames(item) {
+        console.log('item-------', item)
+				return (item.supplierIds || []).map(id => {
+					const sup = (item.supplierList || []).find(s => s.id == id)
+					return sup ? sup.name : ''
+				}).filter(Boolean).join(', ')
+			},
+			getSupplierNameById(item, id) {
+				const sup = (item.supplierList || []).find(s => s.id == id)
+				return sup ? sup.name : id
+			},
+			removeSupplierId(item, index, id) {
+				this.$set(this.productList[index], 'supplierIds', (item.supplierIds || []).filter(i => i != id))
+			},
+			onSupplierSelect(item, index) {
+        console.log('onSupplierSelect~~', item)
+				this.$emit('supplierSelect', item, index)
+			},
+			delProduct(index) {
+				if (!this.acceptUnpack) {
+					uni.showToast({ title: '本采购计划不能拆单', icon: 'none' })
+					return
+				}
+				const code = this.productList[index]?.productCode
+				this.productList.splice(index, 1)
+				this.$emit('delList', code)
+			},
+			// 返回列表数据
+			getTableValue() {
+				const datasource = this.productList
+				if (!datasource.length) return []
+				datasource.forEach(v => {
+					v.totalPrice = (v.totalCount * v.singlePrice)?.toFixed(2) || 0
+					v.files = v.files || null
+				})
+				return datasource
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.mainBox {
+		background: #fff;
+	}
+
+	.supplier-view {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		min-height: 44rpx;
+		color: #157A2C;
+		font-size: 26rpx;
+
+		.supplier-text {
+			flex: 1;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+		}
+
+		.arrow {
+			font-size: 32rpx;
+			color: #ccc;
+			margin-left: 8rpx;
+		}
+	}
+
+	.supplier-tags {
+		display: flex;
+		flex-wrap: wrap;
+		align-items: center;
+		min-height: 44rpx;
+		gap: 8rpx;
+
+		.placeholder {
+			color: #ccc;
+			font-size: 26rpx;
+		}
+	}
+
+	.text-gray {
+		color: #999;
+		font-size: 24rpx;
+	}
+</style>

+ 278 - 0
pages/home/wt/components/pricingManage/supplierManageList.vue

@@ -0,0 +1,278 @@
+<template>
+	<view class="mainBox">
+		<uni-nav-bar fixed="true" statusBar="true" left-icon="back" title="选择供应商" @clickLeft="handleClose">
+		</uni-nav-bar>
+		<view class="searchBox">
+			<input v-model="searchVal" placeholder="请输入供应商名称" class="searchInput" @confirm="doSearch" />
+			<u-button type="icon-shixiangxinzeng" size="30" @click="doSearch" class="searchBtn">
+				<view class="iconfont icon-sousuo"></view>
+				<view class="text">搜索</view>
+			</u-button>
+		</view>
+		<view class="wrapper">
+			<scroll-view scroll-y class="listContent" @scrolltolower="scrolltolower">
+				<checkbox-group @change="e => onCheckChange(e)">
+					<label v-for="(item, index) in listData" :key="index">
+						<view class="listBox">
+							<view class="listBox-sel">
+								<checkbox :value="item.id" :checked="item.checked" color="#157A2C" style="transform: scale(1.2)" />
+							</view>
+							<view class="listBox-con">
+								<view class="listBox-top">
+									<view class="listBox-name">{{ item.name || '-' }}</view>
+									<view class="listBox-code">{{ item.code || '' }}</view>
+								</view>
+								<view class="listBox-bottom">
+									<view>代号:{{ item.serialNo || '-' }}</view>
+									<view>电话:{{ item.phone || '-' }}</view>
+									<view>地址:{{ item.address || '-' }}</view>
+								</view>
+							</view>
+						</view>
+					</label>
+				</checkbox-group>
+				<u-loadmore v-if="listData.length" :status="loadStatus" />
+				<u-empty class="noDate" style="margin-top: 20vh" v-if="!listData.length && !loading"></u-empty>
+			</scroll-view>
+		</view>
+
+		<view class="footer">
+			<view class="count-text" v-if="checkedCount > 0">已选 {{ checkedCount }} 个</view>
+			<u-button type="success" size="small" :disabled="checkedCount === 0" @click="confirmSelect">
+				选择({{ checkedCount }})
+			</u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { contactPage } from '@/api/saleManage/contact/index.js'
+
+	export default {
+		data() {
+			return {
+				page: 1,
+				size: 20,
+				isEnd: false,
+				loading: false,
+				searchVal: '',
+				listData: [],
+				loadStatus: 'loadmore'
+			}
+		},
+		computed: {
+			checkedCount() {
+				return this.listData.filter(item => item.checked).length
+			}
+		},
+		onLoad() {
+			this.getList()
+		},
+		methods: {
+			scrolltolower() {
+				if (this.isEnd || this.loading) return
+				this.page++
+				this.getList()
+			},
+			doSearch() {
+				this.page = 1
+				this.listData = []
+				this.getList()
+			},
+			async getList() {
+				this.loading = true
+				this.loadStatus = 'loading'
+				try {
+					const result = await contactPage({
+						pageNum: this.page,
+						size: this.size,
+						name: this.searchVal || undefined,
+						type: 2,
+						status: 1
+					})
+					const newList = (result.list || []).map(item => ({
+						...item,
+						checked: false
+					}))
+					if (this.page === 1) {
+						this.listData = newList
+					} else {
+						this.listData = this.listData.concat(newList)
+					}
+					const total = result.count || result.total || 0
+					this.isEnd = this.listData.length >= total
+					this.loadStatus = this.isEnd ? 'nomore' : 'loadmore'
+				} catch (e) {
+					console.error(e)
+					this.loadStatus = 'nomore'
+				}
+				this.loading = false
+			},
+			onCheckChange(e) {
+				const values = e.detail.value || []
+				this.listData.forEach((item, i) => {
+					this.$set(this.listData[i], 'checked', values.includes(item.id))
+				})
+			},
+			confirmSelect() {
+				const selected = this.listData.filter(item => item.checked)
+				if (!selected.length) {
+					uni.showToast({ title: '请选择供应商', icon: 'none' })
+					return
+				}
+				const list = selected.map(item => ({
+					id: item.id,
+					supplierId: item.id,
+					supplierName: item.name,
+					supplierCode: item.code,
+					name: item.name,
+					code: item.code,
+					files: [],
+					preferentialPrice: '',
+					settlementMode: '4',
+					settlementModeName: '分期付款',
+					taxRate: '',
+					totalPrice: '',
+					resultList: []
+				}))
+				uni.$emit('supplierManageChange', list)
+				uni.navigateBack()
+			},
+			handleClose() {
+				uni.navigateBack()
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.mainBox {
+		height: 100vh;
+		display: flex;
+		flex-direction: column;
+		background: #f5f5f5;
+
+		.wrapper {
+			flex: 1;
+			overflow: hidden;
+		}
+
+		.searchBox {
+			padding: 10rpx 0;
+			box-sizing: border-box;
+			background-color: #dedede;
+			height: 90rpx;
+			width: 100%;
+			display: flex;
+			justify-content: space-around;
+			align-items: center;
+
+			input {
+				height: 70rpx;
+				width: 65%;
+				background: #f9f9f9;
+				margin: 0 10rpx;
+				padding: 0 20rpx;
+				box-sizing: border-box;
+				border-radius: 8rpx;
+				font-size: 28rpx;
+			}
+
+			.searchBtn {
+				height: 70rpx;
+				background: #f9f9f9;
+				color: #676767;
+				font-size: 28rpx;
+				padding: 0 30rpx;
+				box-sizing: border-box;
+				outline: none;
+				border: none;
+				width: 240rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+
+				.icon-sousuo {
+					font-size: 22px;
+				}
+
+				.text {
+					font-size: 26rpx;
+					margin-left: 10rpx;
+				}
+			}
+		}
+
+		.listContent {
+			height: 100%;
+			padding: 0 20rpx;
+
+			.listBox {
+				display: flex;
+				padding: 24rpx 0;
+				border-bottom: 2rpx solid #e5e5e5;
+				background: #fff;
+				margin-bottom: 2rpx;
+
+				.listBox-sel {
+					width: 80rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					flex-shrink: 0;
+				}
+
+				.listBox-con {
+					flex: 1;
+					padding-right: 18rpx;
+
+					.listBox-top {
+						display: flex;
+						justify-content: space-between;
+						align-items: center;
+						padding-bottom: 8rpx;
+
+						.listBox-name {
+							font-size: 28rpx;
+							font-weight: bold;
+							color: #333;
+						}
+
+						.listBox-code {
+							font-size: 22rpx;
+							color: #999;
+						}
+					}
+
+					.listBox-bottom {
+						font-size: 22rpx;
+						color: #666;
+
+						>view {
+							padding: 3rpx 0;
+							overflow: hidden;
+							white-space: nowrap;
+							text-overflow: ellipsis;
+						}
+					}
+				}
+			}
+		}
+
+		.footer {
+			height: 100rpx;
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			border-top: 1rpx solid #eeecec;
+			background-color: #fff;
+			padding: 0 30rpx;
+			flex-shrink: 0;
+
+			.count-text {
+				font-size: 26rpx;
+				color: #666;
+			}
+		}
+	}
+</style>

+ 241 - 85
pages/home/wt/components/pricingManage/taskForm.vue

@@ -1,69 +1,65 @@
 <template>
 	<view class="">
-		<!-- 未开发核价管理手机端 -->
 		<u-sticky offset-top="50">
 			<u-subsection fontSize='25' mode='subsection' :list="list" :current="curNow" @change="sectionChange"
 				activeColor='#157A2C'></u-subsection>
 		</u-sticky>
 
+		<!-- 基本信息 -->
 		<view v-show='curNow===0'>
-			<u--form style="margin: 0 20px;" labelPosition="left" :model="form" ref="uForm" labelWidth='140rpx'>
-				
-				<u-form-item label="计划单名称" prop="planName" borderBottom>
-					<u--input style="width: 100%;" disabled v-model="form.planName"></u--input>
+			<u--form style="margin: 0 20px;" labelPosition="left" :model="form" ref="uForm" labelWidth='180rpx'>
+				<u-form-item label="采购计划单名称" prop="planName" borderBottom @click="handParent">
+					<u--input style="width: 100%;" v-model="form.planName" placeholder="点击选择"></u--input>
 				</u-form-item>
-				
-				<u-form-item label="需求单名称" prop="requirementName" borderBottom>
-					<u--input style="width: 100%;" disabled v-model="form.requirementName"></u--input>
-				</u-form-item>
-				<u-form-item label="需求类型" prop="sourceTypeName" borderBottom>
-					<u--input style="width: 100%;" disabled v-model="form.sourceTypeName"></u--input>
-				</u-form-item>
-				<u-form-item label="需求部门" prop="requireDeptName" borderBottom>
-					<u--input style="width: 100%;" disabled v-model="form.requireDeptName"></u--input>
-				</u-form-item>
-				<u-form-item label="需求人" prop="requireUserName" borderBottom>
-					<u--input style="width: 100%;" disabled v-model="form.requireUserName"></u--input>
+				<u-form-item label="询价单名称" prop="inquiryName" borderBottom>
+					<u--input style="width: 100%;" v-model="form.inquiryName"></u--input>
 				</u-form-item>
 				<u-form-item label="是否接受拆单" prop="acceptUnpack" borderBottom>
-
-					<u-tag v-if='form.acceptUnpack==1' text="接受"  size="large" type="success"></u-tag>
-					<u-tag v-else-if='form.acceptUnpack==0' text="不接受"  size="large" type="warning"></u-tag>
-					<u--input v-else style="width: 100%;" disabled v-model="form.acceptUnpack"></u--input>
+					<u-tag v-if='form.acceptUnpack==1' text="接受" size="large" type="success"></u-tag>
+					<u-tag v-else-if='form.acceptUnpack==0' text="不接受" size="large" type="warning"></u-tag>
+					<u--input v-else style="width: 100%;" disabled value="-"></u--input>
 				</u-form-item>
-
 				<u-form-item label="备注" prop="remark" borderBottom>
-					<u--input style="width: 100%;" disabled v-model="form.remark"></u--input>
+					<u--input style="width: 100%;" v-model="form.remark"></u--input>
+				</u-form-item>
+				<u-form-item label="附件" prop="files" borderBottom>
+					<fileMain v-model="form.files" type="add"></fileMain>
 				</u-form-item>
 			</u--form>
-
 		</view>
-		<view v-show='curNow===1'>
-			<view v-for="(item,index) in form['detailList']" :key="index">
-				<u--form style="margin: 0 20px;" labelPosition="left" :model="form" ref="uForm" labelWidth='140rpx'>
-					<u-row v-for="(key,index1) in tableField" :key="index1">
-						<u-col :span="12">
-							<u-form-item :label="key.label" prop="categoryName" borderBottom>
-								<u--input style="width: 100%;" :title='item[key.field]' disabled
-									v-model="item[key.field]"></u--input>
-							</u-form-item>
-						</u-col>
-
-					</u-row>
-				</u--form>
-				<u-gap height="40" bgColor="#f0f0f0"></u-gap>
-			</view>
 
+		<!-- 物品清单 -->
+		<view v-show='curNow===1'>
+			<inventoryTable :productList="productList" :acceptUnpack="form.acceptUnpack" status="edit"
+				ref="inventoryTableRef" @supplierSelect="onItemSupplierSelect">
+			</inventoryTable>
 		</view>
 
+		<!-- 报价清单 -->
+		<view v-show='curNow===2'>
+			<inquiryTable ref="inquiryTableRef" :supplierList="supplierList" status="edit"></inquiryTable>
+			<u-empty v-if="!supplierList.length" text="暂无报价" mode="list"></u-empty>
+		</view>
 	</view>
 </template>
 
 <script>
 	import {
-		getPurchasePlanByIdsAPI
-	} from '@/api/wt/index.js'
+		getpurchaseinquiry
+	} from '@/api/purchasingManage/inquiryManage'
+	import { getplanDetail } from '@/api/purchasingManage/purchasePlanManage'
+	import fileMain from "@/pages/doc/index.vue"
+	import inquiryTable from './inquiryTable.vue'
+	import inventoryTable from './inventoryTable.vue'
+	import { contactQueryByCategoryIdsAPI } from '@/api/warehouseManagement/index.js'
+	import { orderSourceType } from '@/enum/dict'
+
 	export default {
+		components: {
+			fileMain,
+			inquiryTable,
+			inventoryTable
+		},
 		props: {
 			businessId: {
 				default: ''
@@ -74,60 +70,220 @@
 		},
 		data() {
 			return {
-				detailData: {},
 				form: {},
-				tableField: [{
-						label: '名称',
-						field: 'productName',
-					},
-					{
-						label: '编码',
-						field: 'productCode',
-					},
-					{
-						label: '类型',
-						field: 'productCategoryName',
-					},
-					{
-						label: '数量',
-						field: 'totalCount',
-					},
-					{
-						label: '单位',
-						field: 'measuringUnit',
-					},
-					{
-						label: '品牌',
-						field: 'brand',
-					},
-					{
-						label: '型号',
-						field: 'modelType',
-					},
-					{
-						label: '规格',
-						field: 'specification',
-					},
-					
-				],
-				list: ['基本信息', '计划清单'],
+				supplierList: [],
+				productList: [],
+				rawList: [],
+				outputList: [],
+				loading: false,
+				list: ['基本信息', '物品清单', '报价清单'],
 				curNow: 0
 			}
 		},
 		async mounted() {
-			await this.getDetailData(this.businessId);
+			await this.getDetailData(this.businessId, 'init')
+			// 监听采购计划选择回调
+			uni.$on('changeInquiryManageList', (data) => {
+				this.changeInquiryManageList(data)
+			})
+			// 监听供应商选择回调
+			uni.$on('supplierManageChange', (data) => {
+				this.supplierManageChange(data)
+			})
+		},
+		beforeDestroy() {
+			uni.$off('changeInquiryManageList')
+			uni.$off('supplierManageChange')
 		},
 		methods: {
 			sectionChange(index) {
-				this.curNow = index;
+				this.curNow = index
+			},
+			// 打开采购计划选择页
+			handParent() {
+				uni.navigateTo({
+					url: '/pages/home/wt/components/pricingManage/inquiryManageList?planCode=' + (this.form.planCode || '')
+				})
+			},
+			// 打开供应商选择页
+			openSupplier() {
+				uni.navigateTo({
+					url: '/pages/home/wt/components/pricingManage/supplierManageList'
+				})
+			},
+			// 选择采购计划后回调
+			changeInquiryManageList(data) {
+				console.log('changeInquiryManageList~~', data)
+				this.getplanData(data.id, 'change')
+			},
+			// 选择供应商后回调
+			supplierManageChange(list) {
+				const existingIds = this.supplierList.map(item => item.supplierId)
+				list.forEach(supplier => {
+					if (!existingIds.includes(supplier.supplierId)) {
+						this.supplierList.push(supplier)
+					}
+				})
+			},
+			// 物品清单中某产品选择供应商
+			onItemSupplierSelect(item, index) {
+				console.log('onItemSupplierSelect~~', item)
+				uni.navigateTo({
+					url: '/pages/home/wt/components/pricingManage/supplierManageList'
+				})
+			},
+			// 获取计划详情
+			async getplanData(id, type) {
+				this.loading = true
+				const data = await getplanDetail(id)
+				this.rawList = data.rawDetailList || []
+				this.outputList = data.outputDetailList || []
+				this.loading = false
+				if (data) {
+					// 获取产品对应的供应商
+					const supplierObj = await this.getSupplierObj(data.detailList, 'productId')
+					// 处理产品列表:补充到货日期和供应商信息
+					data.detailList.forEach((item) => {
+						if (item.arrivalWay == 2 && item.arrivalBatch && item.arrivalBatch.length > 0) {
+							item.expectReceiveDate = item.arrivalBatch[item.arrivalBatch.length - 1].arriveDate
+						}
+						item.supplierList = supplierObj[item.productId] || []
+						if (item.supplierList.length === 1) {
+							item.supplierIds = [item.supplierList[0].id]
+						}
+					})
+					// 设置表单字段
+					this.$set(this.form, 'acceptUnpack', data.acceptUnpack)
+					this.form.planId = data.id
+					this.form.planCode = data.planCode
+					this.form.planName = data.planName
+					// init 类型不覆盖 productList(已在 getDetailData 中设置)
+					if (type === 'init') return
+					// 变更时合并产品清单
+					if (orderSourceType.includes(this.form.sourceType)) {
+						this.productList = [...data.detailList, ...this.rawList, ...this.outputList]
+					} else {
+						this.productList = [...data.detailList]
+					}
+				}
+			},
+			async getSupplierObj(productList, queryName) {
+				try {
+					const categoryIds = productList.map((item) => item[queryName])
+					return await contactQueryByCategoryIdsAPI({ categoryIds })
+				} catch (e) {
+					return Promise.resolve({})
+				}
+			},
+			async getDetailData(id, type) {
+				this.loading = true
+				const data = await getpurchaseinquiry(id)
+				this.loading = false
+				if (data) {
+					this.form = data
+					this.supplierList = data.supplierList || []
+					// 获取产品对应的供应商列表
+					const supplierObj = await this.getSupplierObj(data.detailList, 'productId')
+					data.detailList.forEach((item) => {
+						item.supplierList = supplierObj[item.productId] || []
+					})
+					// 合并产品清单
+					this.rawList = data.rawList || []
+					this.outputList = data.outputList || []
+					if (orderSourceType.includes(this.form.sourceType)) {
+						this.productList = [...data.detailList, ...this.rawList, ...this.outputList]
+					} else {
+						this.productList = [...data.detailList]
+					}
+					// 是否首次加载,需要拉取计划详情
+					if (type === 'init') {
+						this.getplanData(data.planId, type)
+					}
+				}
+			},
+			async getTableValue() {
+				// 1. 校验基本信息
+				if (!this.form.inquiryName) {
+					uni.showToast({ title: '请输入询价单名称', icon: 'none' })
+					return false
+				}
+				if (!this.form.planId) {
+					uni.showToast({ title: '请选择采购计划', icon: 'none' })
+					return false
+				}
+
+				// 2. 校验物品清单不为空
+				const inventoryData = this.$refs.inventoryTableRef?.getTableValue()
+				if (!inventoryData || inventoryData.length === 0) {
+					uni.showToast({ title: '物品清单不能为空', icon: 'none' })
+					return false
+				}
+
+				// 3. 校验报价清单
+				const supplierList = this.$refs.inquiryTableRef?.getTableValue() || []
+				if (supplierList.length === 0) {
+					uni.showToast({ title: '报价清单不能为空', icon: 'none' })
+					return false
+				}
+				for (let i = 0; i < supplierList.length; i++) {
+					const supplier = supplierList[i]
+					// 校验供应商可填写字段
+					if (!supplier.settlementMode) {
+						uni.showToast({ title: `供应商${supplier.supplierName || i + 1}:请选择结算方式`, icon: 'none' })
+						return false
+					}
+					if (!supplier.deliveryDate) {
+						uni.showToast({ title: `供应商${supplier.supplierName || i + 1}:请选择交货日期`, icon: 'none' })
+						return false
+					}
+					// 校验报价明细
+					const items = supplier.resultList || []
+					for (let j = 0; j < items.length; j++) {
+						const item = items[j]
+						const name = item.productName || item.productCode || (j + 1)
+						if (item.isWinner === '' || item.isWinner === null || item.isWinner === undefined) {
+							uni.showToast({ title: `${supplier.supplierName || ''} ${name}:请选择是否中标`, icon: 'none' })
+							return false
+						}
+						if (!item.supplierProductName) {
+							uni.showToast({ title: `${supplier.supplierName || ''} ${name}:请输入供应商产品名称`, icon: 'none' })
+							return false
+						}
+						if (!item.purchaseCount || item.purchaseCount <= 0) {
+							uni.showToast({ title: `${supplier.supplierName || ''} ${name}:请输入数量`, icon: 'none' })
+							return false
+						}
+						if (!item.singlePrice || item.singlePrice <= 0) {
+							uni.showToast({ title: `${supplier.supplierName || ''} ${name}:请输入单价`, icon: 'none' })
+							return false
+						}
+						if (!item.taxRate && item.taxRate !== 0) {
+							uni.showToast({ title: `${supplier.supplierName || ''} ${name}:请输入税率`, icon: 'none' })
+							return false
+						}
+						// 校验最低订购量
+						if (item.minimumOrderQuantity && item.purchaseCount < item.minimumOrderQuantity) {
+							uni.showToast({ title: `${supplier.supplierName || ''} ${name}:购买数量不能小于最低订购量`, icon: 'none' })
+							return false
+						}
+					}
+				}
+
+				// 4. 组装返回数据
+				this.form.detailList = inventoryData
+				this.form.supplierList = supplierList
+				// 合并 rawList/outputList 到 supplierList
+				if (orderSourceType.includes(this.form.sourceType)) {
+					this.form.supplierList.forEach(sup => {
+						sup.resultList = [...(sup.resultList || []), ...this.rawList, ...this.outputList]
+					})
+				}
+				this.form.files = this.form.files || []
+				return this.form
 			},
-			async getDetailData(id) {
-				const data = await getPurchasePlanByIdsAPI(id);
-				this.form = data
-			}
 		}
 	}
 </script>
 
-<style>
-</style>
+<style lang="scss" scoped>
+</style>

+ 74 - 18
pages/home/wt/components/pricingManage/taskSubmit.vue

@@ -3,32 +3,40 @@
 		<!-- 未开发核价管理手机端 -->
 		<u--form style="margin: 0 20px;" labelPosition="left" :model="form" :rules="rules" ref="uForm"
 			labelWidth='140rpx'>
-			<u-form-item label="负责人" prop="userName" required>
-				<u--input clearable placeholder="请选择" border="surround" v-model="form.userName"
-					@click.native="showTechnicianPicker"></u--input>
-			</u-form-item>
 			<u-form-item label="审批建议" prop="reason" required>
 				<u--textarea style="width: 100%;" height='120' border='surround' placeholder="请输入审批建议"
 					v-model="form.reason"></u--textarea>
 			</u-form-item>
 		</u--form>
-		<view>
+		<!-- <view>
 			<u-button style="width: 100%;margin-bottom: 10rpx;" icon="edit-pen" :loading='loading' type="success"
 				text="通过" @click="handleAudit(1)">
 			</u-button>
 			<u-button style="width: 100%;" :loading='loading' type="error" icon="close" text="驳回"
 				@click="handleAudit(0)" v-if="!['starter'].includes(taskDefinitionKey)"></u-button>
+		</view> -->
+		<view class="btnList">
+			<u-button style="width: 45%;margin-bottom: 10rpx;" :loading='loading' type="success" text="通过"
+				@click="handleAudit(1)">
+			</u-button>
+			<u-button style="width: 45%;" :loading='loading' type="error" text="驳回" @click="handleAudit(0)"></u-button>
 		</view>
-		<u-picker itemHeight='60' :show="technicianShow" visibleItemCount='10' :columns="userOptions" keyName="name"
-			@confirm='selectTechnicianInfo' @cancel='technicianShow = false' title='选择负责人'></u-picker>
+		<view class="btnConcel">
+			<u-button @click="showAction = true">更多</u-button>
+		</view>
+		<u-action-sheet :actions="actionList" :closeOnClickOverlay="true" :closeOnClickAction="true" title="更多操作" :show="showAction" @close="showAction = false" @select="selectActionClick"></u-action-sheet>
+		
 	</view>
 </template>
 
 <script>
 	import {
 		approveTaskWithVariables,
-		AssignPurchasePlanUserAPI
+		AssignPurchasePlanUserAPI,
+		rejectTask,
+		cancelTask
 	} from '@/api/wt/index.js'
+	import { UpdateInformation } from '@/api/purchasingManage/inquiryManage'
 	import {
 		listAllUserBind
 	} from '@/api/common.js'
@@ -51,6 +59,7 @@
 
 		data() {
 			return {
+				showAction: false,
 				loading: false,
 				technicianShow: false,
 				userOptions: [],
@@ -59,6 +68,11 @@
 					userName: '',
 					reason: '',
 				},
+				actionList: [{
+					name: '作废',
+					fontSize: '28',
+					color: '#ffaa7f'
+				}],
 				rules: {
 					reason: {
 						type: 'string',
@@ -77,7 +91,7 @@
 		},
 		mounted() {
 			this.$refs.uForm.setRules(this.rules)
-			this.getUserOptions()
+			// this.getUserOptions()
 		},
 		methods: {
 			async getUserOptions() {
@@ -99,14 +113,14 @@
 			},
 			async handleAudit(status) {
 				if (!!status) await this.$refs.uForm.validate()
+				let res = await this.getTableValue();
 
-				await AssignPurchasePlanUserAPI({
-					userId: this.form.userId,
-					userName: this.form.userName,
-					id: this.taskId,
-					reason: this.form.reason,
-					businessId: this.businessId
-				})
+				console.log('res~~~~', res)
+
+				const data = await UpdateInformation(res)
+				// if (data.code != '0') {
+				// 	return;
+				// }
 				this.loading = true
 				await this._approveTaskWithVariables(status);
 			},
@@ -114,7 +128,9 @@
 				let variables = {
 					pass: !!status
 				};
-				let res = await approveTaskWithVariables({
+
+				let API = !!status ? approveTaskWithVariables : rejectTask;
+				let res = await API({
 					id: this.taskId,
 					reason: this.form.reason,
 					variables
@@ -129,6 +145,39 @@
 				this.loading = false
 			},
 
+			selectActionClick(item) {
+				console.log('selectActionClick', item)
+				if (item.name == '作废') {
+					uni.showModal({
+						title: '提示',
+						content: '是否确认作废?',
+						success: (res) => {
+							if (res.confirm) {
+								this.loading = true
+								cancelTask({
+									taskId: this.taskId,
+									id: this.id,
+									reason: this.form.reason,
+									businessId: this.businessId,
+								}).then(() => {
+									if (res.code != '-1') {
+										this.loading = false
+										this.$emit('handleAudit', {
+											title: '作废'
+										});
+									}
+								}).catch(() => {
+									this.loading = false
+									this.$message.error("流程作废失败");
+								});
+							} else if (res.cancel) {
+								console.log('用户点击取消');
+							}
+						}
+					});
+				}
+			},
+
 			getTableValue() {
 				return new Promise((resolve, reject) => {
 					this.$emit('getTableValue', async (data) => {
@@ -140,5 +189,12 @@
 	}
 </script>
 
-<style>
+<style lang="scss" scoped>
+.btnList {
+		display: flex;
+
+	}
+	.btnConcel {
+		margin-top: 20rpx;
+	}
 </style>

+ 67 - 5
utils/setProduct.js

@@ -1,5 +1,18 @@
 import Vue from 'vue';
 
+// 保留指定位数小数并去掉多余的0
+export function formatPrice(price, decimals = 4) {
+  return price.toFixed(decimals).replace(/\.?0+$/, '');
+}
+
+// 安全格式化数字(兼容 $math 未注入的场景)
+function safeFormat(value) {
+  if (Vue.prototype.$math && Vue.prototype.$math.format) {
+    return Vue.prototype.$math.format(value, 14)
+  }
+  return Number(value)
+}
+
 //改变数量
 export function changeCount(row, countObj, noDiscountSingle) {
   console.log(row, countObj, noDiscountSingle);
@@ -10,10 +23,7 @@ export function changeCount(row, countObj, noDiscountSingle) {
       (ite) => row[countObj.unitIdKey] == ite.id
     );
     for (; 0 < endIndex; endIndex--) {
-      total = Vue.prototype.$math.format(
-        row.packageDispositionList[endIndex].packageCell * total,
-        14
-      );
+      total = safeFormat(row.packageDispositionList[endIndex].packageCell * total)
     }
   }
 
@@ -39,15 +49,67 @@ export function changeCount(row, countObj, noDiscountSingle) {
   // }
   // return getNumTotalPrice(arr, noDiscountSingle);
 }
+
+//改变数量
+export function changeCountNew(row, countObj, noDiscountSingle, weightType) {
+  
+  let data = JSON.parse(JSON.stringify(row));
+  console.log('changeCountNew~~', data);
+  let total = data[countObj.countKey] || 0;
+
+  let _endIndex = 0; //计算单重需要
+  if (data.packageDispositionList) {
+    let endIndex = data.packageDispositionList.findIndex(
+      (ite) => data[countObj.unitIdKey] == ite.id
+    );
+    _endIndex = endIndex;
+    let packageData = data.packageDispositionList.find(
+      (ite) => data[countObj.unitIdKey] == ite.id
+    );
+    data[countObj.unitKey] = packageData.conversionUnit;
+    if (data.weightUnit == data.measuringUnit && data.singleWeight) {
+      data.packageDispositionList[1].packageCell = data.singleWeight;
+    }
+    for (; 0 < endIndex; endIndex--) {
+      total = safeFormat(data.packageDispositionList[endIndex].packageCell * total)
+    }
+  }
+  data['totalCount'] = total;
+
+  data['discountSinglePrice'] = data.singlePrice && data.discountRatio ? formatPrice(+data.singlePrice * (+data.discountRatio /100)) : data.singlePrice;
+
+  if (weightType == 'totalWeight') {
+    console.log('weightType~~', 1);
+    setSingleWeight(data, countObj, _endIndex);
+  } else {
+    console.log('weightType~~', 2);
+    setWeight(data, countObj);
+  }
+
+  data['totalPrice'] = 0;
+  data['discountTotalPrice'] = 0;
+  if (data.pricingWay == 2 || data.pricingWay == 3) {
+    if (data.totalWeight && data.singlePrice) {
+      data['totalPrice'] = formatPrice(data.totalWeight * data.singlePrice);
+    }
+  } else {
+    if (data[countObj.countKey] && data.singlePrice) {
+      data['totalPrice'] = formatPrice(data[countObj.countKey] * data.singlePrice);
+    }
+  }
+  data['discountTotalPrice'] = data['totalPrice'] && data.discountRatio ? formatPrice(+data['totalPrice'] * (data.discountRatio /100)) : data['totalPrice'];
+  return data;
+}
 function setWeight(row) {
   if (row.weightUnit == row.measuringUnit) {
     row['totalWeight'] = row.totalCount;
   } else if (row.totalCount && row.singleWeight) {
-    row['totalWeight'] = row.totalCount * row.singleWeight;
+    row['totalWeight'] = Number((row.totalCount * row.singleWeight).toFixed(2));
   } else {
     row['totalWeight'] = 0;
   }
 }
+
 export function getAllPrice(arr) {
   let sum = 0;
   arr.forEach((item) => {