Brak opisu

index1.vue 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. <template>
  2. <view class="task-management">
  3. <view class="fixed-area">
  4. <view class="title">
  5. <text class="title-text">龙飞街加油站</text>
  6. </view>
  7. <view>
  8. <wd-tabs v-model="tab">
  9. <block v-for="item in tabs" :key="item">
  10. <wd-tab :title="`${item}`" :name="item" />
  11. </block>
  12. </wd-tabs>
  13. </view>
  14. </view>
  15. <scroll-view class="task-container" scroll-y>
  16. <view class="tasks">
  17. <view
  18. v-for="(task, index) in currentTasks"
  19. :key="index"
  20. class="task-item"
  21. >
  22. <!-- 任务描述 -->
  23. <view class="task-description">
  24. <text class="task-number">{{ index + 1 }}.</text>
  25. <text class="task-text"
  26. >{{ task.description }}
  27. <wd-icon
  28. v-if="task?.icon"
  29. :name="task?.icon"
  30. size="32rpx"
  31. @click="() => (popupshow.centershow = true)"
  32. ></wd-icon>
  33. </text>
  34. </view>
  35. <!-- 操作说明 -->
  36. <view v-if="task.instructions" class="task-instructions">
  37. <text class="instructions-text">{{ task.instructions }}</text>
  38. </view>
  39. <!-- 示例图片 -->
  40. <view v-if="task.examples.length > 0" class="example-images">
  41. <view
  42. v-for="(example, idx) in task.examples"
  43. :key="idx"
  44. class="example-image"
  45. >
  46. <image :src="example" mode="aspectFill"></image>
  47. <text class="example-text">示例</text>
  48. </view>
  49. </view>
  50. <!-- 已上传图片 -->
  51. <view v-if="task.images.length > 0" class="uploaded-images">
  52. <view
  53. v-for="(image, idx) in task.images"
  54. :key="idx"
  55. class="uploaded-image"
  56. >
  57. <image :src="image" mode="aspectFill"></image>
  58. </view>
  59. </view>
  60. <!-- 上传按钮 -->
  61. <view
  62. v-if="task.isimages"
  63. class="upload-btn"
  64. @tap="chooseImage(index)"
  65. >
  66. <view class="btn-content">
  67. <text class="btn-icon">+</text>
  68. <text class="btn-text">拍照</text>
  69. </view>
  70. </view>
  71. <!-- 状态选择 -->
  72. <view v-if="!task.details" class="status-select">
  73. <view class="radio-group">
  74. <view class="radio-item" @tap="toggleTaskStatus(index)">
  75. <view
  76. class="radio"
  77. :class="{ selected: task.status === 'normal' }"
  78. ></view>
  79. <text class="radio-text">正常</text>
  80. </view>
  81. <view class="radio-item" @tap="toggleTaskStatus(index)">
  82. <view
  83. class="radio"
  84. :class="{ selected: task.status === 'abnormal' }"
  85. ></view>
  86. <text class="radio-text">异常</text>
  87. </view>
  88. </view>
  89. </view>
  90. <!-- 带详情的状态选择 -->
  91. <view v-if="task.details" class="detail-status">
  92. <text class="detail-name">{{ task.details[0].name }}</text>
  93. <view class="radio-group">
  94. <view class="radio-item" @tap="toggleTaskStatus(index, 0)">
  95. <view
  96. class="radio"
  97. :class="{ selected: task.details[0].status === 'normal' }"
  98. ></view>
  99. <text class="radio-text">正常</text>
  100. </view>
  101. <view class="radio-item" @tap="toggleTaskStatus(index, 0)">
  102. <view
  103. class="radio"
  104. :class="{ selected: task.details[0].status === 'abnormal' }"
  105. ></view>
  106. <text class="radio-text">异常</text>
  107. </view>
  108. </view>
  109. <view class="detail-items">
  110. <view
  111. v-for="(detail, idx) in task.details.slice(1)"
  112. :key="idx"
  113. class="detail-item"
  114. >
  115. <view class="detail-name">{{ detail.name }}</view>
  116. <view
  117. class="radio-item"
  118. @tap="toggleTaskStatus(index, idx + 1)"
  119. >
  120. <view
  121. class="radio"
  122. :class="{ selected: detail.status === 'normal' }"
  123. ></view>
  124. <text class="radio-text">正常</text>
  125. </view>
  126. <view
  127. class="radio-item"
  128. @tap="toggleTaskStatus(index, idx + 1)"
  129. >
  130. <view
  131. class="radio"
  132. :class="{ selected: detail.status === 'abnormal' }"
  133. ></view>
  134. <text class="radio-text">异常</text>
  135. </view>
  136. </view>
  137. </view>
  138. </view>
  139. </view>
  140. </view>
  141. </scroll-view>
  142. <centerpopup v-model:centershow="popupshow.centershow" />
  143. <bottompopup v-model:bottomshow="popupshow.bottomshow" />
  144. <!-- 确定按钮 -->
  145. <view class="confirm-btn">
  146. <button class="btn" @click="submitTasks">确 定</button>
  147. </view>
  148. </view>
  149. </template>
  150. <script lang="ts" setup>
  151. import { ref, computed } from 'vue'
  152. import useUpload from '@/hooks/useUpload'
  153. import centerpopup from '../popup/centerpopup.vue'
  154. import bottompopup from '../popup/bottompopup.vue'
  155. const popupshow = ref({
  156. centershow: false,
  157. bottomshow: false,
  158. })
  159. // const popupshow = ref({})
  160. const tab = ref('前庭')
  161. const tabs = ['营销', '出入口', '前庭', '便利店', '洗手间']
  162. // 定义任务类型
  163. interface Task {
  164. id: string
  165. type: string
  166. description: string
  167. icon?: string
  168. instructions: string
  169. examples: string[]
  170. images: string[]
  171. status: 'normal' | 'abnormal'
  172. isimages: boolean
  173. details?: {
  174. name: string
  175. status: 'normal' | 'abnormal'
  176. }[]
  177. }
  178. // 任务数据
  179. const tasks = ref<Task[]>([
  180. {
  181. id: '1',
  182. type: '前庭',
  183. description:
  184. '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
  185. instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
  186. examples: [
  187. 'https://picsum.photos/id/1018/200/150',
  188. 'https://picsum.photos/id/1019/200/150',
  189. ],
  190. images: [],
  191. isimages: true,
  192. status: 'normal',
  193. },
  194. {
  195. id: '2',
  196. type: '前庭',
  197. description: '[卫生] 抽油枪栓是否出现空转或存在明显渗油。',
  198. icon: 'help-circle',
  199. instructions: '',
  200. examples: [],
  201. images: [],
  202. isimages: false,
  203. status: 'normal',
  204. details: [{ name: '12号枪', status: 'normal' }],
  205. },
  206. {
  207. id: '3',
  208. type: '前庭',
  209. description:
  210. '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
  211. instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
  212. examples: [
  213. 'https://picsum.photos/id/1018/200/150',
  214. 'https://picsum.photos/id/1019/200/150',
  215. ],
  216. images: ['https://picsum.photos/id/1025/200/150'],
  217. isimages: true,
  218. status: 'normal',
  219. },
  220. ])
  221. // 根据当前标签筛选任务
  222. const currentTasks = computed(() => {
  223. return tasks.value.filter((task) => task.type === tab.value)
  224. })
  225. // 选择图片
  226. const chooseImage = (index: number) => {
  227. // 为每个上传请求创建独立的upload实例,确保能正确处理对应的任务索引
  228. const { run: uploadImage } = useUpload({
  229. fileType: 'image',
  230. success: (res) => {
  231. console.log('上传成功', res)
  232. // 将上传成功的图片URL添加到对应任务的images数组中
  233. // 假设res包含图片URL,实际结构需要根据后端返回调整
  234. const imageUrl = typeof res === 'string' ? res : res.url || ''
  235. if (imageUrl) {
  236. tasks.value[index].images.push(imageUrl)
  237. }
  238. },
  239. error: (err) => {
  240. console.error('上传失败', err)
  241. },
  242. })
  243. uploadImage()
  244. }
  245. // 切换任务状态
  246. const toggleTaskStatus = (taskIndex: number, detailIndex?: number) => {
  247. const task = tasks.value[taskIndex]
  248. if (detailIndex !== undefined && task.details) {
  249. // 有子项的任务
  250. const detail = task.details[detailIndex]
  251. detail.status = detail.status === 'normal' ? 'abnormal' : 'normal'
  252. // 检查是否有任一子项异常
  253. task.status = task.details.some((d) => d.status === 'abnormal')
  254. ? 'abnormal'
  255. : 'normal'
  256. } else {
  257. // 无子项的任务
  258. task.status = task.status === 'normal' ? 'abnormal' : 'normal'
  259. }
  260. if (task.status === 'abnormal') {
  261. popupshow.value.bottomshow = true
  262. }
  263. }
  264. // 提交任务
  265. const submitTasks = () => {
  266. console.log('提交任务', tasks.value)
  267. // 这里可以添加提交逻辑
  268. uni.showToast({
  269. title: '提交成功',
  270. icon: 'success',
  271. })
  272. }
  273. // 关闭弹窗按钮
  274. // const handleClose = () => {
  275. // popupshow.value = false
  276. // }
  277. </script>
  278. <style lang="scss" scoped>
  279. .task-management {
  280. display: flex;
  281. flex-direction: column;
  282. height: 1500rpx;
  283. overflow: hidden;
  284. .fixed-area {
  285. flex-shrink: 0; // 不让它被压缩
  286. background-color: #fff;
  287. position: sticky; // 若想随外层滚就 sticky;想完全固定用 fixed 即可
  288. top: 0;
  289. z-index: 10;
  290. }
  291. .title {
  292. width: 100%;
  293. height: 128rpx;
  294. background-color: #4a80c3;
  295. display: flex;
  296. align-items: center;
  297. box-sizing: border-box;
  298. padding-left: 46rpx;
  299. }
  300. .title-text {
  301. font-size: 36rpx;
  302. font-weight: 500;
  303. color: #fff;
  304. }
  305. // // 任务容器
  306. // .task-container {
  307. // flex: 1;
  308. // padding: 0 30rpx;
  309. // .tasks {
  310. // padding: 20rpx 0;
  311. // }
  312. // }
  313. .task-container {
  314. flex: 1; // 占剩余高度
  315. overflow-y: auto; // 仅这里滚动
  316. padding: 0 30rpx;
  317. }
  318. // 任务项
  319. .task-item {
  320. margin-bottom: 40rpx;
  321. // 任务描述
  322. .task-description {
  323. display: flex;
  324. margin-bottom: 16rpx;
  325. .task-number {
  326. font-size: 28rpx;
  327. color: #333;
  328. margin-right: 8rpx;
  329. font-weight: 500;
  330. }
  331. .task-text {
  332. font-size: 28rpx;
  333. font-weight: 400;
  334. color: #333;
  335. line-height: 44rpx;
  336. }
  337. }
  338. // 操作说明
  339. .task-instructions {
  340. margin-bottom: 20rpx;
  341. .instructions-text {
  342. font-size: 24rpx;
  343. color: #666;
  344. line-height: 36rpx;
  345. }
  346. }
  347. // 示例图片
  348. .example-images {
  349. display: flex;
  350. gap: 16rpx;
  351. margin-bottom: 20rpx;
  352. .example-image {
  353. width: 140rpx;
  354. height: 100rpx;
  355. position: relative;
  356. image {
  357. width: 100%;
  358. height: 100%;
  359. border-radius: 8rpx;
  360. }
  361. .example-text {
  362. position: absolute;
  363. bottom: 0;
  364. left: 0;
  365. right: 0;
  366. background-color: rgba(0, 0, 0, 0.5);
  367. color: white;
  368. font-size: 20rpx;
  369. text-align: center;
  370. padding: 4rpx 0;
  371. border-radius: 0 0 8rpx 8rpx;
  372. }
  373. }
  374. }
  375. // 已上传图片
  376. .uploaded-images {
  377. display: flex;
  378. gap: 16rpx;
  379. margin-bottom: 20rpx;
  380. .uploaded-image {
  381. width: 140rpx;
  382. height: 100rpx;
  383. image {
  384. width: 100%;
  385. height: 100%;
  386. border-radius: 8rpx;
  387. }
  388. }
  389. }
  390. // 上传按钮
  391. .upload-btn {
  392. width: 140rpx;
  393. height: 100rpx;
  394. border: 2rpx dashed #ccc;
  395. border-radius: 8rpx;
  396. display: flex;
  397. align-items: center;
  398. justify-content: center;
  399. margin-bottom: 24rpx;
  400. .btn-content {
  401. display: flex;
  402. flex-direction: column;
  403. align-items: center;
  404. .btn-icon {
  405. font-size: 40rpx;
  406. color: #999;
  407. line-height: 40rpx;
  408. }
  409. .btn-text {
  410. font-size: 24rpx;
  411. color: #999;
  412. margin-top: 4rpx;
  413. }
  414. }
  415. }
  416. // 状态选择
  417. .status-select {
  418. margin-bottom: 20rpx;
  419. .radio-group {
  420. display: flex;
  421. gap: 40rpx;
  422. }
  423. }
  424. // 带详情的状态选择
  425. .detail-status {
  426. margin-bottom: 20rpx;
  427. .detail-name {
  428. font-size: 28rpx;
  429. color: #333;
  430. margin-right: 20rpx;
  431. // margin-bottom: 10rpx;
  432. }
  433. .radio-group {
  434. display: flex;
  435. gap: 40rpx;
  436. }
  437. .detail-items {
  438. margin-top: 16rpx;
  439. display: flex;
  440. flex-wrap: wrap;
  441. gap: 40rpx;
  442. }
  443. }
  444. // 单选按钮
  445. .radio-group {
  446. .radio-item {
  447. // margin-top: 50rpx;
  448. display: flex;
  449. align-items: center;
  450. gap: 8rpx;
  451. .radio {
  452. width: 32rpx;
  453. height: 32rpx;
  454. border: 2rpx solid #ccc;
  455. border-radius: 50%;
  456. display: flex;
  457. align-items: center;
  458. justify-content: center;
  459. &.selected {
  460. border-color: #215acd;
  461. // background-color: #4a80c3;
  462. &::after {
  463. content: '';
  464. width: 16rpx;
  465. height: 16rpx;
  466. border-radius: 50%;
  467. background-color: #215acd;
  468. }
  469. }
  470. }
  471. .radio-text {
  472. font-size: 28rpx;
  473. color: #333;
  474. }
  475. }
  476. }
  477. }
  478. // 确定按钮
  479. .confirm-btn {
  480. padding: 20rpx 30rpx;
  481. background-color: white;
  482. border-top: 1rpx solid #eee;
  483. .btn {
  484. width: 100%;
  485. height: 80rpx;
  486. background-color: #215acd;
  487. color: white;
  488. font-size: 32rpx;
  489. border-radius: 12rpx;
  490. font-weight: 400;
  491. border: none;
  492. outline: none;
  493. }
  494. }
  495. }
  496. </style>