OaDetail.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  1. <template>
  2. <div class="oa-detail">
  3. <div v-if="loading" class="oa-detail__loading">
  4. <OaPulseLoading text="加载审批详情…" />
  5. </div>
  6. <template v-else>
  7. <div
  8. class="oa-detail__body"
  9. :class="{
  10. 'oa-detail__body--approve': canApprove,
  11. 'oa-detail__body--mine-act': (canWithdraw || canCopyRestart) && !canApprove
  12. }"
  13. >
  14. <header class="oa-detail__hd">
  15. <h1 class="oa-detail__title">{{ detail?.title || '审批详情' }}</h1>
  16. <span
  17. v-if="detail?.status"
  18. class="oa-detail__status"
  19. :class="statusTone(detail.status)"
  20. >
  21. {{ statusText(detail.status) }}
  22. </span>
  23. </header>
  24. <form-create-mobile
  25. v-if="formRule.length"
  26. v-model="formData"
  27. :rule="formRule"
  28. :option="formOption"
  29. />
  30. <p v-else class="oa-detail__empty">暂无表单内容</p>
  31. <OaProgressTimeline :items="timelineItems" />
  32. </div>
  33. <footer class="oa-detail__ft">
  34. <van-button class="oa-detail__btn oa-detail__btn--close" @click="onClose">
  35. 关闭
  36. </van-button>
  37. <van-button
  38. class="oa-detail__btn oa-detail__btn--ghost oa-detail__btn--print"
  39. :loading="printing"
  40. :disabled="acting || printing"
  41. @click="onPrint"
  42. >
  43. 打印
  44. </van-button>
  45. <template v-if="canApprove">
  46. <van-button
  47. class="oa-detail__btn oa-detail__btn--ghost"
  48. :loading="acting"
  49. :disabled="acting"
  50. @click="openComment('reject')"
  51. >
  52. 拒绝
  53. </van-button>
  54. <van-button
  55. v-if="detail?.allowReturn !== false"
  56. class="oa-detail__btn oa-detail__btn--ghost"
  57. :loading="acting"
  58. :disabled="acting"
  59. @click="openComment('return')"
  60. >
  61. 打回
  62. </van-button>
  63. <van-button
  64. v-if="detail?.allowAddSign !== false"
  65. class="oa-detail__btn oa-detail__btn--ghost"
  66. :loading="acting"
  67. :disabled="acting"
  68. @click="openAddSign"
  69. >
  70. 加签
  71. </van-button>
  72. <van-button
  73. class="oa-detail__btn oa-detail__btn--ghost"
  74. :loading="acting"
  75. :disabled="acting"
  76. @click="openTransfer"
  77. >
  78. 转交
  79. </van-button>
  80. <van-button
  81. class="oa-detail__btn oa-detail__btn--primary"
  82. type="primary"
  83. :loading="acting"
  84. :disabled="acting"
  85. @click="openComment('approve')"
  86. >
  87. 同意
  88. </van-button>
  89. </template>
  90. <van-button
  91. v-if="canWithdraw"
  92. class="oa-detail__btn oa-detail__btn--ghost"
  93. :loading="acting"
  94. :disabled="acting"
  95. @click="withdraw"
  96. >
  97. 撤销
  98. </van-button>
  99. <van-button
  100. v-if="canCopyRestart"
  101. class="oa-detail__btn oa-detail__btn--primary"
  102. type="primary"
  103. :disabled="acting"
  104. @click="goCopyRestart"
  105. >
  106. 复制并重新发起
  107. </van-button>
  108. </footer>
  109. </template>
  110. <!-- 同意 / 拒绝 / 打回意见 -->
  111. <van-dialog
  112. v-model:show="commentVisible"
  113. :title="commentTitle"
  114. show-cancel-button
  115. confirm-button-text="确定"
  116. cancel-button-text="取消"
  117. :before-close="onCommentBeforeClose"
  118. >
  119. <div class="oa-detail__dialog-body">
  120. <van-field
  121. v-model="commentText"
  122. type="textarea"
  123. rows="3"
  124. autosize
  125. maxlength="500"
  126. show-word-limit
  127. :placeholder="commentPlaceholder"
  128. />
  129. </div>
  130. </van-dialog>
  131. <!-- 加签 -->
  132. <van-popup
  133. v-model:show="addSignVisible"
  134. position="bottom"
  135. round
  136. :style="{ height: '72%' }"
  137. >
  138. <div class="oa-act-sheet">
  139. <div class="oa-act-sheet__hd">
  140. <span @click="addSignVisible = false">取消</span>
  141. <strong>加签</strong>
  142. <span class="oa-act-sheet__ok" @click="submitAddSign">确定</span>
  143. </div>
  144. <div class="oa-act-sheet__body">
  145. <p class="oa-act-sheet__label">加签人</p>
  146. <OaPersonSelect v-model="addSignUsers" :multiple="true" />
  147. <p class="oa-act-sheet__label">说明(可选)</p>
  148. <van-field
  149. v-model="addSignComment"
  150. type="textarea"
  151. rows="2"
  152. autosize
  153. maxlength="500"
  154. show-word-limit
  155. placeholder="加签说明"
  156. />
  157. </div>
  158. </div>
  159. </van-popup>
  160. <!-- 转交 -->
  161. <van-popup
  162. v-model:show="transferVisible"
  163. position="bottom"
  164. round
  165. :style="{ height: '72%' }"
  166. >
  167. <div class="oa-act-sheet">
  168. <div class="oa-act-sheet__hd">
  169. <span @click="transferVisible = false">取消</span>
  170. <strong>转交</strong>
  171. <span class="oa-act-sheet__ok" @click="submitTransfer">确定</span>
  172. </div>
  173. <div class="oa-act-sheet__body">
  174. <p class="oa-act-sheet__label">转交给</p>
  175. <OaPersonSelect
  176. v-model="transferUser"
  177. :multiple="false"
  178. :exclude-ids="transferExcludeIds"
  179. />
  180. <p class="oa-act-sheet__label">说明(可选)</p>
  181. <van-field
  182. v-model="transferComment"
  183. type="textarea"
  184. rows="2"
  185. autosize
  186. maxlength="500"
  187. show-word-limit
  188. placeholder="转交说明"
  189. />
  190. </div>
  191. </div>
  192. </van-popup>
  193. </div>
  194. </template>
  195. <script setup>
  196. import { computed, onMounted, ref } from 'vue'
  197. import { useRoute, useRouter } from 'vue-router'
  198. import { showConfirmDialog, showToast } from 'vant'
  199. import {
  200. fetchOaInstanceDetail,
  201. fetchCurrentUser,
  202. fetchApproveOaTask,
  203. fetchRejectOaTask,
  204. fetchReturnOaTask,
  205. fetchAddSignOaTask,
  206. fetchTransferOaTask,
  207. fetchWithdrawOaInstance
  208. } from '../api/oa.js'
  209. import { prepareMobileFormSchema } from '../oa/schema-prep.js'
  210. import { statusText, statusTone } from '../oa/status-label.js'
  211. import { exportOaApprovalPdf } from '../oa/print/exportOaApprovalPdf.js'
  212. import OaProgressTimeline from '../oa/components/OaProgressTimeline.vue'
  213. import OaPulseLoading from '../oa/components/OaPulseLoading.vue'
  214. import OaPersonSelect from '../oa/components/OaPersonSelect.vue'
  215. const route = useRoute()
  216. const router = useRouter()
  217. const loading = ref(true)
  218. const acting = ref(false)
  219. const printing = ref(false)
  220. const detail = ref(null)
  221. const formData = ref({})
  222. const formRule = ref([])
  223. const formOption = ref({})
  224. const currentUserId = ref('')
  225. const currentUserName = ref('')
  226. const autoPrinted = ref(false)
  227. const commentVisible = ref(false)
  228. const commentKind = ref('approve') // approve | reject | return
  229. const commentText = ref('')
  230. const addSignVisible = ref(false)
  231. const addSignUsers = ref([])
  232. const addSignComment = ref('')
  233. const transferVisible = ref(false)
  234. const transferUser = ref(null)
  235. const transferComment = ref('')
  236. const instanceId = computed(() => String(route.query.instanceId || '').trim())
  237. const queryTaskId = computed(() => String(route.query.taskId || '').trim())
  238. const wantAutoPrint = computed(() => {
  239. const v = String(route.query.autoPrint || '').trim()
  240. return v === '1' || v === 'true'
  241. })
  242. const mode = computed(() => {
  243. const m = String(route.query.mode || 'view').trim()
  244. return m || 'view'
  245. })
  246. const timelineItems = computed(() => {
  247. const list = detail.value?.progress
  248. if (!Array.isArray(list) || !list.length) return []
  249. return list
  250. .filter((p) => {
  251. const kind = String(p?.kind || '')
  252. const status = String(p?.status || '')
  253. const title = String(p?.title || '').trim()
  254. if (kind === 'terminal' && (status === 'returned' || title === '已打回')) return false
  255. return true
  256. })
  257. .map((p, idx) => ({
  258. key: `${p.kind}-${p.nodeKey || p.taskId || idx}-${p.roundNo || 1}`,
  259. title: p.title || '-',
  260. status: p.status || '',
  261. timestamp: p.timestamp || '',
  262. assignee: p.assigneeUserName || p.assigneeUserId || '',
  263. commentText: p.commentText || ''
  264. }))
  265. })
  266. function isPendingApprovalForMe(t) {
  267. const uid = currentUserId.value
  268. if (!uid || !t) return false
  269. return (
  270. t.taskType === 'approval' &&
  271. t.status === 'pending' &&
  272. String(t.assigneeUserId || '') === uid
  273. )
  274. }
  275. /** 优先 query.taskId(须为本用户待办审批任务),否则取本用户第一条 pending approval */
  276. const myPendingTask = computed(() => {
  277. const list = detail.value?.tasks || []
  278. const qid = queryTaskId.value
  279. if (qid) {
  280. const matched = list.find((t) => String(t.id || '') === qid && isPendingApprovalForMe(t))
  281. if (matched) return matched
  282. }
  283. return list.find((t) => isPendingApprovalForMe(t))
  284. })
  285. const canApprove = computed(() => !!myPendingTask.value && mode.value === 'approve')
  286. /** 与 Web instances/detail:发起人 + 进行中 */
  287. const isInitiator = computed(() => {
  288. const uid = currentUserId.value
  289. if (!uid || !detail.value) return false
  290. return String(detail.value.initiatorUserId || '') === uid
  291. })
  292. const canWithdraw = computed(
  293. () => isInitiator.value && String(detail.value?.status || '') === 'running'
  294. )
  295. const canCopyRestart = computed(() => {
  296. if (!isInitiator.value || !detail.value) return false
  297. return ['returned', 'cancelled', 'rejected'].includes(String(detail.value.status || ''))
  298. })
  299. /** 转交:排除自己 + 本节点已有 pending approval 的办理人 */
  300. const transferExcludeIds = computed(() => {
  301. const me = currentUserId.value
  302. const pendingIds = (detail.value?.tasks || [])
  303. .filter((t) => t.status === 'pending' && t.taskType === 'approval')
  304. .map((t) => String(t.assigneeUserId || '').trim())
  305. .filter(Boolean)
  306. return [...new Set([me, ...pendingIds].filter(Boolean))]
  307. })
  308. const commentTitle = computed(() => {
  309. if (commentKind.value === 'reject') return '拒绝'
  310. if (commentKind.value === 'return') return '打回'
  311. return '同意'
  312. })
  313. const commentPlaceholder = computed(() => {
  314. if (commentKind.value === 'return') return '请填写打回意见(必填)'
  315. return '审批意见(可选)'
  316. })
  317. /** 通知宿主关闭(对齐 OaStart closeToMini) */
  318. function closeToMini(payload) {
  319. const data = payload || { action: 'oa-detail-close' }
  320. try {
  321. if (typeof wx !== 'undefined' && wx.miniProgram?.navigateBack) {
  322. wx.miniProgram.postMessage({ data })
  323. wx.miniProgram.navigateBack()
  324. return
  325. }
  326. } catch (_) {
  327. /* ignore */
  328. }
  329. try {
  330. const uniWv = typeof uni !== 'undefined' ? uni.webView : null
  331. if (uniWv?.postMessage) {
  332. uniWv.postMessage({ data })
  333. }
  334. if (uniWv?.navigateBack) {
  335. uniWv.navigateBack()
  336. return
  337. }
  338. } catch (_) {
  339. /* ignore */
  340. }
  341. try {
  342. if (typeof window !== 'undefined' && window.parent && window.parent !== window) {
  343. window.parent.postMessage({ source: 'zkxw-oa-h5', ...data }, '*')
  344. return
  345. }
  346. } catch (_) {
  347. /* ignore */
  348. }
  349. showToast((data && data.message) || '已关闭(非小程序环境)')
  350. }
  351. function onClose() {
  352. closeToMini({ action: 'oa-detail-close' })
  353. }
  354. async function onPrint() {
  355. if (!detail.value || printing.value) return
  356. printing.value = true
  357. try {
  358. const printerName =
  359. currentUserName.value ||
  360. String(detail.value?.initiatorUserName || '').trim() ||
  361. '-'
  362. const result = await exportOaApprovalPdf(detail.value, { printerName })
  363. if (!result?.ok) {
  364. showToast(
  365. result?.message ||
  366. '当前环境无法调起打印,请用手机浏览器打开或到电脑端导出'
  367. )
  368. }
  369. } catch (e) {
  370. showToast(
  371. e?.message || '当前环境无法调起打印,请用手机浏览器打开或到电脑端导出'
  372. )
  373. } finally {
  374. printing.value = false
  375. }
  376. }
  377. function parseFormData(d) {
  378. if (d?.formData && typeof d.formData === 'object') return { ...d.formData }
  379. if (d?.formDataJson && typeof d.formDataJson === 'object') return { ...d.formDataJson }
  380. return {}
  381. }
  382. async function withdraw() {
  383. try {
  384. await showConfirmDialog({
  385. title: '提示',
  386. message: '确定撤销该审批吗?'
  387. })
  388. } catch {
  389. return
  390. }
  391. const id = instanceId.value
  392. if (!id) return
  393. acting.value = true
  394. try {
  395. await fetchWithdrawOaInstance(id)
  396. showToast('已撤销')
  397. closeToMini({ action: 'oa-detail-withdraw', message: '已撤销' })
  398. } catch (e) {
  399. showToast(e?.message || '撤销失败')
  400. } finally {
  401. acting.value = false
  402. }
  403. }
  404. function goCopyRestart() {
  405. const appId = String(detail.value?.appId || '').trim()
  406. const id = instanceId.value
  407. if (!appId || !id) {
  408. showToast('无法复制:缺少应用或实例信息')
  409. return
  410. }
  411. // 优先同 webview 内跳转发起页(带 fromInstanceId 预填)
  412. const q = { appId, fromInstanceId: id }
  413. const token = String(route.query.token || '').trim()
  414. if (token) q.token = token
  415. try {
  416. router.replace({ path: '/oa-start', query: q })
  417. return
  418. } catch (_) {
  419. /* fallback below */
  420. }
  421. // 离开 webview 栈时:宿主按 oa-detail-restart 再开发起页
  422. closeToMini({
  423. action: 'oa-detail-restart',
  424. appId,
  425. fromInstanceId: id,
  426. message: '复制并重新发起'
  427. })
  428. }
  429. function openComment(kind) {
  430. if (!myPendingTask.value) return
  431. commentKind.value = kind
  432. commentText.value = ''
  433. commentVisible.value = true
  434. }
  435. function onCommentBeforeClose(action) {
  436. if (action !== 'confirm') return true
  437. if (commentKind.value === 'return') {
  438. const c = String(commentText.value || '').trim()
  439. if (!c) {
  440. showToast('打回意见必填')
  441. return false
  442. }
  443. }
  444. void submitComment()
  445. return false
  446. }
  447. async function submitComment() {
  448. const task = myPendingTask.value
  449. if (!task) {
  450. commentVisible.value = false
  451. return
  452. }
  453. const comment = String(commentText.value || '').trim()
  454. acting.value = true
  455. try {
  456. const kind = commentKind.value
  457. if (kind === 'approve') {
  458. await fetchApproveOaTask(task.id, comment || undefined)
  459. commentVisible.value = false
  460. showToast('已同意')
  461. closeToMini({ action: 'oa-detail-done', message: '已同意' })
  462. } else if (kind === 'reject') {
  463. await fetchRejectOaTask(task.id, comment || undefined)
  464. commentVisible.value = false
  465. showToast('已拒绝')
  466. closeToMini({ action: 'oa-detail-done', message: '已拒绝' })
  467. } else if (kind === 'return') {
  468. await fetchReturnOaTask(task.id, comment)
  469. commentVisible.value = false
  470. showToast('已打回')
  471. closeToMini({ action: 'oa-detail-done', message: '已打回' })
  472. }
  473. } catch (e) {
  474. showToast(e?.message || '操作失败')
  475. } finally {
  476. acting.value = false
  477. }
  478. }
  479. function openAddSign() {
  480. if (!myPendingTask.value) return
  481. addSignUsers.value = []
  482. addSignComment.value = ''
  483. addSignVisible.value = true
  484. }
  485. async function submitAddSign() {
  486. const task = myPendingTask.value
  487. if (!task) return
  488. const userIds = (Array.isArray(addSignUsers.value) ? addSignUsers.value : [])
  489. .map((u) => String(u?.id || '').trim())
  490. .filter(Boolean)
  491. if (!userIds.length) {
  492. showToast('请选择加签人')
  493. return
  494. }
  495. acting.value = true
  496. try {
  497. const c = String(addSignComment.value || '').trim()
  498. await fetchAddSignOaTask(task.id, {
  499. userIds,
  500. comment: c || undefined
  501. })
  502. addSignVisible.value = false
  503. showToast('已加签')
  504. closeToMini({ action: 'oa-detail-done', message: '已加签' })
  505. } catch (e) {
  506. showToast(e?.message || '加签失败')
  507. } finally {
  508. acting.value = false
  509. }
  510. }
  511. function openTransfer() {
  512. if (!myPendingTask.value) return
  513. transferUser.value = null
  514. transferComment.value = ''
  515. transferVisible.value = true
  516. }
  517. async function submitTransfer() {
  518. const task = myPendingTask.value
  519. if (!task) return
  520. const targetUserId = String(transferUser.value?.id || '').trim()
  521. if (!targetUserId) {
  522. showToast('请选择转交人')
  523. return
  524. }
  525. acting.value = true
  526. try {
  527. const c = String(transferComment.value || '').trim()
  528. await fetchTransferOaTask(task.id, {
  529. targetUserId,
  530. comment: c || undefined
  531. })
  532. transferVisible.value = false
  533. showToast('已转交')
  534. closeToMini({ action: 'oa-detail-done', message: '已转交' })
  535. } catch (e) {
  536. showToast(e?.message || '转交失败')
  537. } finally {
  538. acting.value = false
  539. }
  540. }
  541. async function bootstrap() {
  542. loading.value = true
  543. try {
  544. const id = instanceId.value
  545. if (!id) {
  546. showToast('缺少 instanceId')
  547. return
  548. }
  549. const [me, d] = await Promise.all([
  550. fetchCurrentUser().catch(() => null),
  551. fetchOaInstanceDetail(id)
  552. ])
  553. currentUserId.value = String(me?.id || me?.userId || '').trim()
  554. currentUserName.value = String(
  555. me?.nickName || me?.userName || me?.name || ''
  556. ).trim()
  557. detail.value = d
  558. try {
  559. document.title = d?.title || d?.appName || '审批详情'
  560. } catch (_) {
  561. /* ignore */
  562. }
  563. const prepared = prepareMobileFormSchema(d?.formSchemaJson)
  564. formRule.value = prepared.rule
  565. formOption.value = {
  566. ...prepared.option,
  567. form: {
  568. ...(prepared.option?.form || {}),
  569. disabled: true
  570. },
  571. submitBtn: false,
  572. resetBtn: false
  573. }
  574. formData.value = parseFormData(d)
  575. } catch (e) {
  576. showToast(e?.message || '加载失败')
  577. } finally {
  578. loading.value = false
  579. }
  580. if (wantAutoPrint.value && detail.value && !autoPrinted.value) {
  581. autoPrinted.value = true
  582. // 等首屏渲染后再调打印,避免遮挡 loading
  583. setTimeout(() => {
  584. void onPrint()
  585. }, 320)
  586. }
  587. }
  588. onMounted(() => {
  589. bootstrap()
  590. })
  591. </script>
  592. <style scoped>
  593. .oa-detail {
  594. min-height: 100vh;
  595. display: flex;
  596. flex-direction: column;
  597. background: var(--bg-page, #ffffff);
  598. }
  599. .oa-detail__loading {
  600. flex: 1;
  601. display: flex;
  602. align-items: center;
  603. justify-content: center;
  604. padding: 48px 16px;
  605. }
  606. .oa-detail__body {
  607. flex: 1;
  608. padding: 12px 12px 96px;
  609. }
  610. .oa-detail__body--approve {
  611. padding-bottom: 128px;
  612. }
  613. .oa-detail__body--mine-act {
  614. padding-bottom: 120px;
  615. }
  616. .oa-detail__hd {
  617. display: flex;
  618. flex-wrap: wrap;
  619. gap: 8px;
  620. align-items: center;
  621. margin-bottom: 12px;
  622. padding: 4px 2px;
  623. }
  624. .oa-detail__title {
  625. margin: 0;
  626. font-size: 17px;
  627. font-weight: 700;
  628. line-height: 1.35;
  629. color: var(--text-primary, #0f291e);
  630. }
  631. .oa-detail__status {
  632. flex-shrink: 0;
  633. padding: 2px 8px;
  634. border-radius: 6px;
  635. font-size: 12px;
  636. font-weight: 600;
  637. }
  638. .oa-detail__status.ok {
  639. color: var(--success, #10b981);
  640. background: var(--brand-green-soft, #ecfdf5);
  641. }
  642. .oa-detail__status.warn {
  643. color: var(--warning, #d97706);
  644. background: var(--barley-yellow-soft, #fffbeb);
  645. }
  646. .oa-detail__status.danger {
  647. color: var(--danger, #ef4444);
  648. background: #fef2f2;
  649. }
  650. .oa-detail__status.mute {
  651. color: var(--text-muted, #5f7a6d);
  652. background: #f1f5f9;
  653. }
  654. .oa-detail__empty {
  655. margin: 12px 0;
  656. padding: 16px 8px;
  657. color: var(--text-muted, #5f7a6d);
  658. font-size: 13px;
  659. text-align: center;
  660. }
  661. .oa-detail__ft {
  662. position: fixed;
  663. right: 0;
  664. bottom: 0;
  665. left: 0;
  666. z-index: 20;
  667. display: flex;
  668. flex-wrap: wrap;
  669. align-items: center;
  670. gap: 8px;
  671. padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
  672. background: #fff;
  673. border-top: 1px solid #eef2f6;
  674. box-shadow: 0 -4px 16px rgba(15, 23, 42, 0.04);
  675. }
  676. .oa-detail__btn {
  677. margin: 0;
  678. height: 40px;
  679. min-width: 0;
  680. flex: 1 1 auto;
  681. border-radius: 12px !important;
  682. font-size: 14px;
  683. padding: 0 10px !important;
  684. }
  685. .oa-detail__btn--close {
  686. flex: 0 0 auto;
  687. min-width: 64px;
  688. border: none !important;
  689. background: #f1f5f9 !important;
  690. color: #334155 !important;
  691. font-weight: 600;
  692. }
  693. .oa-detail__btn--close:active {
  694. opacity: 0.85;
  695. }
  696. .oa-detail__btn--print {
  697. flex: 0 0 auto;
  698. min-width: 64px;
  699. }
  700. .oa-detail__btn--ghost {
  701. border: 1px solid #e2e8f0 !important;
  702. background: #fff !important;
  703. color: #334155 !important;
  704. font-weight: 600;
  705. }
  706. .oa-detail__btn--primary {
  707. flex: 1 1 88px;
  708. border: none !important;
  709. background: var(--brand-green, #22c55e) !important;
  710. color: #fff !important;
  711. font-weight: 700;
  712. }
  713. .oa-detail__dialog-body {
  714. padding: 8px 16px 4px;
  715. }
  716. .oa-act-sheet {
  717. display: flex;
  718. flex-direction: column;
  719. height: 100%;
  720. background: #fff;
  721. }
  722. .oa-act-sheet__hd {
  723. display: flex;
  724. align-items: center;
  725. justify-content: space-between;
  726. padding: 14px 16px;
  727. border-bottom: 1px solid #f1f5f9;
  728. font-size: 15px;
  729. color: #64748b;
  730. }
  731. .oa-act-sheet__hd strong {
  732. color: #0f172a;
  733. font-size: 16px;
  734. }
  735. .oa-act-sheet__ok {
  736. color: var(--brand-green, #22c55e);
  737. font-weight: 700;
  738. }
  739. .oa-act-sheet__body {
  740. flex: 1;
  741. overflow: auto;
  742. padding: 12px 16px 24px;
  743. -webkit-overflow-scrolling: touch;
  744. }
  745. .oa-act-sheet__label {
  746. margin: 12px 0 8px;
  747. font-size: 13px;
  748. font-weight: 600;
  749. color: #334155;
  750. }
  751. .oa-act-sheet__label:first-child {
  752. margin-top: 0;
  753. }
  754. </style>