PipeCheckProject.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. <template>
  2. <view class="check-project-container">
  3. <view class="search-bar">
  4. <view class="dropdown-wrapper">
  5. <view class="dropdown-selector" @click="showDropdown = !showDropdown">
  6. <text class="dropdown-text">{{ inspectionNatureLabel }}</text>
  7. <!-- <text class="dropdown-arrow">▼</text> -->
  8. </view>
  9. <!-- <view v-if="showDropdown" class="dropdown-menu">
  10. <view
  11. v-for="option in inspectionNatureOptions"
  12. :key="option.value"
  13. class="dropdown-item"
  14. :class="{ active: inspectionNature === option.value }"
  15. @click="selectInspectionNature(option)"
  16. >
  17. <text>{{ option.label }}</text>
  18. </view>
  19. </view> -->
  20. </view>
  21. <input
  22. v-model="searchKeyword"
  23. class="search-input"
  24. placeholder="搜索项目名称"
  25. @confirm="handleSearch"
  26. />
  27. </view>
  28. <scroll-view class="project-list" :scroll-y="true">
  29. <view v-if="dataSource.length === 0" class="empty-state">
  30. <text class="empty-text">暂无数据</text>
  31. </view>
  32. <wd-collapse v-else v-model="expandedNames">
  33. <wd-collapse-item
  34. v-for="group in groupedData"
  35. :key="group.type"
  36. :title="group.title"
  37. :name="group.type"
  38. >
  39. <view class="project-grid">
  40. <view
  41. v-for="(item, index) in group.items"
  42. :key="item.templateId"
  43. class="project-item"
  44. :class="{
  45. first: index === 0 || index === 1,
  46. last: index === group.items.length - 1 || index === group.items.length - 2,
  47. selected: isSelected(item),
  48. }"
  49. @click="handleSelect(item)"
  50. >
  51. <text class="project-name">{{ item.name }}</text>
  52. </view>
  53. </view>
  54. </wd-collapse-item>
  55. </wd-collapse>
  56. </scroll-view>
  57. <view class="bottom-actions">
  58. <button class="action-btn cancel-btn" @click="handleCancel">取消</button>
  59. <button class="action-btn confirm-btn" @click="handleConfirm">确认</button>
  60. </view>
  61. </view>
  62. </template>
  63. <script lang="ts" setup>
  64. import { ref, computed, onMounted, watch } from 'vue'
  65. import { getInspectProjectItemPage, addInspectProject } from '@/api/pipe/pipeTaskOrder'
  66. import { PipeCheckTypeMap } from '@/utils/dictMap'
  67. interface ReportTemplate {
  68. orderId: string
  69. name: string
  70. isAutoAmount: string
  71. templateId: string
  72. connectId: string
  73. formulaTemplateUrl: string
  74. fee: number
  75. use: boolean
  76. reportType: number
  77. recordTemplateUrl: string
  78. reportTemplateUrl: string
  79. isMainProject: string
  80. feeCalcType: string
  81. taskOrderItemId: string
  82. }
  83. interface Props {
  84. propjectList: ReportTemplate[][]
  85. selectTemplates: Record<string, any[]>
  86. useOnline?: string
  87. equipData: any
  88. }
  89. const props = defineProps<Props>()
  90. const emit = defineEmits<{
  91. setIds: [item: any, type: string]
  92. cleanIds: [type: string]
  93. change: [selectedItems: any[]]
  94. confirm: [selectedItems: any[]]
  95. cancel: []
  96. }>()
  97. const inspectionNature = ref(100)
  98. const searchKeyword = ref('')
  99. const dataSource = ref<ReportTemplate[]>([])
  100. const showDropdown = ref(false)
  101. const hadTemplateId = ref('')
  102. const expandedNames = ref<string[]>(['100'])
  103. const localSelectedTemplates = ref<ReportTemplate[]>([])
  104. const inspectionNatureLabel = computed(() => {
  105. const checkTypeNum = props.equipData?.taskOrder?.checkType
  106. return PipeCheckTypeMap[checkTypeNum]
  107. })
  108. interface GroupItem {
  109. type: string
  110. title: string
  111. items: ReportTemplate[]
  112. }
  113. const groupedData = computed(() => {
  114. const result: GroupItem[] = []
  115. result.push({
  116. type: '100',
  117. title: inspectionNatureLabel.value + ' 法定收费项目',
  118. items: [...dataSource.value],
  119. })
  120. result.push({
  121. type: '200',
  122. title: inspectionNatureLabel.value + ' 服务收费项目',
  123. items: [...dataSource.value],
  124. })
  125. return result
  126. })
  127. const isSelected = (item: ReportTemplate): boolean => {
  128. return localSelectedTemplates.value.some((template) => template.templateId === item.templateId)
  129. }
  130. const selectedItems = computed(() => {
  131. return localSelectedTemplates.value
  132. })
  133. const selectInspectionNature = (option: any) => {
  134. inspectionNature.value = option.value
  135. showDropdown.value = false
  136. loadTemplates()
  137. }
  138. const handleSearch = () => {
  139. loadTemplates()
  140. }
  141. const loadTemplates = async () => {
  142. if (props.useOnline === '1') {
  143. const resp = await getInspectProjectItemPage({
  144. orderId: props.equipData?.taskOrder?.id,
  145. equipmentCategory: '300',
  146. inspectionNature: [props.equipData?.taskOrder?.checkType],
  147. equipType: props.equipData?.taskOrderItem?.pipeType,
  148. itemIds: props.equipData?.reportList?.map((item: any) => item.id) || [],
  149. })
  150. const templateList = resp?.data?.filter((item: any) =>
  151. ['100', '200', '300'].includes(String(item.reportType)),
  152. )
  153. checkExistingTemplates(templateList || [])
  154. dataSource.value = templateList || []
  155. } else {
  156. uni.showToast({ title: '离线模式暂不支持添加项目', icon: 'none' })
  157. dataSource.value = []
  158. }
  159. }
  160. const checkExistingTemplates = (templates: ReportTemplate[]) => {
  161. for (const list of props.propjectList) {
  162. for (const item of list) {
  163. if (item.reportType == 100) {
  164. for (const template of templates) {
  165. if (template.templateId == item.templateId) {
  166. hadTemplateId.value = item.templateId
  167. break
  168. }
  169. }
  170. }
  171. }
  172. }
  173. }
  174. const handleSelect = (item: ReportTemplate) => {
  175. if (hadTemplateId.value === item.templateId && item.reportType === 100) {
  176. uni.showToast({ title: '该主报告已存在', icon: 'none' })
  177. return
  178. }
  179. const index = localSelectedTemplates.value.findIndex(
  180. (template) => template.templateId === item.templateId,
  181. )
  182. if (index > -1) {
  183. localSelectedTemplates.value.splice(index, 1)
  184. } else {
  185. localSelectedTemplates.value.push(item)
  186. }
  187. emit('setIds', item, 'CheckProject')
  188. emit('change', selectedItems.value)
  189. }
  190. onMounted(() => {
  191. const initSelected = props.selectTemplates.CheckProject || []
  192. localSelectedTemplates.value = [...initSelected]
  193. loadTemplates()
  194. })
  195. watch(
  196. () => props.selectTemplates.CheckProject,
  197. (newVal) => {
  198. if (newVal) {
  199. localSelectedTemplates.value = [...newVal]
  200. }
  201. },
  202. { deep: true },
  203. )
  204. const handleConfirm = async () => {
  205. const selected = localSelectedTemplates.value
  206. if (selected.length === 0) {
  207. uni.showToast({ title: '请至少选择一个检验项目', icon: 'none' })
  208. return
  209. }
  210. const itemList = selected.map((item) => ({
  211. connectId: item.connectId,
  212. fee: item.fee,
  213. orderItemId: props.equipData?.taskOrderItem?.id,
  214. templateId: item.templateId,
  215. type: props.equipData?.taskOrderItem?.pipeType,
  216. }))
  217. await addInspectProject({
  218. itemList,
  219. type: 200,
  220. orderId: props.equipData?.taskOrder?.id,
  221. })
  222. console.log('确认选中的检验项目:', itemList)
  223. emit('confirm', itemList)
  224. }
  225. const handleCancel = () => {
  226. localSelectedTemplates.value = []
  227. emit('cancel')
  228. }
  229. </script>
  230. <style lang="scss" scoped>
  231. .check-project-container {
  232. display: flex;
  233. flex-direction: column;
  234. height: 100%;
  235. overflow: hidden;
  236. }
  237. .search-bar {
  238. display: flex;
  239. gap: 10px;
  240. align-items: center;
  241. padding: 10px 0;
  242. }
  243. .dropdown-wrapper {
  244. position: relative;
  245. }
  246. .dropdown-selector {
  247. display: flex;
  248. align-items: center;
  249. margin: 0 12px;
  250. padding: 8px 12px;
  251. background-color: #f5f5f5;
  252. border-radius: 4px;
  253. }
  254. .dropdown-text {
  255. margin-right: 8px;
  256. font-size: 14px;
  257. color: #333;
  258. }
  259. .dropdown-arrow {
  260. font-size: 10px;
  261. color: #666;
  262. }
  263. .dropdown-menu {
  264. position: absolute;
  265. top: 100%;
  266. left: 0;
  267. z-index: 100;
  268. min-width: 100px;
  269. background-color: #fff;
  270. border: 1px solid #e0e0e0;
  271. border-radius: 4px;
  272. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  273. }
  274. .dropdown-item {
  275. padding: 10px 12px;
  276. font-size: 14px;
  277. color: #333;
  278. border-bottom: 1px solid #f0f0f0;
  279. }
  280. .dropdown-item:last-child {
  281. border-bottom: none;
  282. }
  283. .dropdown-item.active {
  284. color: #2f8eff;
  285. background-color: #e6f7ff;
  286. }
  287. .search-input {
  288. flex: 1;
  289. padding: 0 12px;
  290. height: 100%;
  291. margin: 0 12px 0 0;
  292. font-size: 14px;
  293. background-color: #f5f5f5;
  294. border-radius: 4px;
  295. }
  296. .project-list {
  297. flex: 1;
  298. height: 0;
  299. }
  300. .project-grid {
  301. display: flex;
  302. flex-wrap: wrap;
  303. gap: 10px;
  304. }
  305. .project-item {
  306. display: flex;
  307. align-items: center;
  308. min-width: calc(50% - 5px);
  309. max-width: calc(50% - 5px);
  310. padding: 10px 15px;
  311. background-color: #fff;
  312. border: 1px solid #d9d9d9;
  313. border-radius: 4px;
  314. }
  315. .project-item.first {
  316. border-top-left-radius: 4px;
  317. }
  318. .project-item.first:last-child,
  319. .project-item.first:nth-last-child(2) {
  320. border-top-right-radius: 4px;
  321. }
  322. .project-item.last {
  323. border-bottom-left-radius: 4px;
  324. }
  325. .project-item.last:nth-child(odd) {
  326. border-bottom-right-radius: 4px;
  327. }
  328. .project-item.selected {
  329. background-color: #e6f7ff;
  330. border-color: #2f8eff;
  331. }
  332. .project-name {
  333. flex: 1;
  334. overflow: hidden;
  335. font-size: 14px;
  336. color: #333;
  337. text-overflow: ellipsis;
  338. white-space: nowrap;
  339. }
  340. .empty-state {
  341. display: flex;
  342. align-items: center;
  343. justify-content: center;
  344. padding: 40px 0;
  345. }
  346. .empty-text {
  347. font-size: 14px;
  348. color: #999;
  349. }
  350. .bottom-actions {
  351. display: flex;
  352. flex-direction: row;
  353. flex-shrink: 0;
  354. gap: 10px;
  355. padding: 10px 15px;
  356. background-color: #fff;
  357. border-top: 1px solid #eee;
  358. .action-btn {
  359. display: flex;
  360. flex: 1;
  361. align-items: center;
  362. justify-content: center;
  363. height: 40px;
  364. font-size: 15px;
  365. border: none;
  366. border-radius: 5px;
  367. }
  368. .cancel-btn {
  369. color: #666;
  370. background-color: #f5f5f5;
  371. border: 1px solid #ddd;
  372. }
  373. .confirm-btn {
  374. color: #fff;
  375. background-color: rgb(47, 142, 255);
  376. }
  377. }
  378. :deep(.wd-collapse-item) {
  379. margin-bottom: 10px;
  380. overflow: hidden;
  381. background-color: #fff;
  382. border-radius: 8px;
  383. }
  384. :deep(.wd-collapse-item__title) {
  385. padding: 12px 15px;
  386. font-size: 15px;
  387. font-weight: 500;
  388. color: #333;
  389. }
  390. :deep(.wd-collapse-item__title-wrapper) {
  391. background-color: #fff;
  392. }
  393. :deep(.wd-collapse-item__content) {
  394. padding: 10px;
  395. }
  396. :deep(.wd-collapse-item__arrow) {
  397. color: #999;
  398. }
  399. </style>