created.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <template>
  2. <view class="common-page" style="padding: 0;">
  3. <view class="form-page">
  4. <u-form ref="formRef" :model="form" :rules="rules" labelPosition="left" labelWidth="85px">
  5. <u-form-item label="快递单号:" prop="waybillCode" required>
  6. <template v-if="isReadonly && form.waybillCode">
  7. <text class="readonly-text">{{ form.waybillCode }}</text>
  8. </template>
  9. <u-input v-else v-model="form.waybillCode" placeholder="请输入快递单号" clearable></u-input>
  10. </u-form-item>
  11. <!-- <u-form-item label="订单编号:" prop="orderId" v-if="form.orderId || isReadonly">
  12. <template v-if="isReadonly && form.orderId">
  13. <text class="clickable-text" @click="goToOrderDetail">{{ form.orderId }}</text>
  14. </template>
  15. <u-input v-else v-model="form.orderId" placeholder="请输入订单编号" clearable></u-input>
  16. </u-form-item> -->
  17. <u-form-item label="承运商:" prop="carrier" required>
  18. <u-input v-model="carrierText" readonly suffixIcon="arrow-down" placeholder="请选择"
  19. @click="!isReadonly && (showCarrierPicker = true)"></u-input>
  20. </u-form-item>
  21. <u-form-item label="验货状态:" prop="verifyStatus" required>
  22. <u-input v-model="verifyText" readonly suffixIcon="arrow-down" placeholder="请选择"
  23. @click="showVerifyPicker = true"></u-input>
  24. </u-form-item>
  25. <u-form-item label="任务类型:" prop="taskType" required>
  26. <u-input v-model="taskTypeText" readonly suffixIcon="arrow-down" placeholder="请选择"
  27. @click="showTaskTypePicker = true"></u-input>
  28. </u-form-item>
  29. <u-form-item label="任务详情:" prop="taskDetail" required>
  30. <u-textarea v-model="form.taskDetail" placeholder="请输入" :height="120"
  31. :autoHeight="true"></u-textarea>
  32. </u-form-item>
  33. <u-form-item label="上传图片:" prop="images" required>
  34. <cy-upload :filename="form.images" @update:filename="form.images = $event" />
  35. </u-form-item>
  36. <view class="divider"></view>
  37. <u-form-item label="指派给:" prop="assignTo">
  38. <u-input v-model="assignText" readonly suffixIcon="arrow-down" placeholder="请选择 (非必填)"
  39. @click="openAssignPicker"></u-input>
  40. </u-form-item>
  41. </u-form>
  42. </view>
  43. <u-picker :show="showCarrierPicker" :columns="[carrierOptions]" title="选择承运商" @confirm="onPickCarrier"
  44. @cancel="showCarrierPicker = false" @close="showCarrierPicker = false"></u-picker>
  45. <u-picker :show="showVerifyPicker" :columns="[verifyOptions]" title="选择验货状态" @confirm="onPickVerify"
  46. @cancel="showVerifyPicker = false" @close="showVerifyPicker = false"></u-picker>
  47. <u-picker :show="showTaskTypePicker" :columns="[taskTypeOptions]" title="选择任务类型" @confirm="onPickTaskType"
  48. @cancel="showTaskTypePicker = false" @close="showTaskTypePicker = false"></u-picker>
  49. <!-- 指派给选择器 - 使用 popup + checkbox 实现多选 -->
  50. <u-popup v-model:show="showAssignPicker" mode="bottom" :round="20">
  51. <view class="assign-picker-content">
  52. <view class="assign-header">
  53. <text class="title">选择指派人</text>
  54. <view class="actions">
  55. <text class="cancel-btn" @click="showAssignPicker = false">取消</text>
  56. <text class="confirm-btn" @click="confirmAssign">确定</text>
  57. </view>
  58. </view>
  59. <view class="assign-body">
  60. <view
  61. v-for="(item, index) in assignOptions"
  62. :key="index"
  63. class="assign-item"
  64. @click="toggleAssign(index)"
  65. >
  66. <text class="item-text">{{ item.text }}</text>
  67. <view
  68. class="custom-checkbox"
  69. :class="{ checked: tempAssignIndexes.includes(index) }"
  70. >
  71. </view>
  72. </view>
  73. </view>
  74. </view>
  75. </u-popup>
  76. <view class="fixed-bottom">
  77. <u-button type="warning" size="large" @click="onCancel">取消</u-button>
  78. <u-button type="primary" size="large" :loading="submitting" @click="onSubmit">提交</u-button>
  79. </view>
  80. </view>
  81. </template>
  82. <script setup>
  83. import { ref } from 'vue'
  84. import { onLoad } from '@dcloudio/uni-app'
  85. import CyUpload from '@/components/cy-upload/index.vue'
  86. const formRef = ref(null)
  87. const submitting = ref(false)
  88. const form = ref({
  89. waybillCode: '',
  90. orderId: '',
  91. expressType: '',
  92. verifyStatus: '',
  93. taskType: '',
  94. taskDetail: '',
  95. images: [],
  96. assignTo: []
  97. })
  98. const rules = {
  99. waybillCode: [{ required: true, message: '请输入快递单号' }],
  100. expressType: [{ required: true, message: '请选择承运商' }],
  101. verifyStatus: [{ required: true, message: '请选择验货状态' }],
  102. taskType: [{ required: true, message: '请选择任务类型' }],
  103. taskDetail: [{ required: true, message: '请输入任务详情' }],
  104. assignTo: [{ required: false, message: '请选择指派对象' }]
  105. }
  106. const carrierOptions = ref([])
  107. const verifyOptions = [
  108. { text: '已验货', value: 1 },
  109. { text: '未验货', value: 0 }
  110. ]
  111. const taskTypeOptions = ref([])
  112. const assignOptions = ref([{ text: '默认(不指派)', value: '' }])
  113. // 获取快递公司字典
  114. const getCarrierOptions = async () => {
  115. try {
  116. const res = await uni.$u.http.get('/system/dict/data/type/shop_order_express_name')
  117. if (res.code === 200 && res.data) {
  118. carrierOptions.value = res.data.map(item => ({
  119. text: item.dictLabel,
  120. value: item.dictValue
  121. }))
  122. }
  123. } catch (e) {
  124. console.error('获取快递公司失败', e)
  125. }
  126. }
  127. // 获取任务类型字典
  128. const getTaskTypeOptions = async () => {
  129. try {
  130. const res = await uni.$u.http.get('/system/dict/data/type/sell_task_type')
  131. if (res.code === 200 && res.data) {
  132. taskTypeOptions.value = res.data.map(item => ({
  133. text: item.dictLabel,
  134. value: parseInt(item.dictValue)
  135. }))
  136. }
  137. } catch (e) {
  138. console.error('获取任务类型失败', e)
  139. }
  140. }
  141. // 获取指派人列表
  142. const getAssignOptions = async () => {
  143. try {
  144. const res = await uni.$u.http.get('/system/user/list', {
  145. params: {
  146. pageSize: 1000
  147. }
  148. })
  149. if (res.code === 200 && res.rows) {
  150. assignOptions.value = res.rows.map(item => ({
  151. text: item.nickName,
  152. value: item.userId
  153. }))
  154. }
  155. } catch (e) {
  156. console.error('获取指派人失败', e)
  157. }
  158. }
  159. const showCarrierPicker = ref(false)
  160. const showVerifyPicker = ref(false)
  161. const showTaskTypePicker = ref(false)
  162. const showAssignPicker = ref(false)
  163. // 临时存储选中的索引数组
  164. const tempAssignIndexes = ref([])
  165. const carrierText = ref('')
  166. const verifyText = ref('')
  167. const taskTypeText = ref('')
  168. const assignText = ref('')
  169. const onPickCarrier = (e) => {
  170. const cell = e.value?.[0]
  171. form.value.expressType = cell?.value || ''
  172. carrierText.value = cell?.text || ''
  173. showCarrierPicker.value = false
  174. console.log('选择承运商:', form.value.expressType, carrierText.value)
  175. }
  176. const onPickVerify = (e) => {
  177. const cell = e.value?.[0]
  178. form.value.verifyStatus = cell?.value || ''
  179. verifyText.value = cell?.text || ''
  180. showVerifyPicker.value = false
  181. console.log('选择验货状态:', form.value.verifyStatus, verifyText.value)
  182. }
  183. const onPickTaskType = (e) => {
  184. const cell = e.value?.[0]
  185. form.value.taskType = cell?.value || ''
  186. taskTypeText.value = cell?.text || ''
  187. showTaskTypePicker.value = false
  188. console.log('选择任务类型:', form.value.taskType, taskTypeText.value)
  189. }
  190. // 打开指派人选择器时,初始化临时选中状态
  191. const openAssignPicker = () => {
  192. if (isReadonly.value) return
  193. // 根据当前已选择的值,初始化 tempAssignIndexes
  194. tempAssignIndexes.value = []
  195. if (form.value.assignTo && form.value.assignTo.length > 0) {
  196. assignOptions.value.forEach((item, index) => {
  197. if (form.value.assignTo.includes(item.value)) {
  198. tempAssignIndexes.value.push(index)
  199. }
  200. })
  201. }
  202. showAssignPicker.value = true
  203. console.log('打开选择器,已选中索引:', tempAssignIndexes.value)
  204. }
  205. const onPickAssign = (e) => {
  206. // 这个方法不再使用,改用 toggleAssign 和 confirmAssign
  207. }
  208. // 切换选中状态
  209. const toggleAssign = (index) => {
  210. const idx = tempAssignIndexes.value.indexOf(index)
  211. if (idx > -1) {
  212. tempAssignIndexes.value.splice(idx, 1)
  213. } else {
  214. tempAssignIndexes.value.push(index)
  215. }
  216. }
  217. // 确认选择
  218. const confirmAssign = () => {
  219. const selectedList = []
  220. tempAssignIndexes.value.forEach(index => {
  221. selectedList.push(assignOptions.value[index])
  222. })
  223. form.value.assignTo = selectedList.map(item => item.value)
  224. assignText.value = selectedList.map(item => item.text).join(',')
  225. showAssignPicker.value = false
  226. console.log('选择指派人:', form.value.assignTo, assignText.value)
  227. }
  228. const onCancel = () => {
  229. uni.navigateBack()
  230. }
  231. const goToOrderDetail = () => {
  232. if (!form.value.orderId) return
  233. uni.navigateTo({
  234. url: `/pages/index/detail/index?id=${form.value.orderId}`
  235. })
  236. }
  237. const workOrderId = ref('')
  238. const isEdit = ref(false)
  239. const isReadonly = ref(false)
  240. const onLoadDetail = async (id) => {
  241. try {
  242. const res = await uni.$u.http.get('/app/workOrder/getWorkOrderDetail', {
  243. params: { workOrderId: id }
  244. })
  245. if (res.code === 200 && res.data) {
  246. const data = res.data
  247. form.value.waybillCode = data.waybillCode || ''
  248. form.value.orderId = data.orderId || ''
  249. form.value.taskDetail = data.deatil || data.taskDetail || ''
  250. form.value.images = data.imgInfo?.imgUrlList.length ? data.imgInfo.imgUrlList : []
  251. // 加载任务类型字典
  252. await getTaskTypeOptions()
  253. // 回显任务类型
  254. if (data.taskType) {
  255. form.value.taskType = data.taskType
  256. const matchedType = taskTypeOptions.value.find(item => item.value === data.taskType)
  257. if (matchedType) {
  258. taskTypeText.value = matchedType.text
  259. }
  260. }
  261. // 回显指派人(多选)
  262. if (data.handleUsers && data.handleUsers.length > 0) {
  263. form.value.assignTo = data.handleUsers.map(user => user.userId)
  264. assignText.value = data.handleUsers.map(user => user.userName).join(',')
  265. }
  266. if (data.inspectionStatus !== undefined && data.inspectionStatus !== null) {
  267. form.value.verifyStatus = data.inspectionStatus
  268. const matchedVerify = verifyOptions.find(item => item.value === data.inspectionStatus)
  269. if (matchedVerify) {
  270. verifyText.value = matchedVerify.text
  271. }
  272. }
  273. // 回显承运商
  274. if (data.expressType) {
  275. form.value.expressType = data.expressType
  276. // 从字典中查找对应的文本
  277. const matchedCarrier = carrierOptions.value.find(item => {
  278. return item.value === data.expressType
  279. })
  280. if (matchedCarrier) {
  281. carrierText.value = matchedCarrier.text
  282. }
  283. }
  284. }
  285. } catch (e) {
  286. console.error(e)
  287. }
  288. }
  289. const onSubmit = async () => {
  290. if (submitting.value) return
  291. try {
  292. await formRef.value?.validate?.()
  293. } catch (e) {
  294. return
  295. }
  296. submitting.value = true
  297. const payload = {
  298. taskType: form.value.taskType || 9,
  299. inspectionStatus: form.value.verifyStatus ?? 0,
  300. deatil: form.value.taskDetail,
  301. expressType: form.value.expressType || 0,
  302. waybillCode: form.value.waybillCode,
  303. orderId: form.value.orderId || '',
  304. type: 1, // 工单类型:1-卖书
  305. imgInfo: form.value.images.length > 0 ? form.value.images : [],
  306. handleUsers: form.value.assignTo.length > 0 ? form.value.assignTo : []
  307. }
  308. if (isEdit.value) {
  309. payload.id = workOrderId.value
  310. payload.updateType = 1
  311. }
  312. const apiUrl = isEdit.value ? '/app/workOrder/update' : '/app/workOrder/createWorkOrder'
  313. try {
  314. const res = await uni.$u.http.post(apiUrl, payload)
  315. if (res?.code === 200) {
  316. uni.$u.toast('提交成功')
  317. uni.navigateBack()
  318. } else {
  319. uni.$u.toast(res?.msg || '提交失败')
  320. }
  321. } catch (err) {
  322. uni.$u.toast('网络错误,已本地保存')
  323. } finally {
  324. submitting.value = false
  325. }
  326. }
  327. onLoad((options) => {
  328. form.value.waybillCode = options?.waybillCode || ''
  329. form.value.orderId = options?.orderId || ''
  330. if (options?.readonly == 1) {
  331. isReadonly.value = true
  332. // 尝试回显承运商
  333. if (options?.expressType !== undefined && options?.expressType !== null) {
  334. form.value.expressType = parseInt(options.expressType)
  335. // 从字典中查找对应的文本
  336. const matchedCarrier = carrierOptions.value.find(item => item.value === options.expressType)
  337. if (matchedCarrier) {
  338. carrierText.value = matchedCarrier.text
  339. }
  340. }
  341. }
  342. if (options?.mode === 'edit' && options?.workOrderId) {
  343. isEdit.value = true
  344. isReadonly.value = true
  345. workOrderId.value = options.workOrderId
  346. uni.setNavigationBarTitle({ title: '编辑工单' })
  347. onLoadDetail(options.workOrderId)
  348. } else {
  349. uni.setNavigationBarTitle({ title: '提交工单' })
  350. }
  351. // 加载字典数据
  352. getCarrierOptions()
  353. getTaskTypeOptions()
  354. getAssignOptions()
  355. })
  356. </script>
  357. <style lang="scss" scoped>
  358. .form-page {
  359. padding: 24rpx 24rpx 140rpx;
  360. background: #ffffff;
  361. }
  362. .divider {
  363. height: 16rpx;
  364. background: #f4f5f5;
  365. margin: 20rpx 0;
  366. }
  367. // 指派人多选选择器样式
  368. .assign-picker-content {
  369. padding: 0 24rpx;
  370. background: #fff;
  371. border-radius: 20rpx 20rpx 0 0;
  372. .assign-header {
  373. display: flex;
  374. justify-content: space-between;
  375. align-items: center;
  376. padding: 24rpx 0;
  377. border-bottom: 1px solid #eee;
  378. .title {
  379. font-size: 32rpx;
  380. font-weight: bold;
  381. color: #333;
  382. }
  383. .actions {
  384. display: flex;
  385. gap: 24rpx;
  386. .cancel-btn {
  387. font-size: 28rpx;
  388. color: #666;
  389. padding: 8rpx 16rpx;
  390. }
  391. .confirm-btn {
  392. font-size: 28rpx;
  393. color: #2979ff;
  394. font-weight: 500;
  395. padding: 8rpx 16rpx;
  396. }
  397. }
  398. }
  399. .assign-body {
  400. max-height: 600rpx;
  401. overflow-y: auto;
  402. padding: 16rpx 0;
  403. .assign-item {
  404. display: flex;
  405. justify-content: space-between;
  406. align-items: center;
  407. padding: 24rpx 16rpx;
  408. border-bottom: 1px solid #f5f5f5;
  409. &:active {
  410. background: #f8f9fa;
  411. }
  412. .item-text {
  413. font-size: 30rpx;
  414. color: #333;
  415. }
  416. .custom-checkbox {
  417. width: 40rpx;
  418. height: 40rpx;
  419. border: 2px solid #c8c9cc;
  420. border-radius: 8rpx;
  421. position: relative;
  422. transition: all 0.2s;
  423. &.checked {
  424. background: #2979ff;
  425. border-color: #2979ff;
  426. &::after {
  427. content: '✓';
  428. position: absolute;
  429. top: 50%;
  430. left: 50%;
  431. transform: translate(-50%, -50%);
  432. color: #fff;
  433. font-size: 28rpx;
  434. font-weight: bold;
  435. }
  436. }
  437. }
  438. }
  439. }
  440. }
  441. .readonly-text {
  442. font-size: 28rpx;
  443. color: #333;
  444. line-height: 60rpx;
  445. }
  446. .clickable-text {
  447. font-size: 28rpx;
  448. color: #2979ff;
  449. line-height: 60rpx;
  450. }
  451. </style>