index.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. <route lang="json5" type="page">
  2. {
  3. layout: 'default',
  4. style: {
  5. navigationBarTitleText: '签字',
  6. navigationStyle: 'custom',
  7. },
  8. }
  9. </route>
  10. <template>
  11. <view class="sign-container">
  12. <!-- 导航栏 -->
  13. <NavBar>
  14. <template #title>
  15. <text class="nav-title">{{ title }}</text>
  16. </template>
  17. </NavBar>
  18. <!-- 内容区域 -->
  19. <scroll-view class="scroll-content" scroll-y>
  20. <!-- PDF 图片预览 -->
  21. <BackendPDFViewer
  22. ref="backendPdfViewerRef"
  23. :refId="refId"
  24. :templateId="templateId"
  25. viewerHeight="50vh"
  26. />
  27. <!-- 签名区域分割线 -->
  28. <view class="sign-divider"></view>
  29. <!-- 签名区域(三种状态:空/签名中/已签名) -->
  30. <view class="sign-section">
  31. <!-- 状态1:空状态 - 点击签名 -->
  32. <template v-if="signStatus === 'empty'">
  33. <view class="sign-header">
  34. <text class="sign-title-text">手写签名</text>
  35. </view>
  36. <view class="sign-view" @click="handleToSign">
  37. <text class="sign-view-text">点击签名</text>
  38. </view>
  39. </template>
  40. <!-- 状态2:签名中 - 显示画布 -->
  41. <template v-if="signStatus === 'signing'">
  42. <view class="sign-header">
  43. <text class="sign-title-text">手写签名</text>
  44. <view class="sign-header-actions">
  45. <button class="header-action-btn header-reset-btn" @click="clearCanvas">清除</button>
  46. <button class="header-action-btn header-cancel-btn" @click="handleCancelSign">
  47. 取消
  48. </button>
  49. <button class="header-action-btn header-confirm-btn" @click="confirmSign">
  50. 确认
  51. </button>
  52. </view>
  53. </view>
  54. <view class="sign-canvas-wrapper">
  55. <SignatureCanvas
  56. ref="signatureRef"
  57. canvas-id="signCanvas"
  58. :width="canvasWidth"
  59. :height="178"
  60. @signed="onCanvasSigned"
  61. />
  62. </view>
  63. </template>
  64. <!-- 状态3:已签名 - 显示签名图片 -->
  65. <template v-if="signStatus === 'signed'">
  66. <view class="sign-header">
  67. <text class="sign-title-text">签名时间:</text>
  68. <text class="sign-title-text">{{ signTime }}</text>
  69. </view>
  70. <view class="sign-img">
  71. <view class="sign-img-del" @click="handleDelSign">X</view>
  72. <image :src="showSignImg" mode="aspectFit" class="sign-image" :style="signImgStyle" />
  73. </view>
  74. </template>
  75. </view>
  76. <!-- 联系信息表单(服务单/受理单类型) -->
  77. <view v-if="routeType === 'FWD'" class="form-section">
  78. <view class="form-item">
  79. <text class="form-label">接收人手机号:</text>
  80. <input
  81. v-model="fwdInputPhone"
  82. class="form-input"
  83. type="number"
  84. maxlength="11"
  85. placeholder="请输入接收人手机号"
  86. />
  87. </view>
  88. </view>
  89. </scroll-view>
  90. <!-- 底部按钮 -->
  91. <view class="footer-bar">
  92. <button class="footer-btn more-btn" @click="handlePushOrder">更多操作</button>
  93. <button v-show="isSigned == '0'" class="footer-btn confirm-btn" @click="signSubmit">
  94. {{ signButtonText }}
  95. </button>
  96. <button v-show="isSigned == '1'" class="footer-btn resign-btn" @click="signSubmit">
  97. 重新签名
  98. </button>
  99. </view>
  100. <!-- 底部操作面板 -->
  101. <wd-action-sheet
  102. v-model="showActionSheet"
  103. :actions="actionSheetActions"
  104. @select="handleActionSelect"
  105. />
  106. <!-- 服务单接收人确认弹窗 -->
  107. <view v-if="showFwdPopup" class="popup-overlay" @click="closeFwdPopup">
  108. <view class="popup-content" @click.stop>
  109. <text class="popup-title">确认提交</text>
  110. <text class="popup-message">是否确认签约代表签名?</text>
  111. <view class="popup-actions">
  112. <button class="action-btn cancel-btn" @click="closeFwdPopup">取消</button>
  113. <button class="action-btn confirm-btn" @click="confirmFwdSubmit">确定</button>
  114. </view>
  115. </view>
  116. </view>
  117. <!-- 推送任务单弹窗 -->
  118. <view v-if="showInputPopup" class="popup-overlay" @click="closeInputPopup">
  119. <view class="popup-content input-popup" @click.stop>
  120. <text class="popup-title">推送任务单</text>
  121. <view class="input-row">
  122. <text class="row-label">接收人名称:</text>
  123. <input class="row-input" v-model="inputName" placeholder="请输入接收人名称" />
  124. </view>
  125. <view class="input-row">
  126. <text class="row-label">接收人手机号:</text>
  127. <input
  128. class="row-input"
  129. v-model="inputPhone"
  130. type="number"
  131. maxlength="11"
  132. placeholder="请输入接收人手机号"
  133. />
  134. </view>
  135. <view v-if="routeType !== 'ZXXX'" class="input-row">
  136. <text class="row-label">电子邮箱:</text>
  137. <input
  138. class="row-input"
  139. v-model="inputEmail"
  140. type="text"
  141. placeholder="请输入电子邮箱(选填)"
  142. />
  143. </view>
  144. <view class="popup-actions">
  145. <button class="action-btn cancel-btn" @click="closeInputPopup">取消</button>
  146. <button class="action-btn confirm-btn" @click="handlePushOrderSubmit">确定</button>
  147. </view>
  148. </view>
  149. </view>
  150. </view>
  151. </template>
  152. <script lang="ts" setup>
  153. import { ref, computed, onMounted, nextTick } from 'vue'
  154. import { onLoad } from '@dcloudio/uni-app'
  155. import SignatureCanvas from '@/components/Signature/SignatureCanvas.vue'
  156. import BackendPDFViewer from '@/components/BackendPDFViewer/index.vue'
  157. import NavBar from '@/components/NavBar/NavBar.vue'
  158. import { useUserStore } from '@/store/user'
  159. import { useConfigStore } from '@/store/config'
  160. import { getGcConfig, getTaskOrderImg, pushPressure2TaskOrder, uploadSignImage } from '@/api/sign'
  161. import { getTaskOrderReport } from '@/api/orderReport'
  162. import { SignFuncName, requestFunc } from '@/api/ApiRouter/sign'
  163. import { TaskOrderFuncName, requestFunc as taskOrderRequestFunc } from '@/api/ApiRouter/taskOrder'
  164. import {
  165. requestFunc as SecurityRequestFunc,
  166. SecurityCheckFuncName,
  167. } from '@/api/ApiRouter/taskOrderSecurityCheck'
  168. import { EquipmentType } from '@/utils/dictMap'
  169. const equipType = useConfigStore().getEquipType()
  170. const title = ref('')
  171. const routeType = ref<'FWD' | 'JYRS' | 'AQJC' | 'ZXXX'>()
  172. const orderId = ref('')
  173. const orderItemId = ref('')
  174. const securityCheckId = ref('')
  175. const reportId = ref('')
  176. const unitContact = ref('')
  177. const unitPhone = ref('')
  178. const receiverEmail = ref('')
  179. const templateId = ref('')
  180. const refId = ref('')
  181. const showSignImg = ref('')
  182. const signImg = ref('')
  183. const uploadedSignUrl = ref('')
  184. const signImgStyle = ref<any>({})
  185. const signTime = ref('')
  186. const fwdInputPhone = ref('')
  187. const showFwdPopup = ref(false)
  188. const showInputPopup = ref(false)
  189. const showActionSheet = ref(false)
  190. const actionSheetActions = ref<any[]>([])
  191. const canvasWidth = ref(300)
  192. const inputName = ref('')
  193. const inputPhone = ref('')
  194. const inputEmail = ref('')
  195. const gcConfig = ref<any>(null)
  196. const backendPdfViewerRef = ref<any>(null)
  197. // 签名区域状态:empty | signing | signed
  198. const signStatus = ref<'empty' | 'signing' | 'signed'>('empty')
  199. const signatureRef = ref<any>(null)
  200. const userStore = useUserStore()
  201. const userInfo = computed(() => userStore.userInfo)
  202. const titleTextMap: Record<string, string> = {
  203. FWD: '服务单/受理单',
  204. JYRS: '检验结果告知',
  205. AQJC: '安全检查记录',
  206. ZXXX: '重大问题线索告知',
  207. }
  208. const businessTypeMap: Record<string, number> = {
  209. FWD: 100,
  210. JYRS: 200,
  211. AQJC: 300,
  212. ZXXX: 400,
  213. }
  214. const signButtonTextMap: Record<string, string> = {
  215. FWD: '签约代表签名',
  216. JYRS: '客户代表签名',
  217. AQJC: '受检单位签名',
  218. ZXXX: '受检单位签名',
  219. }
  220. const confirmTextMap: Record<string, string> = {
  221. FWD: '是否确认签约代表签名?',
  222. JYRS: '是否确认客户代表签名?',
  223. AQJC: '是否确认受检单位签名?',
  224. ZXXX: '是否提交审核?',
  225. }
  226. const signButtonText = computed(() => {
  227. return routeType.value ? signButtonTextMap[routeType.value] || '签名' : '签名'
  228. })
  229. onLoad((options) => {
  230. const type = options?.type as string
  231. routeType.value = type as any
  232. title.value = titleTextMap[type] || '签字'
  233. orderId.value = options?.orderId || ''
  234. orderItemId.value = options?.orderItemId || ''
  235. securityCheckId.value = options?.securityCheckId || ''
  236. reportId.value = options?.reportId || ''
  237. unitContact.value = options?.unitContact || ''
  238. unitPhone.value = options?.unitPhone || ''
  239. receiverEmail.value = options?.receiverEmail || ''
  240. templateId.value = options?.templateId || ''
  241. fwdInputPhone.value = unitPhone.value
  242. inputName.value = unitContact.value
  243. inputPhone.value = unitPhone.value
  244. inputEmail.value = receiverEmail.value
  245. // 初始化画布宽度
  246. const sysInfo = uni.getSystemInfoSync()
  247. canvasWidth.value = sysInfo.windowWidth - 32
  248. })
  249. const orderReportId = ref('')
  250. const isSigned = ref('0')
  251. const getPreviewData = async () => {
  252. if (!routeType.value) {
  253. uni.showToast({ title: '必须选择签字文件类型', icon: 'error' })
  254. return
  255. }
  256. const orderDetail = await taskOrderRequestFunc(TaskOrderFuncName.TaskOrderDetail, equipType, {
  257. id: orderId.value,
  258. })
  259. const signFileList = orderDetail.data.signFileList || []
  260. const targetBusinessType = businessTypeMap[routeType.value]
  261. const signFile = signFileList.find((row: any) => row.businessType === targetBusinessType)
  262. if (signFile == null || signFile.isSignature == '0') {
  263. isSigned.value = '0'
  264. } else {
  265. isSigned.value = '1'
  266. }
  267. switch (routeType.value) {
  268. case 'FWD':
  269. const orderReportResp = await getTaskOrderReport({ taskOrderId: orderId.value })
  270. const orderReportRespList = orderReportResp.data?.list
  271. let orderReport = null
  272. if (orderReportRespList?.length) {
  273. orderReport = orderReportRespList[0]
  274. }
  275. templateId.value = orderReport?.templateId
  276. refId.value = orderReport?.acceptOrderId
  277. orderReportId.value = orderReport?.id || ''
  278. break
  279. case 'JYRS':
  280. const notificationformReport = orderDetail.data?.notificationformReport
  281. if (!notificationformReport) {
  282. const orderFormRes = await taskOrderRequestFunc(TaskOrderFuncName.GetOrderForm, equipType, {
  283. orderId: orderId.value,
  284. businessType: 1000,
  285. })
  286. const JYRSTemplateId = orderFormRes.data?.templateId || ''
  287. const addMajorIssuesRes = await taskOrderRequestFunc(
  288. TaskOrderFuncName.AddMajorIssues,
  289. equipType,
  290. {
  291. orderId: orderId.value,
  292. businessType: 1000,
  293. templateId: JYRSTemplateId,
  294. },
  295. )
  296. const JYRSReportId = addMajorIssuesRes.data
  297. templateId.value = JYRSTemplateId
  298. refId.value = (JYRSReportId as string) || ''
  299. } else {
  300. templateId.value = notificationformReport?.templateId || ''
  301. refId.value = notificationformReport?.id || ''
  302. }
  303. break
  304. case 'AQJC':
  305. const defaultTemplateResp = await SecurityRequestFunc(
  306. SecurityCheckFuncName.getTemplate,
  307. equipType,
  308. { orderId: orderId.value },
  309. )
  310. templateId.value = defaultTemplateResp.data?.templateId
  311. refId.value = securityCheckId.value
  312. break
  313. default:
  314. uni.showToast({ title: '请选择正确的签字文件类型', icon: 'error' })
  315. break
  316. }
  317. }
  318. const handleToSign = () => {
  319. signStatus.value = 'signing'
  320. // 重新计算画布宽度
  321. const sysInfo = uni.getSystemInfoSync()
  322. canvasWidth.value = sysInfo.windowWidth - 32
  323. }
  324. // 画布签名完成回调
  325. const onCanvasSigned = (hasContent: boolean) => {
  326. console.log('画布签名状态:', hasContent)
  327. }
  328. // 清除画布
  329. const clearCanvas = () => {
  330. if (signatureRef.value) {
  331. signatureRef.value.clear()
  332. }
  333. }
  334. // 取消签名 - 返回空状态
  335. const handleCancelSign = () => {
  336. signStatus.value = 'empty'
  337. }
  338. const base64ToFile = (base64Data: string, fileName: string = 'signature.png'): File | null => {
  339. // #ifdef H5
  340. try {
  341. const arr = base64Data.split(',')
  342. const mime = arr[0].match(/:(.*?);/)?.[1] || 'image/png'
  343. const bstr = atob(arr[1])
  344. let n = bstr.length
  345. const u8arr = new Uint8Array(n)
  346. while (n--) {
  347. u8arr[n] = bstr.charCodeAt(n)
  348. }
  349. return new File([u8arr], fileName, { type: mime })
  350. } catch (e) {
  351. console.error('base64 转 File 失败:', e)
  352. return null
  353. }
  354. // #endif
  355. }
  356. const base64ToTempFilePath = (base64Data: string): Promise<string> => {
  357. return new Promise((resolve, reject) => {
  358. // #ifdef H5
  359. const file = base64ToFile(base64Data)
  360. if (file) {
  361. resolve('') // H5 使用 File 对象
  362. } else {
  363. reject(new Error('转换失败'))
  364. }
  365. // #endif
  366. // #ifndef H5
  367. const fs = uni.getFileSystemManager()
  368. const filePath = `${uni.env.USER_DATA_PATH}/signature_${Date.now()}.png`
  369. // 移除 data:image/png;base64, 前缀
  370. const base64 = base64Data.replace(/^data:image\/\w+;base64,/, '')
  371. fs.writeFile({
  372. filePath,
  373. data: base64,
  374. encoding: 'base64',
  375. success: () => {
  376. resolve(filePath)
  377. },
  378. fail: (err) => {
  379. reject(err)
  380. },
  381. })
  382. // #endif
  383. })
  384. }
  385. const uploadSignature = async (base64Data: string): Promise<string> => {
  386. try {
  387. // #ifdef H5
  388. const file = base64ToFile(base64Data)
  389. if (!file) {
  390. throw new Error('签名图片转换失败')
  391. }
  392. const fd = new FormData()
  393. fd.append('file', file)
  394. const resp: any = await uploadSignImage(fd)
  395. if (resp?.code === 0 && resp?.data) {
  396. return resp.data
  397. }
  398. throw new Error(resp?.msg || '上传失败')
  399. // #endif
  400. } catch (error) {
  401. console.error('签名图片上传失败:', error)
  402. throw error
  403. }
  404. }
  405. // 确认签名
  406. const confirmSign = async () => {
  407. if (!signatureRef.value) return
  408. if (signatureRef.value.isEmpty()) {
  409. uni.showToast({ title: '请先签字再保存', icon: 'none' })
  410. return
  411. }
  412. try {
  413. uni.showLoading({ title: '正在保存...' })
  414. const path = await signatureRef.value.getImage()
  415. signImg.value = path
  416. showSignImg.value = path
  417. // console.log('imagePath....', path)
  418. uni.getImageInfo({
  419. src: path,
  420. success: (imageInfo) => {
  421. const screenWidth = uni.getSystemInfoSync().windowWidth - 32
  422. signImgStyle.value = {
  423. width: `${screenWidth}px`,
  424. height: `${(screenWidth * imageInfo.height) / imageInfo.width}px`,
  425. }
  426. },
  427. })
  428. // 上传签名图片
  429. try {
  430. const uploadedUrl = await uploadSignature(path)
  431. uploadedSignUrl.value = uploadedUrl
  432. console.log('签名图片上传成功:', uploadedUrl)
  433. } catch (uploadError) {
  434. console.error('签名图片上传失败,继续使用原图片:', uploadError)
  435. uni.showToast({ title: '签名保存成功,上传失败', icon: 'none' })
  436. }
  437. const now = new Date()
  438. signTime.value = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`
  439. signStatus.value = 'signed'
  440. uni.hideLoading()
  441. } catch (err) {
  442. uni.hideLoading()
  443. console.error('转换失败:', err)
  444. uni.showToast({ title: '签名失败', icon: 'error' })
  445. }
  446. }
  447. // 删除签名
  448. const handleDelSign = () => {
  449. signImg.value = ''
  450. showSignImg.value = ''
  451. uploadedSignUrl.value = ''
  452. signTime.value = ''
  453. signStatus.value = 'empty'
  454. }
  455. // 提交签名
  456. const signSubmit = () => {
  457. if (!signImg.value) {
  458. uni.showToast({ title: '请先签名', icon: 'none' })
  459. return
  460. }
  461. if (routeType.value === 'FWD') {
  462. if (!fwdInputPhone.value) {
  463. uni.showToast({ title: '请输入接收人手机号', icon: 'none' })
  464. return
  465. }
  466. if (!/^1[3456789]\d{9}$/.test(fwdInputPhone.value)) {
  467. uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
  468. return
  469. }
  470. showFwdPopup.value = true
  471. return
  472. }
  473. submitConfirm()
  474. }
  475. // 确认提交
  476. const submitConfirm = async () => {
  477. try {
  478. const params: any = {
  479. id: orderId.value,
  480. signUrl: uploadedSignUrl.value || signImg.value,
  481. // signTime: ,
  482. businessType: routeType.value ? businessTypeMap[routeType.value] : '',
  483. // orderItemId: orderItemId.value || undefined,
  484. // securityCheckId: ,
  485. }
  486. if (routeType.value === 'FWD') {
  487. params.receiverPhone = fwdInputPhone.value
  488. params.orderReportId = orderReportId.value
  489. } else if (routeType.value === 'AQJC') {
  490. params.securityCheckId = securityCheckId.value
  491. }
  492. const result: any = await requestFunc(SignFuncName.SubmitSign, equipType, params)
  493. if (result?.code === 0) {
  494. isSigned.value = '1'
  495. uni.showToast({
  496. title: routeType.value === 'ZXXX' ? '已自动提交审核' : '签名成功',
  497. icon: 'success',
  498. })
  499. getPreviewData()
  500. } else {
  501. uni.showToast({ title: result?.msg || '签名失败', icon: 'error' })
  502. }
  503. } catch (error: any) {
  504. console.error('签名失败:', error)
  505. uni.showToast({ title: error?.msg || '签名失败', icon: 'error' })
  506. } finally {
  507. showFwdPopup.value = false
  508. }
  509. }
  510. // 服务单提交确认
  511. const confirmFwdSubmit = () => {
  512. submitConfirm()
  513. }
  514. // 关闭服务单弹窗
  515. const closeFwdPopup = () => {
  516. showFwdPopup.value = false
  517. }
  518. // 更多操作
  519. const handlePushOrder = () => {
  520. if (routeType.value === 'ZXXX') {
  521. actionSheetActions.value = [{ name: '小程序推送签名' }, { name: '更新' }]
  522. } else {
  523. actionSheetActions.value = [{ name: '推送' }, { name: '更新' }]
  524. }
  525. showActionSheet.value = true
  526. }
  527. // 处理操作面板选择
  528. const handleActionSelect = (item: any) => {
  529. const name = item.item.name
  530. if (name === '推送' || name === '小程序推送签名') {
  531. showInputPopup.value = true
  532. } else if (name === '更新') {
  533. if (!templateId.value || !refId.value) {
  534. return uni.showToast({ title: '配置信息不完整', icon: 'none' })
  535. }
  536. let url = ''
  537. if (routeType.value === 'FWD') {
  538. url = `/pages/serviceOrderDetail/serviceOrderEditor?templateId=${templateId.value}&refId=${refId.value}`
  539. } else if (routeType.value === 'JYRS') {
  540. uni.showToast({ title: '更新检验结果告知表单暂未实现', icon: 'none' })
  541. } else {
  542. uni.showToast({ title: '系统错误,不能更新', icon: 'none' })
  543. }
  544. uni.navigateTo({ url })
  545. }
  546. }
  547. // 关闭推送弹窗
  548. const closeInputPopup = () => {
  549. showInputPopup.value = false
  550. inputEmail.value = ''
  551. }
  552. // 推送任务单提交
  553. const handlePushOrderSubmit = () => {
  554. if (!inputName.value || !inputPhone.value) {
  555. return uni.showToast({ title: '请输入接收人姓名和手机号', icon: 'none' })
  556. }
  557. if (!/^1[3456789]\d{9}$/.test(inputPhone.value)) {
  558. return uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
  559. }
  560. const params: any = {
  561. orderId: orderId.value,
  562. receiver: inputName.value,
  563. receiverPhone: inputPhone.value,
  564. receiverEmail: routeType.value !== 'ZXXX' ? inputEmail.value || '' : '',
  565. businessType: routeType.value ? businessTypeMap[routeType.value] : '',
  566. signUrl: uploadedSignUrl.value || signImg.value,
  567. orderItemId: orderItemId.value || undefined,
  568. securityCheckId: securityCheckId.value,
  569. equipMainType: equipTypeCode(equipType),
  570. }
  571. pushPressure2TaskOrder(params)
  572. .then((res: any) => {
  573. if (res?.code === 0) {
  574. uni.showToast({ title: '推送成功', icon: 'success' })
  575. showInputPopup.value = false
  576. } else {
  577. uni.showToast({ title: res?.msg || '推送失败', icon: 'none' })
  578. }
  579. })
  580. .catch((error) => {
  581. console.error('推送失败:', error)
  582. uni.showToast({ title: '推送失败', icon: 'none' })
  583. })
  584. }
  585. const equipTypeCode = (type: EquipmentType) => {
  586. switch (type) {
  587. case EquipmentType.BOILER:
  588. return '200'
  589. case EquipmentType.PIPE:
  590. return '300'
  591. case EquipmentType.CONTAINER:
  592. return '100'
  593. default:
  594. return ''
  595. }
  596. }
  597. onMounted(() => {
  598. if (orderId.value) {
  599. getPreviewData()
  600. }
  601. })
  602. </script>
  603. <style lang="scss" scoped>
  604. .sign-container {
  605. display: flex;
  606. flex-direction: column;
  607. height: 100vh;
  608. background-color: #fff;
  609. }
  610. .navigate-view {
  611. display: flex;
  612. flex-direction: row;
  613. align-items: center;
  614. justify-content: space-between;
  615. height: 44px;
  616. padding: 0 15px;
  617. background-color: #fff;
  618. border-bottom: 1px solid #eee;
  619. .navigate-left {
  620. display: flex;
  621. align-items: center;
  622. }
  623. .back-icon {
  624. width: 20px;
  625. height: 20px;
  626. }
  627. .navigate-title {
  628. font-size: 17px;
  629. font-weight: 500;
  630. color: #333;
  631. }
  632. .navigate-right {
  633. width: 20px;
  634. }
  635. }
  636. .nav-title {
  637. font-size: 17px;
  638. font-weight: 500;
  639. color: #333;
  640. }
  641. .scroll-content {
  642. flex: 1;
  643. padding: 16px;
  644. }
  645. // 分割线
  646. .sign-divider {
  647. width: 100%;
  648. height: 1px;
  649. margin: 0 0 12px;
  650. background-color: #e0e0e0;
  651. }
  652. // 签名区域
  653. .sign-section {
  654. width: 100%;
  655. padding-bottom: 12px;
  656. background-color: #fff;
  657. .sign-header {
  658. display: flex;
  659. flex-direction: row;
  660. align-items: center;
  661. justify-content: space-between;
  662. margin-bottom: 8px;
  663. .sign-title-text {
  664. font-size: 20px;
  665. line-height: 28px;
  666. color: #333;
  667. }
  668. .sign-header-actions {
  669. display: flex;
  670. flex-direction: row;
  671. gap: 8px;
  672. .header-action-btn {
  673. display: flex;
  674. align-items: center;
  675. justify-content: center;
  676. height: 30px;
  677. padding: 0 12px;
  678. font-size: 14px;
  679. border: none;
  680. border-radius: 6px;
  681. }
  682. .header-reset-btn {
  683. color: rgb(16, 16, 16);
  684. background-color: rgb(230, 238, 245);
  685. border: 1px solid rgb(187, 187, 187);
  686. }
  687. .header-cancel-btn {
  688. color: rgb(16, 16, 16);
  689. background-color: #f5f5f5;
  690. border: 1px solid rgb(187, 187, 187);
  691. }
  692. .header-confirm-btn {
  693. color: #fff;
  694. background-color: rgb(7, 31, 80);
  695. }
  696. }
  697. }
  698. // 空状态 - 点击签名
  699. .sign-view {
  700. display: flex;
  701. align-items: center;
  702. justify-content: center;
  703. width: 100%;
  704. height: 178px;
  705. background-color: #d8d8d8;
  706. .sign-view-text {
  707. font-size: 42px;
  708. font-weight: bold;
  709. color: #999;
  710. }
  711. }
  712. // 签名画布区域
  713. .sign-canvas-wrapper {
  714. width: 100%;
  715. overflow: hidden;
  716. background-color: #ffffff;
  717. border: 2px dashed rgb(187, 187, 187);
  718. border-radius: 8px;
  719. }
  720. // 已签名 - 图片展示
  721. .sign-img {
  722. position: relative;
  723. display: flex;
  724. align-items: center;
  725. justify-content: center;
  726. padding: 10px;
  727. border: 1px solid #ccc;
  728. border-radius: 10px;
  729. .sign-img-del {
  730. position: absolute;
  731. top: 0;
  732. right: 0;
  733. z-index: 2;
  734. display: flex;
  735. align-items: center;
  736. justify-content: center;
  737. width: 20px;
  738. height: 20px;
  739. font-size: 12px;
  740. color: #333;
  741. background-color: #e0e0e0;
  742. border-radius: 10px;
  743. }
  744. .sign-image {
  745. display: block;
  746. width: 100%;
  747. }
  748. }
  749. }
  750. // 表单区域
  751. .form-section {
  752. padding: 15px 0;
  753. margin-bottom: 10px;
  754. background-color: #fff;
  755. border-top: 1px solid #eee;
  756. .form-item {
  757. display: flex;
  758. flex-direction: row;
  759. align-items: center;
  760. .form-label {
  761. width: 120px;
  762. font-size: 14px;
  763. color: #666;
  764. }
  765. .form-input {
  766. flex: 1;
  767. height: 40px;
  768. padding: 0 10px;
  769. font-size: 14px;
  770. border: 1px solid #ddd;
  771. border-radius: 5px;
  772. }
  773. }
  774. }
  775. // 底部按钮
  776. .footer-bar {
  777. display: flex;
  778. flex-direction: row;
  779. gap: 12px;
  780. padding: 12px 16px 16px;
  781. background-color: #ffffff;
  782. border-top: 1px solid #e0e0e0;
  783. .footer-btn {
  784. display: flex;
  785. flex: 1;
  786. align-items: center;
  787. justify-content: center;
  788. height: 44px;
  789. font-size: 16px;
  790. color: #fff;
  791. border: none;
  792. border-radius: 6px;
  793. }
  794. .more-btn {
  795. background-color: #e6a23c;
  796. }
  797. .confirm-btn {
  798. background-color: #00a811;
  799. }
  800. .resign-btn {
  801. background-color: #e9e3e3;
  802. border-width: 2px;
  803. border-color: #000000;
  804. color: #000000;
  805. }
  806. }
  807. // 弹窗
  808. .popup-overlay {
  809. position: fixed;
  810. top: 0;
  811. right: 0;
  812. bottom: 0;
  813. left: 0;
  814. z-index: 999;
  815. display: flex;
  816. align-items: center;
  817. justify-content: center;
  818. background-color: rgba(0, 0, 0, 0.5);
  819. .popup-content {
  820. display: flex;
  821. flex-direction: column;
  822. align-items: center;
  823. width: 66.67%;
  824. padding: 20px;
  825. background-color: #fff;
  826. border-radius: 10px;
  827. &.input-popup {
  828. width: 80%;
  829. max-width: 360px;
  830. }
  831. .popup-title {
  832. margin-bottom: 15px;
  833. font-size: 18px;
  834. font-weight: 500;
  835. color: #333;
  836. }
  837. .popup-message {
  838. margin-bottom: 20px;
  839. font-size: 15px;
  840. color: #666;
  841. }
  842. .popup-actions {
  843. display: flex;
  844. flex-direction: row;
  845. gap: 10px;
  846. width: 100%;
  847. .action-btn {
  848. display: flex;
  849. flex: 1;
  850. align-items: center;
  851. justify-content: center;
  852. height: 40px;
  853. font-size: 15px;
  854. border: none;
  855. border-radius: 5px;
  856. }
  857. .cancel-btn {
  858. color: #fff;
  859. background-color: #94bddf;
  860. }
  861. .confirm-btn {
  862. color: #fff;
  863. background-color: #071f50;
  864. }
  865. }
  866. }
  867. }
  868. .input-row {
  869. display: flex;
  870. flex-direction: row;
  871. align-items: center;
  872. width: 100%;
  873. margin-bottom: 12px;
  874. .row-label {
  875. flex-shrink: 0;
  876. width: 90px;
  877. font-size: 14px;
  878. color: #666;
  879. }
  880. .row-input {
  881. flex: 1;
  882. height: 36px;
  883. padding: 0 10px;
  884. font-size: 14px;
  885. border: 1px solid #ddd;
  886. border-radius: 5px;
  887. }
  888. }
  889. </style>