ContractForm.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. <script setup lang="ts">
  2. import { ref, reactive, nextTick } from 'vue'
  3. import Drawer from '@/components/Drawer/index.vue'
  4. import Dialog from '@/components/dialog/Dialog.vue'
  5. import type { FormInstance, FormRules } from 'element-plus'
  6. import { Delete } from '@element-plus/icons-vue'
  7. import { GlobalStore } from '@/stores/index'
  8. import Upload from '@/components/Upload/index.vue'
  9. import {
  10. Storehouse_Contract_Add,
  11. Storehouse_Product_List,
  12. Storehouse_Contract_Edit,
  13. Storehouse_ProductClass_List,
  14. Storehouse_Product_Model_List,
  15. Storehouse_Product_Name_List,
  16. Storehouse_Contract_Product_List
  17. } from '@/api/storehouse/index'
  18. import { ElMessage } from 'element-plus'
  19. import { default as vElTableInfiniteScroll } from 'el-table-infinite-scroll'
  20. let selectProductData: any[] = []
  21. const isNew = ref(true)
  22. const globalStore = GlobalStore()
  23. const formLabelWidth = ref('120px')
  24. const ruleFormRef = ref<FormInstance>()
  25. const drawerRef = ref<InstanceType<typeof Drawer> | null>(null)
  26. const dialogRef = ref<InstanceType<typeof Dialog> | null>(null)
  27. const uploadRef = ref<InstanceType<typeof Upload> | null>(null)
  28. const selectTable = ref()
  29. const drawerProductRef = ref<InstanceType<typeof Drawer> | null>(null)
  30. const validate_T_product = (rule: any, value: any, callback: any) => {
  31. if (form.T_type === 1 && value === '') {
  32. callback(new Error('请选择产品明细'))
  33. } else if (value.includes(undefined)) {
  34. callback(new Error('请填写产品数量'))
  35. } else {
  36. callback()
  37. }
  38. }
  39. const rules = reactive<FormRules>({
  40. T_number: [{ required: true, message: '请输入物联网卡号', trigger: 'blur' }],
  41. T_customer: [{ required: true, message: '请输入客户名称', trigger: 'blur' }],
  42. T_type: [{ required: true, message: '请选择合同类型', trigger: 'blur' }],
  43. T_product: [{ validator: validate_T_product, trigger: 'blur' }],
  44. T_money: [{ required: true, message: '请输入合同金额', trigger: 'blur' }],
  45. T_date: [{ required: true, message: '请选择业务日期', trigger: 'blur' }]
  46. })
  47. const callbackDrawer = (done: Fn) => {
  48. resetForm(ruleFormRef.value)
  49. isNew.value = true
  50. done()
  51. }
  52. const callbackProductDrawer = (done: Fn) => done()
  53. const resetForm = (formEl: FormInstance | undefined) => {
  54. if (!formEl) return
  55. tableData.value = []
  56. formEl.resetFields()
  57. }
  58. // 父级方法
  59. const emit = defineEmits<{ (event: 'onTableList'): void }>()
  60. // 添加仓库名称
  61. interface FormType {
  62. // T_id: string
  63. T_number: string
  64. T_customer: string
  65. T_type: any
  66. T_product: any
  67. T_money: string
  68. T_date: string
  69. T_remark: string
  70. T_pdf: string
  71. }
  72. type Fn = () => void
  73. const form = reactive<FormType>({
  74. // T_id: '',
  75. T_number: '',
  76. T_customer: '',
  77. T_type: '',
  78. T_product: '',
  79. T_money: '',
  80. T_date: '',
  81. T_remark: '',
  82. T_pdf: ''
  83. })
  84. const openDrawer = (type: string, row?: any) => {
  85. isNew.value = type === 'new' ? true : false
  86. if (!isNew.value) {
  87. nextTick(() => {
  88. editDataEcho(row)
  89. })
  90. }
  91. drawerRef.value?.openDrawer()
  92. }
  93. // edit data echo
  94. const editDataEcho = async (row: any) => {
  95. // console.log(row)
  96. // form.T_id = row.Id
  97. form.T_pdf = row.T_pdf
  98. form.T_date = row.T_date
  99. form.T_type = row.T_type
  100. form.T_money = row.T_money
  101. form.T_remark = row.T_remark
  102. form.T_number = row.T_number
  103. form.T_customer = row.T_customer
  104. const res: any = await Storehouse_Contract_Product_List({
  105. User_tokey: globalStore.GET_User_tokey,
  106. T_number: row.T_number
  107. })
  108. if (res.Code === 200) {
  109. res.Data &&
  110. (tableData.value = res.Data.map((item: any) => {
  111. item.Id = item.T_product_id
  112. item.T_img = item.T_product_img
  113. item.T_name = item.T_product_name
  114. item.T_class_name = item.T_product_class_name
  115. item.T_model = item.T_product_model
  116. item.T_spec = item.T_product_spec
  117. item.T_relation_sn = item.T_product_relation_sn
  118. item.count = item.T_product_total
  119. return item
  120. }))
  121. selectProductData = tableData.value
  122. }
  123. }
  124. const blurHandle = () => {
  125. form.T_product = tableData.value.map(item => {
  126. if (!item.count) return undefined
  127. return `${item.Id},${item.count}`
  128. })
  129. }
  130. const deleteProduct = (row: any) => {
  131. tableData.value = tableData.value.filter(item => item.Id !== row.Id)
  132. // 设置产品的选中
  133. selectTable.value?.toggleRowSelection(row, false)
  134. }
  135. const AddContract = (formEl: FormInstance | undefined) => {
  136. if (!formEl) return
  137. formEl.validate(async valid => {
  138. if (valid) {
  139. let res: any = {}
  140. let product = ''
  141. tableData.value.forEach(item => {
  142. product += `${item.Id},${item.count}|`
  143. })
  144. if (isNew.value) {
  145. res = await Storehouse_Contract_Add({
  146. ...form,
  147. User_tokey: globalStore.GET_User_tokey,
  148. T_product: product
  149. })
  150. } else {
  151. res = await Storehouse_Contract_Edit({
  152. ...form,
  153. T_product: product,
  154. User_tokey: globalStore.GET_User_tokey
  155. })
  156. }
  157. if (res.Code === 200) {
  158. ElMessage.success(`${isNew.value ? '添加' : '修改'}合同成功!!`)
  159. nextTick(() => {
  160. drawerRef.value?.closeDrawer()
  161. emit('onTableList')
  162. resetForm(ruleFormRef.value)
  163. isNew.value = true
  164. })
  165. }
  166. } else {
  167. return false
  168. }
  169. })
  170. }
  171. // 增加产品
  172. // dialog
  173. const initParam = reactive({
  174. User_tokey: globalStore.GET_User_tokey,
  175. T_name: '',
  176. T_model: '',
  177. T_class: '',
  178. page: 0,
  179. page_z: 20
  180. })
  181. const classOptions = ref<any[]>([])
  182. const modelOptions = ref<any[]>([])
  183. // 获取产品分类
  184. const getProductClassList = async () => {
  185. const res: any = await Storehouse_ProductClass_List({ page: 1, page_z: 999 })
  186. classOptions.value = res.Data.Data
  187. }
  188. // 获取产品型号
  189. const getProductModelList = async () => {
  190. const res: any = await Storehouse_Product_Model_List({ T_name: autoSelect.value })
  191. modelOptions.value = res.Data.map((item: any, index: number) => {
  192. return {
  193. value: item,
  194. index: index
  195. }
  196. })
  197. }
  198. const AddProductionDetailed = () => {
  199. // getProductList()
  200. !classOptions.value.length && getProductClassList()
  201. drawerProductRef.value?.openDrawer()
  202. }
  203. // 搜索模型
  204. const searchModelHandle = () => {
  205. total = 0
  206. initParam.page = 1
  207. tableProductData.value = []
  208. getProductList()
  209. }
  210. // 保存选中的数据id,row-key就是要指定一个key标识这一行的数据
  211. const getRowKey = (row: any) => {
  212. return row.Id
  213. }
  214. // 选中的产品
  215. const ProductselectionChange = (row: any[]) => {
  216. if (!isNew.value) {
  217. const newProduct = row.find(
  218. (product: any) => tableData.value.findIndex((item: any) => item.Id === product.Id) === -1
  219. )
  220. newProduct && tableData.value.push({ ...newProduct })
  221. } else {
  222. tableData.value = row
  223. }
  224. }
  225. // 加载第二个抽屉数据
  226. const load = () => {
  227. if (initParam.page && total === tableProductData.value.length) {
  228. ElMessage.warning('没有更多数据了!!')
  229. return
  230. }
  231. initParam.page++
  232. getProductList()
  233. }
  234. let total = 0
  235. const tableProductData = ref<any[]>([])
  236. const getProductList = async () => {
  237. const res: any = await Storehouse_Product_List({ ...initParam, T_name: autoSelect.value })
  238. tableProductData.value.push(...res.Data.Data)
  239. total = res.Data.Num
  240. if (!isNew.value) {
  241. // 设置产品的选中
  242. tableProductData.value.forEach((row: any) => {
  243. const matchedIndex = selectProductData.findIndex((item: any) => item.Id == row.Id)
  244. selectTable.value?.toggleRowSelection(row, matchedIndex != -1)
  245. })
  246. }
  247. }
  248. const tableData = ref<any[]>([])
  249. const columns = [
  250. { type: 'index', label: '序号', width: 80, align: 'center ' },
  251. { label: '产品图片', prop: 'T_img', align: 'center ', name: 'T_img' },
  252. { label: '产品名称', prop: 'T_name', align: 'center ' },
  253. { label: '产品分类', prop: 'T_class_name', align: 'center ' },
  254. { label: '产品型号', prop: 'T_model', align: 'center ' },
  255. { label: '产品规格', prop: 'T_spec', align: 'center ' },
  256. { label: '是否关联SN', prop: 'T_relation_sn', align: 'center ', width: 120, name: 'T_relation_sn' },
  257. { label: '*数量', prop: 'count', align: 'center ', name: 'count' },
  258. { prop: 'operation', label: '操作', width: 80, fixed: 'right' }
  259. ]
  260. const productColumns = [
  261. { type: 'selection', width: 80 },
  262. { prop: 'T_img', label: '产品图片', name: 'T_img' },
  263. { prop: 'T_name', label: '产品名称' },
  264. { prop: 'T_class_name', label: '产品分类' },
  265. { prop: 'T_model', label: '产品型号', ellipsis: true },
  266. { prop: 'T_spec', label: '产品规格' },
  267. { prop: 'T_relation_sn', label: '关联SN', name: 'T_relation_sn' },
  268. { prop: 'T_remark', label: '备注', ellipsis: true }
  269. ]
  270. // 自动搜索
  271. const autoSelect = ref('')
  272. let timeout: NodeJS.Timeout
  273. const querySearchAsync = async (queryString: string, cb: (arg: any) => void) => {
  274. clearTimeout(timeout)
  275. timeout = setTimeout(async () => {
  276. const results = await getNameAsync(queryString)
  277. console.log(autoSelect, queryString)
  278. cb(results)
  279. }, 2000)
  280. }
  281. const getNameAsync = async (str: string): Promise<any> => {
  282. const res: any = await Storehouse_Product_Name_List({ T_name: str, T_class: initParam.T_class })
  283. if (!res.Data) return
  284. return res.Data.map((item: any, index: number) => {
  285. return {
  286. value: item,
  287. index: index
  288. }
  289. })
  290. }
  291. const handleSelect = (item: any) => {
  292. console.log(item, autoSelect.value)
  293. // initParam.T_name = item.value
  294. getProductModelList()
  295. }
  296. defineExpose({
  297. openDrawer
  298. })
  299. </script>
  300. <template>
  301. <div class="contract-form">
  302. <Drawer ref="drawerRef" :handleClose="callbackDrawer" size="80%">
  303. <template #header="{ params }">
  304. <h4 :id="params.titleId" :class="params.titleClass">{{ isNew ? '添加' : '编辑' }} - 合同</h4>
  305. </template>
  306. <el-form ref="ruleFormRef" :model="form" :rules="rules">
  307. <el-divider border-style="dashed" />
  308. <el-form-item label="合同编号:" :label-width="formLabelWidth" prop="T_number">
  309. <el-input
  310. v-model="form.T_number"
  311. type="text"
  312. :disabled="!isNew"
  313. autocomplete="off"
  314. placeholder="请输入合同编号"
  315. class="w-50"
  316. />
  317. </el-form-item>
  318. <el-form-item label="客户名称:" :label-width="formLabelWidth" prop="T_customer">
  319. <el-input
  320. v-model="form.T_customer"
  321. type="text"
  322. autocomplete="off"
  323. placeholder="请输入客户名称"
  324. class="w-50"
  325. />
  326. </el-form-item>
  327. <el-form-item label="合同类型:" :label-width="formLabelWidth" prop="T_type">
  328. <el-select v-model="form.T_type" class="w-50" clearable :disabled="!isNew" placeholder="请选择合同类型~">
  329. <el-option label="销售合同" :value="1" />
  330. <el-option label="验证合同" :value="2" />
  331. </el-select>
  332. </el-form-item>
  333. <el-form-item label="产品明细:" :label-width="formLabelWidth" prop="T_product">
  334. <el-table
  335. :data="tableData"
  336. style="width: 100%"
  337. border
  338. stripe
  339. :header-cell-style="{
  340. background: '#909399',
  341. height: '50px',
  342. color: '#fff'
  343. }"
  344. >
  345. <template v-for="item in columns" :key="item.prop">
  346. <el-table-column v-bind="item" v-if="item.fixed !== 'right'">
  347. <template #header v-if="item.prop === 'count'">
  348. <span style="color: red">*数量</span>
  349. </template>
  350. <template #default="{ row }" v-if="item.prop === item.name">
  351. <el-input
  352. v-if="item.prop === 'count'"
  353. v-model.number="row.count"
  354. type="text"
  355. autocomplete="off"
  356. @blur="blurHandle"
  357. />
  358. <span v-if="item.prop === 'T_relation_sn'">
  359. <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
  360. <el-tag v-else type="success" effect="dark">否</el-tag>
  361. </span>
  362. <el-image v-if="item.prop === 'T_img'" style="height: 50px" :src="row.T_img" fit="cover" />
  363. </template>
  364. </el-table-column>
  365. <el-table-column v-bind="item" v-if="item.fixed === 'right'">
  366. <template #default="{ row }">
  367. <el-button link type="danger" size="small" :icon="Delete" @click="deleteProduct(row)">删除</el-button>
  368. </template>
  369. </el-table-column>
  370. </template>
  371. <template #append>
  372. <el-button type="primary" @click="AddProductionDetailed">
  373. <el-icon><Plus /></el-icon><span style="margin-left: 6px">添加产品</span>
  374. </el-button>
  375. </template>
  376. </el-table>
  377. </el-form-item>
  378. <el-form-item label="业务日期:" :label-width="formLabelWidth" prop="T_date">
  379. <el-date-picker
  380. class="my-date-picker"
  381. style="width: 21.5rem"
  382. v-model="form.T_date"
  383. type="date"
  384. placeholder="选择日期"
  385. format="YYYY-MM-DD"
  386. value-format="YYYY-MM-DD"
  387. />
  388. </el-form-item>
  389. <el-form-item label="合同金额:" :label-width="formLabelWidth" prop="T_money">
  390. <el-input v-model="form.T_money" type="text" autocomplete="off" placeholder="请输入合同金额" class="w-50" />
  391. </el-form-item>
  392. <el-form-item label="合同备注:" :label-width="formLabelWidth" prop="T_remark">
  393. <el-input
  394. class="w-50"
  395. v-model="form.T_remark"
  396. :autosize="{ minRows: 4, maxRows: 6 }"
  397. type="textarea"
  398. placeholder="请输入备注信息"
  399. />
  400. </el-form-item>
  401. <el-form-item label="上传附件:" :label-width="formLabelWidth" prop="T_pdf">
  402. <Upload
  403. v-model="form.T_pdf"
  404. class="w-50"
  405. ref="uploadRef"
  406. :limit="1"
  407. successText="文件上传成功!!"
  408. errorText="文件上传失败"
  409. accept=".pdf"
  410. listType="text"
  411. >
  412. <el-button type="primary">上传文件</el-button>
  413. <template #tip> 只能上传pdf格式文件!!</template>
  414. </Upload>
  415. </el-form-item>
  416. <div class="btn">
  417. <el-divider>
  418. <el-button>取消</el-button>
  419. <el-button v-if="isNew" color="#626aef" @click="AddContract(ruleFormRef)">提交</el-button>
  420. <el-button v-else color="#626aef" @click="AddContract(ruleFormRef)">修改</el-button>
  421. </el-divider>
  422. </div>
  423. <Drawer ref="drawerProductRef" :handleClose="callbackProductDrawer" size="70%">
  424. <template #header="{ params }">
  425. <h4 :id="params.titleId" :class="params.titleClass">选择产品</h4>
  426. </template>
  427. <el-card class="box-card" shadow="never">
  428. <template #header>
  429. <div class="input-suffix">
  430. <el-row :gutter="20" style="margin-bottom: 0">
  431. <el-col :xl="5" :lg="8" :md="10" class="d-flex">
  432. <span class="inline-flex items-center">产品分类:</span>
  433. <el-select v-model="initParam.T_class" clearable placeholder="请选择分类~">
  434. <el-option v-for="item in classOptions" :key="item.Id" :label="item.T_name" :value="item.Id" />
  435. </el-select>
  436. </el-col>
  437. <el-col :xl="7" :lg="8" :md="10" class="d-flex">
  438. <span class="inline-flex items-center">产品名称:</span>
  439. <el-autocomplete
  440. v-model="autoSelect"
  441. clearable
  442. :fetch-suggestions="querySearchAsync"
  443. placeholder="Please input"
  444. :debounce="2000"
  445. :trigger-on-focus="false"
  446. @select="handleSelect"
  447. />
  448. </el-col>
  449. <el-col :xl="7" :lg="8" :md="12" class="d-flex">
  450. <span class="inline-flex items-center">产品型号:</span>
  451. <el-select v-model="initParam.T_model" clearable placeholder="请选择型号~">
  452. <el-option
  453. v-for="item in modelOptions"
  454. :key="item.index"
  455. :label="item.value"
  456. :value="item.index"
  457. />
  458. </el-select>
  459. <el-button type="primary" @click="searchModelHandle">搜索</el-button>
  460. </el-col>
  461. </el-row>
  462. </div>
  463. </template>
  464. <el-table
  465. ref="selectTable"
  466. :row-key="getRowKey"
  467. :data="tableProductData"
  468. style="width: 100%; height: 99%"
  469. v-el-table-infinite-scroll="load"
  470. @selection-change="ProductselectionChange"
  471. >
  472. <template v-for="item in productColumns" :key="item">
  473. <el-table-column
  474. v-if="item.type === 'index' || item.type === 'selection'"
  475. align="center"
  476. v-bind="item"
  477. />
  478. <el-table-column v-if="!item.ellipsis && item.prop" v-bind="item">
  479. <template #default="{ row }">
  480. <span v-if="item.prop === 'T_relation_sn'">
  481. <el-tag v-if="row.T_relation_sn === 1" effect="dark">是</el-tag>
  482. <el-tag v-else type="success" effect="dark">否</el-tag>
  483. </span>
  484. <el-image v-if="item.prop === 'T_img'" style="height: 50px" :src="row.T_img" fit="cover" />
  485. </template>
  486. </el-table-column>
  487. <el-table-column v-if="item.ellipsis && item.prop === 'T_model'" align="center" v-bind="item">
  488. <template #default="{ row }">
  489. <el-tooltip effect="dark" :content="row.T_model" placement="bottom">
  490. {{ row.T_model }}
  491. </el-tooltip>
  492. </template>
  493. </el-table-column>
  494. <el-table-column v-if="item.ellipsis && item.prop === 'T_remark'" align="center" v-bind="item">
  495. <template #default="{ row }">
  496. <el-tooltip effect="customized" placement="left">
  497. <template #content>
  498. <div class="tooltip-content">{{ row.T_remark }}</div>
  499. </template>
  500. {{ row.T_remark }}
  501. </el-tooltip>
  502. </template>
  503. </el-table-column>
  504. </template>
  505. </el-table>
  506. </el-card>
  507. </Drawer>
  508. </el-form>
  509. </Drawer>
  510. </div>
  511. </template>
  512. <style scoped lang="scss">
  513. .tooltip-content {
  514. max-width: 500px;
  515. overflow-y: auto;
  516. }
  517. .contract-form {
  518. :deep(.el-table--border .el-table__cell) {
  519. border-right: 0;
  520. }
  521. :deep(.table-header),
  522. :deep(.card) {
  523. border: 0;
  524. }
  525. .box-card {
  526. height: 100%;
  527. :deep(.el-card__body) {
  528. height: calc(100% - 70px);
  529. }
  530. }
  531. .btn {
  532. margin-top: 32px;
  533. display: flex;
  534. justify-content: center;
  535. .el-button {
  536. padding: 0 32px;
  537. }
  538. }
  539. .w-50 {
  540. width: 21.5rem;
  541. }
  542. .input-suffix {
  543. width: 100%;
  544. .inline-flex {
  545. white-space: nowrap;
  546. }
  547. .d-flex {
  548. display: flex;
  549. }
  550. }
  551. }
  552. </style>