index.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. <template>
  2. <div class="task-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
  3. <div class="task-top">
  4. <div class="map-container" ref="mapContainer"></div>
  5. </div>
  6. <div class="calendar-container">
  7. <customCalendar ref="calendarRef" @dateSelect="handleDateSelect"></customCalendar>
  8. </div>
  9. <div class="task-list">
  10. <div class="list-filter">
  11. <div class="filter-item" :class="{ active: activeIndex === 0 }" @click="handleActiveFilter(0)">
  12. {{ t("workExecute.pending") }}({{ taskCounts[0] || 0 }})
  13. </div>
  14. <div class="filter-item" :class="{ active: activeIndex === 2 }" @click="handleActiveFilter(2)">
  15. {{ t("workExecute.accepted") }}({{ taskCounts[2] || 0 }})
  16. </div>
  17. <div class="filter-item" :class="{ active: activeIndex === 3 }" @click="handleActiveFilter(3)">
  18. {{ t("workExecute.executing") }}({{ taskCounts[3] || 0 }})
  19. </div>
  20. <div class="filter-item" :class="{ active: activeIndex === 4 }" @click="handleActiveFilter(3)">
  21. {{ t("workExecute.completed") }}({{ taskCounts[4] || 0 }})
  22. </div>
  23. </div>
  24. <div class="list-box">
  25. <div class="list-header">
  26. <div class="select-group">
  27. <el-select class="select-item" v-model="selectParma.farmWorkTypeId" :placeholder="t('workExecute.farmWorkType')"
  28. @change="getSimpleList">
  29. <el-option v-for="item in farmWorkTypeList" :key="item.id"
  30. :label="item.id === 0 ? t('workExecute.all') : item.name"
  31. :value="item.id" />
  32. </el-select>
  33. <el-select class="select-item" v-model="selectParma.districtCode" :placeholder="t('workExecute.farmFilter')"
  34. @change="getSimpleList">
  35. <el-option v-for="item in districtList" :key="item.code" :label="item.name"
  36. :value="item.code" />
  37. </el-select>
  38. </div>
  39. <!-- <div class="action-btn">{{ t("workExecute.batchAccept") }}</div> -->
  40. </div>
  41. <!-- 任务列表 -->
  42. <div class="work-task-list" v-loading="loading" element-loading-background="rgba(0, 0, 0, 0.3)">
  43. <div class="task-item" v-for="(item, index) in taskList" :key="index"
  44. :class="isTimeoutItem(index) ? 'timeout-item' : ''" @click="handleItem(item, index)">
  45. <div class="item-title">
  46. <img class="task-icon" src="@/assets/img/home/task.png" alt="">
  47. <span class="title-text">{{ item.operation?.name }}</span>
  48. <span class="task-status" :class="`task-status--${item.operation?.type}`">{{
  49. item.operation?.typeName }}</span>
  50. </div>
  51. <span class="task-tag timeout" v-if="isTimeoutItem(index)">
  52. <el-icon>
  53. <WarningFilled />
  54. </el-icon>
  55. {{ getTimeoutText() }}
  56. </span>
  57. <span class="task-tag" v-else>
  58. {{ getStatusLabel(activeStatus) }}
  59. </span>
  60. <div class="item-content">
  61. <div class="item-info">
  62. <div class="info-item">
  63. <div class="info-name">{{ t("workExecute.responsible") }}</div><span class="val-text">
  64. 某某某、某某某</span>
  65. </div>
  66. <div class="info-item" v-if="mode !== 'review'">
  67. <div class="info-name">{{ t("workExecute.farmWorkDetail") }}</div><span class="val-text">
  68. {{ item.operation?.drug }}</span>
  69. </div>
  70. <div class="info-item">
  71. <div class="info-name">{{ t("workExecute.farmSituation") }}</div><span class="val-text">
  72. {{ item.operation?.work_reason }}</span>
  73. </div>
  74. </div>
  75. <div class="excutor-info">
  76. <div class="executor-stats">
  77. <div class="stat-cell">
  78. <div class="stat-value">
  79. {{ item.time ? formatGMTToYMD(item.time) : "--" }}
  80. </div>
  81. <div class="stat-label">{{ t("workExecute.executeTime") }}</div>
  82. </div>
  83. <div class="cell-line"></div>
  84. <div class="stat-cell">
  85. <div class="stat-value">{{ item.geohash_sample || "--" }}</div>
  86. <div class="stat-label">{{ t("workExecute.executeArea") }}</div>
  87. </div>
  88. <div class="cell-line"></div>
  89. <div class="stat-cell">
  90. <div class="stat-value">{{ item.operation?.machine_code || "--" }}</div>
  91. <div class="stat-label">{{ t("workExecute.executeMachine") }}</div>
  92. </div>
  93. </div>
  94. </div>
  95. </div>
  96. <div class="unqualified-reason" v-if="activeStatus === '未达标'">
  97. {{ t("workExecute.unqualifiedReason") }}{{ item.operation?.reason || t("workExecute.defaultUnqualifiedReason") }}
  98. </div>
  99. <div class="compare-imgs" v-if="activeStatus === '未达标' && (item.beforeImage || item.afterImage)"
  100. @click.stop>
  101. <div class="img-tag">{{ t("workExecute.before") }}</div>
  102. <div class="img-tag right-tag">{{ t("workExecute.after") }}</div>
  103. <div class="img-item" v-if="item.beforeImage">
  104. <img :src="getWorkImageUrl(item.beforeImage)" alt="" />
  105. </div>
  106. <div class="img-item" v-if="item.afterImage">
  107. <img :src="getWorkImageUrl(item.afterImage)" alt="" />
  108. </div>
  109. </div>
  110. <div class="task-footer">
  111. <div class="farm-info">{{ t("workExecute.fromFarm") }}</div>
  112. <div class="btn-group" v-if="getItemStatusButtons(index).length">
  113. <div v-for="btn in getItemStatusButtons(index)" :key="btn.label" class="edit-btn"
  114. :class="btn.type" @click.stop="handleStatusBtn(btn, item, index)">
  115. {{ btn.label }}
  116. </div>
  117. </div>
  118. </div>
  119. </div>
  120. <div class="empty-tip" v-if="!loading && taskList.length === 0">{{ t("workExecute.noData") }}</div>
  121. </div>
  122. </div>
  123. </div>
  124. </div>
  125. <!-- <upload-execute ref="uploadExecuteRef" :onlyShare="onlyShare" @uploadSuccess="handleUploadSuccess" /> -->
  126. <!-- 服务报价单 -->
  127. <!-- <price-sheet-popup :key="activeIndex" ref="priceSheetPopupRef"></price-sheet-popup> -->
  128. <!-- 新增:激活上传弹窗 -->
  129. <!-- <active-upload-popup ref="activeUploadPopupRef" @handleUploadSuccess="handleUploadSuccess"></active-upload-popup> -->
  130. </template>
  131. <script setup>
  132. import { computed, nextTick, onActivated, onMounted, ref, watch } from "vue";
  133. import { useRoute } from "vue-router";
  134. import { useStore } from "vuex";
  135. import IndexMap from "./index";
  136. import { useRouter } from "vue-router";
  137. import { WarningFilled } from "@element-plus/icons-vue";
  138. import { ElMessage } from "element-plus";
  139. import config from "@/api/config.js";
  140. import customCalendar from "./components/calendar.vue";
  141. import { useI18n } from "@/i18n";
  142. const { t } = useI18n();
  143. const store = useStore();
  144. const router = useRouter();
  145. const route = useRoute();
  146. const indexMap = new IndexMap();
  147. const mapContainer = ref(null);
  148. const tabBarHeight = computed(() => store.state.home.tabBarHeight);
  149. const selectParma = ref({
  150. farmWorkTypeId: null,
  151. districtCode: null,
  152. });
  153. // 任务列表数据(用于显示,可能被筛选)
  154. const taskList = ref([
  155. {
  156. time: "Fri, 22 May 2026 00:00:00 GMT",
  157. geohash_sample: "ws0gs49ns213",
  158. farmName: "冬季清园",
  159. typeName: "荔枝",
  160. userType: 1,
  161. address: "广东省广州市从化区太平镇凤凰路88号",
  162. farmPoint: "POINT(113.61652616170711 23.58399613872042)",
  163. sourceData: {
  164. currentPhenologyName: "花芽分化",
  165. currentPhenologyStartDate: "2026-01-15",
  166. daysUntilNext: 3,
  167. },
  168. operation: {
  169. name: "冬季清园",
  170. type: "1",
  171. typeName: "标准类",
  172. work_reason: "近期气温回升,需及时清理园内枯枝落叶,减少病虫越冬基数",
  173. drug: "石硫合剂 500倍液",
  174. machine_code: "DJI-T40-001",
  175. work_id: 5001,
  176. },
  177. },
  178. {
  179. time: "Mon, 26 May 2026 00:00:00 GMT",
  180. geohash_sample: "ws0gefxm68u5",
  181. farmName: "施肥促花",
  182. typeName: "柑橘",
  183. userType: 2,
  184. address: "广东省肇庆市德庆县官圩镇果园示范区",
  185. farmPoint: "POINT(113.62757101477101 23.590796948574365)",
  186. sourceData: {
  187. currentPhenologyName: "开花期",
  188. currentPhenologyStartDate: "2026-02-20",
  189. daysUntilNext: 12,
  190. },
  191. operation: {
  192. name: "施肥促花",
  193. type: "2",
  194. typeName: "感知类",
  195. work_reason: "花芽分化期养分需求增加,需补充磷钾肥",
  196. drug: "磷酸二氢钾 0.3%",
  197. machine_code: "DJI-T50-003",
  198. work_id: 5002,
  199. },
  200. },
  201. {
  202. time: "Wed, 28 May 2026 00:00:00 GMT",
  203. geohash_sample: "ws0gefzjqqqw",
  204. farmId: 1003,
  205. farmName: "病虫害防治",
  206. typeName: "水稻",
  207. userType: 1,
  208. address: "广东省清远市清城区龙塘镇农业科技园",
  209. farmPoint: "POINT(113.62240816252164 23.59499176519138)",
  210. sourceData: {
  211. currentPhenologyName: "分蘖期",
  212. currentPhenologyStartDate: "2026-03-01",
  213. daysUntilNext: 8,
  214. },
  215. operation: {
  216. name: "病虫害防治",
  217. type: "1",
  218. typeName: "复核类",
  219. work_reason: "监测到蚜虫活动增多,建议及时喷药防治",
  220. drug: "吡虫啉 1500倍液",
  221. machine_code: "DJI-T40-002",
  222. work_id: 5003,
  223. },
  224. },
  225. ]);
  226. const mode = "execute";
  227. const activeStatus = ref("待接受");
  228. const STATUS_BTN_MAP = {
  229. 待接受: [
  230. { labelKey: "workExecute.redispatch", type: "normal", action: "redispatch" },
  231. { labelKey: "workExecute.remindAccept", type: "primary", action: "remindAccept" },
  232. ],
  233. 已接受: [{ labelKey: "workExecute.remindExecute", type: "primary", action: "remindExecute" }],
  234. 执行中: [{ labelKey: "workExecute.remindComplete", type: "primary", action: "remindComplete" }],
  235. 已超时: [
  236. { labelKey: "workExecute.redispatch", type: "normal", action: "redispatch" },
  237. { labelKey: "workExecute.remindAccept", type: "primary", action: "remindAccept" },
  238. ],
  239. 未达标: [{ labelKey: "workExecute.redispatchAgain", type: "primary", action: "redispatch" }],
  240. };
  241. const STATUS_LABEL_MAP = {
  242. 待接受: "workExecute.pending",
  243. 已接受: "workExecute.accepted",
  244. 执行中: "workExecute.executing",
  245. 已完成: "workExecute.completed",
  246. 已超时: "workExecute.timeout",
  247. 未达标: "workExecute.unqualified",
  248. };
  249. const getStatusLabel = (status) => t(STATUS_LABEL_MAP[status] || status);
  250. const isTimeoutItem = (index) => index === 0;
  251. const getTimeoutRemindBtn = () => {
  252. const map = {
  253. 待接受: { labelKey: "workExecute.remindAccept", type: "primary", action: "remindAccept" },
  254. 已接受: { labelKey: "workExecute.remindExecute", type: "primary", action: "remindExecute" },
  255. 执行中: { labelKey: "workExecute.remindComplete", type: "primary", action: "remindComplete" },
  256. };
  257. const btn = map[activeStatus.value];
  258. return btn ? { ...btn, label: t(btn.labelKey) } : null;
  259. };
  260. const mapStatusButtons = (buttons) =>
  261. buttons.map((btn) => ({
  262. ...btn,
  263. label: t(btn.labelKey),
  264. }));
  265. const getItemStatusButtons = (index) => {
  266. const baseButtons = mapStatusButtons(STATUS_BTN_MAP[activeStatus.value] || []);
  267. if (!isTimeoutItem(index)) {
  268. return baseButtons;
  269. }
  270. if (activeStatus.value === "待接受") {
  271. return baseButtons;
  272. }
  273. const remindBtn = getTimeoutRemindBtn();
  274. if (remindBtn) {
  275. return [
  276. { label: t("workExecute.redispatch"), type: "normal", action: "redispatch" },
  277. remindBtn,
  278. ];
  279. }
  280. return [
  281. { label: t("workExecute.redispatch"), type: "normal", action: "redispatch" },
  282. ...baseButtons,
  283. ];
  284. };
  285. const getTimeoutText = () => {
  286. const map = {
  287. 待接受: "workExecute.acceptTimeout",
  288. 已接受: "workExecute.executeTimeout",
  289. 执行中: "workExecute.completeTimeout",
  290. };
  291. return t(map[activeStatus.value] || "workExecute.timeout");
  292. };
  293. const formatGMTToYMD = (gmtString) => {
  294. const date = new Date(gmtString);
  295. return date.toISOString().split("T")[0];
  296. };
  297. const getWorkImageUrl = (image) => {
  298. if (!image?.cloud_filename) return "";
  299. return config.base_img_url3 + image.cloud_filename;
  300. };
  301. const handleItem = (item, index) => {
  302. router.push({
  303. path: "/agri_record_detail",
  304. query: {
  305. miniJson: JSON.stringify({
  306. id: item.operation?.work_id,
  307. status: activeStatus.value,
  308. }),
  309. },
  310. });
  311. };
  312. const handleStatusBtn = (btn, item, index) => {
  313. if (btn.action === "redispatch") {
  314. ElMessage.info(t("workExecute.redispatchMsg", { name: item.operation?.name }));
  315. } else {
  316. ElMessage.success(t("workExecute.remindSuccess"));
  317. }
  318. };
  319. const calendarRef = ref(null);
  320. const filterDate = ref(null);
  321. const fullTaskList = ref([]);
  322. const getCalendarTaskList = (list) => {
  323. return list.map((item) => ({
  324. expectedExecuteDate: item.time,
  325. executeDeadlineDate: item.time,
  326. }));
  327. };
  328. const syncCalendarData = () => {
  329. nextTick(() => {
  330. calendarRef.value?.setSolarTerm(getCalendarTaskList(fullTaskList.value));
  331. });
  332. };
  333. const handleDateSelect = (date) => {
  334. filterDate.value = date;
  335. // if (!date) {
  336. // taskList.value = [...fullTaskList.value];
  337. // } else {
  338. // taskList.value = fullTaskList.value.filter((item) => {
  339. // if (!item.time) return false;
  340. // return formatGMTToYMD(item.time) === date;
  341. // });
  342. // }
  343. syncCalendarData();
  344. };
  345. // 各状态任务数量
  346. const taskCounts = ref([0, 0, 0]);
  347. // 当前选中的筛选索引
  348. const activeIndex = ref(2);
  349. const noData = ref(false);
  350. const loading = ref(false);
  351. // 分页相关
  352. const page = ref(0);
  353. const limit = ref(10);
  354. const loadingMore = ref(false);
  355. const finished = ref(false);
  356. // 查询未来农事预警
  357. const getFutureFarmWorkWarning = async (item) => {
  358. const res = await VE_API.home.listFutureFarmWorkWarning({ farmId: item.farmId });
  359. item.timelineList = res.data || [];
  360. };
  361. const cityCode = ref("");
  362. //根据城市的坐标返回区县列表
  363. const districtList = ref([]);
  364. //农事类型列表
  365. const farmWorkTypeList = ref([]);
  366. function getFarmWorkTypeList() {
  367. VE_API.z_farm_work_record.getFarmWorkTypeList().then(({ data }) => {
  368. farmWorkTypeList.value = data;
  369. farmWorkTypeList.value.unshift({ id: 0, name: "全部" });
  370. });
  371. }
  372. const mapPoint = ref(null);
  373. onMounted(() => {
  374. mapPoint.value = store.state.home.miniUserLocationPoint;
  375. fullTaskList.value = [...taskList.value];
  376. // getDistrictListByCity();
  377. resetAndLoad();
  378. getFarmWorkTypeList();
  379. syncCalendarData();
  380. nextTick(() => {
  381. indexMap.initMap(mapPoint.value, mapContainer.value, true);
  382. // 更新地图数据
  383. indexMap.initData(taskList.value, '', 'farmPoint');
  384. });
  385. });
  386. onActivated(() => {
  387. if (route.query.noReload) {
  388. return;
  389. }
  390. // 确保地图已初始化,使用 nextTick 等待 DOM 更新
  391. nextTick(() => {
  392. // 检查地图实例是否已初始化
  393. if (!indexMap.kmap) {
  394. // 如果地图未初始化,重新初始化
  395. if (mapContainer.value) {
  396. mapPoint.value = store.state.home.miniUserLocationPoint;
  397. indexMap.initMap(mapPoint.value, mapContainer.value, true);
  398. // 等待地图初始化完成后再加载数据
  399. setTimeout(() => {
  400. resetAndLoad();
  401. }, 300);
  402. return;
  403. }
  404. } else {
  405. // 如果地图已初始化,需要等待 tab 切换完成,容器完全可见后再更新尺寸
  406. // Tab 切换时容器可能被隐藏,需要更长的延迟确保容器可见
  407. if (mapContainer.value && indexMap.kmap.map) {
  408. // 检查容器是否可见
  409. const checkAndUpdateSize = () => {
  410. const container = mapContainer.value;
  411. if (container) {
  412. const rect = container.getBoundingClientRect();
  413. // 如果容器可见(有宽度和高度),更新地图尺寸
  414. if (rect.width > 0 && rect.height > 0) {
  415. indexMap.kmap.map.updateSize();
  416. } else {
  417. // 如果容器不可见,继续等待
  418. setTimeout(checkAndUpdateSize, 100);
  419. }
  420. }
  421. };
  422. // 延迟检查,确保 tab 切换完成
  423. setTimeout(checkAndUpdateSize, 200);
  424. }
  425. }
  426. resetAndLoad();
  427. });
  428. });
  429. // 监听 activeIndex 变化,重新加载数据
  430. watch(activeIndex, () => {
  431. resetAndLoad();
  432. });
  433. // 加载列表数据(支持分页)
  434. async function getSimpleList(isLoadMore = false) {
  435. if (!isLoadMore) {
  436. // 重置分页
  437. page.value = 0;
  438. finished.value = false;
  439. // taskList.value = [];
  440. loading.value = true;
  441. } else {
  442. loadingMore.value = true;
  443. }
  444. const params = {
  445. ...selectParma.value,
  446. page: page.value,
  447. limit: limit.value,
  448. flowStatus: 5,
  449. };
  450. try {
  451. const { data } = await VE_API.home.listUnansweredFarms(params);
  452. if (data && data.length > 0) {
  453. // 为每个item初始化timelineList
  454. const newItems = data.map((item) => {
  455. let sourceData = item?.latestPhenologyProgressBroadcast?.sourceData;
  456. if (sourceData) {
  457. try {
  458. sourceData = JSON.parse(sourceData);
  459. } catch (e) {
  460. console.error("解析sourceData失败:", e);
  461. sourceData = null;
  462. }
  463. }
  464. return {
  465. ...item,
  466. sourceData,
  467. timelineList: [],
  468. };
  469. });
  470. // 串行请求,为每个农场获取时间轴数据
  471. for (let i = 0; i < newItems.length; i++) {
  472. await getFutureFarmWorkWarning(newItems[i]);
  473. }
  474. // 追加数据
  475. // const newTaskList = [...taskList.value, ...newItems];
  476. // taskList.value = newTaskList.filter(item => item.timelineList.length > 0);
  477. // 更新分页
  478. page.value += 1;
  479. // 判断是否还有更多数据
  480. if (data.length < limit.value) {
  481. finished.value = true;
  482. }
  483. // 更新地图数据
  484. indexMap.initData(taskList.value, '', 'farmPoint');
  485. } else {
  486. finished.value = true;
  487. if (taskList.value.length === 0) {
  488. noData.value = true;
  489. }
  490. }
  491. // 数据处理完成后再设置loading为false
  492. if (!isLoadMore) {
  493. loading.value = false;
  494. } else {
  495. loadingMore.value = false;
  496. }
  497. } catch (error) {
  498. console.error("获取任务列表失败:", error);
  499. if (!isLoadMore) {
  500. loading.value = false;
  501. } else {
  502. loadingMore.value = false;
  503. }
  504. finished.value = true;
  505. if (taskList.value.length === 0) {
  506. noData.value = true;
  507. }
  508. }
  509. }
  510. // 滚动加载更多
  511. const onLoad = () => {
  512. if (!finished.value && !loadingMore.value) {
  513. getSimpleList(true);
  514. }
  515. };
  516. // 重置并重新加载
  517. const resetAndLoad = () => {
  518. getSimpleList(false);
  519. };
  520. function handleActiveFilter(i) {
  521. activeIndex.value = i;
  522. selectParma.value.districtCode = cityCode.value;
  523. selectParma.value.farmWorkTypeId = null;
  524. const statusMap = { 0: "待接受", 2: "已接受", 3: "执行中" };
  525. activeStatus.value = statusMap[i] || "待接受";
  526. filterDate.value = null;
  527. taskList.value = [...fullTaskList.value];
  528. calendarRef.value?.clearSelection();
  529. syncCalendarData();
  530. }
  531. function handleRemindCustomer(item) {
  532. // 接受
  533. }
  534. </script>
  535. <style lang="scss" scoped>
  536. .task-page {
  537. width: 100%;
  538. height: calc(100vh - 50px);
  539. overflow: auto;
  540. box-sizing: border-box;
  541. background: #FFFFFF;
  542. .map-container {
  543. width: 100%;
  544. height: 162px;
  545. clip-path: inset(0px round 8px);
  546. }
  547. .list-box {
  548. background: #F1F3F4;
  549. padding: 12px 12px 8px 12px;
  550. .list-header {
  551. display: flex;
  552. align-items: center;
  553. justify-content: space-between;
  554. .action-btn {
  555. box-sizing: border-box;
  556. height: 32px;
  557. line-height: 30px;
  558. color: #2199F8;
  559. font-size: 14px;
  560. border-radius: 5px;
  561. padding: 0 10px;
  562. background: #FFFFFF;
  563. border: 1px solid #2199F8;
  564. color: #2199F8;
  565. }
  566. }
  567. }
  568. .select-group {
  569. display: flex;
  570. padding: 0px 12px 0 12px;
  571. .select-item {
  572. width: 100%;
  573. ::v-deep {
  574. .el-select__wrapper {
  575. text-align: center;
  576. gap: 2px;
  577. box-shadow: none;
  578. justify-content: center;
  579. background: none;
  580. }
  581. .el-select__selection {
  582. flex: none;
  583. width: fit-content;
  584. }
  585. .el-select__placeholder {
  586. position: static;
  587. transform: none;
  588. width: fit-content;
  589. color: rgba(0, 0, 0, 0.2);
  590. }
  591. .el-select__caret {
  592. color: rgba(0, 0, 0, 0.2);
  593. }
  594. }
  595. }
  596. }
  597. .calendar-container {
  598. padding: 4px 12px 8px 12px;
  599. }
  600. .task-top {
  601. padding: 10px 12px 0 12px;
  602. }
  603. .task-content-loading {
  604. height: 80px;
  605. border-radius: 8px;
  606. position: absolute;
  607. top: 60px;
  608. left: 0;
  609. width: 100%;
  610. }
  611. .task-content {
  612. min-height: 80px;
  613. }
  614. .empty-data {
  615. text-align: center;
  616. font-size: 14px;
  617. color: #6f7274;
  618. padding: 20px 0;
  619. }
  620. .task-list {
  621. position: relative;
  622. background: #fff;
  623. }
  624. .list-filter {
  625. display: flex;
  626. align-items: center;
  627. justify-content: space-around;
  628. margin-top: 10px;
  629. .filter-item {
  630. padding: 0 6px;
  631. height: 32px;
  632. color: #1D2129;
  633. font-size: 14px;
  634. border-radius: 20px;
  635. position: relative;
  636. &.active {
  637. color: #2199f8;
  638. &::after {
  639. content: "";
  640. position: absolute;
  641. bottom: 0;
  642. left: 0;
  643. width: 100%;
  644. height: 2px;
  645. background: #2199f8;
  646. }
  647. }
  648. }
  649. }
  650. .work-task-list {
  651. margin-top: 10px;
  652. .task-item {
  653. margin-top: 10px;
  654. background: #fff;
  655. border-radius: 8px;
  656. padding: 12px 16px;
  657. border: 1px solid rgba(0, 0, 0, 0.08);
  658. position: relative;
  659. &.timeout-item {
  660. border: 1px solid #f74e4e;
  661. }
  662. .item-title {
  663. display: flex;
  664. align-items: center;
  665. padding-bottom: 10px;
  666. border-bottom: 1px solid rgba(0, 0, 0, 0.06);
  667. color: #1d2129;
  668. font-size: 16px;
  669. .task-icon {
  670. width: 16px;
  671. }
  672. .title-text {
  673. padding-left: 8px;
  674. }
  675. .task-status {
  676. margin-left: 8px;
  677. border-radius: 2px;
  678. height: 20px;
  679. line-height: 20px;
  680. padding: 1px 6px;
  681. box-sizing: border-box;
  682. font-size: 12px;
  683. color: #ffb32f;
  684. background: rgba(255, 179, 47, 0.1);
  685. &.task-status--1 {
  686. background: rgba(33, 153, 248, 0.1);
  687. color: #2199f8;
  688. }
  689. &.task-status--2 {
  690. background: rgba(255, 179, 47, 0.1);
  691. color: #FFB32F;
  692. }
  693. &.task-status--3 {
  694. background: rgba(58, 173, 148, 0.1);
  695. color: #3AAD94;
  696. }
  697. }
  698. }
  699. .task-tag {
  700. position: absolute;
  701. right: 0;
  702. top: 0;
  703. background: rgba(255, 149, 61, 0.1);
  704. color: #F46E00;
  705. font-size: 12px;
  706. padding: 0 10px;
  707. height: 25px;
  708. line-height: 25px;
  709. border-radius: 0 8px 0 8px;
  710. &.timeout {
  711. background: rgba(255, 106, 106, 0.1);
  712. color: #FF6A6A;
  713. display: flex;
  714. align-items: center;
  715. gap: 2px;
  716. }
  717. }
  718. .item-content {
  719. padding: 12px 0;
  720. .excutor-info {
  721. margin-top: 12px;
  722. background: rgba(189, 189, 189, 0.1);
  723. border-radius: 6px;
  724. padding-bottom: 12px;
  725. .excutor-info-top {
  726. padding: 12px 12px 0 12px;
  727. }
  728. }
  729. .executor-stats {
  730. display: flex;
  731. align-items: center;
  732. padding-top: 10px;
  733. justify-content: space-around;
  734. }
  735. .cell-line {
  736. height: 20px;
  737. width: 1px;
  738. background: #e5e6eb;
  739. }
  740. .stat-value {
  741. font-size: 14px;
  742. font-weight: 500;
  743. color: #0B0B0B;
  744. margin-bottom: 4px;
  745. word-break: break-all;
  746. }
  747. .stat-label {
  748. font-size: 12px;
  749. line-height: 20px;
  750. color: rgba(107, 107, 107, 0.5);
  751. text-align: center;
  752. }
  753. }
  754. .item-info {
  755. color: rgba(111, 114, 116, 0.6);
  756. font-size: 14px;
  757. line-height: 21px;
  758. .info-item {
  759. display: flex;
  760. .info-name {
  761. flex: none;
  762. }
  763. }
  764. .val-text {
  765. // color: #1d2129;
  766. }
  767. .info-item+.info-item {
  768. padding-top: 6px;
  769. }
  770. }
  771. .task-footer {
  772. padding-top: 4px;
  773. display: flex;
  774. align-items: center;
  775. justify-content: space-between;
  776. .farm-info {
  777. font-size: 14px;
  778. color: rgba(32, 32, 32, 0.4);
  779. }
  780. }
  781. .btn-group {
  782. display: flex;
  783. align-items: center;
  784. justify-content: end;
  785. gap: 10px;
  786. }
  787. .edit-btn {
  788. height: 30px;
  789. padding: 0 16px;
  790. border-radius: 24px;
  791. text-align: center;
  792. line-height: 30px;
  793. font-size: 14px;
  794. &.normal {
  795. background: #F6F6F6;
  796. color: #000000;
  797. }
  798. &.primary {
  799. background: #FF953D;
  800. color: #fff;
  801. }
  802. }
  803. .compare-imgs {
  804. display: flex;
  805. align-items: center;
  806. gap: 10px;
  807. position: relative;
  808. margin: 10px 0;
  809. .img-tag {
  810. position: absolute;
  811. z-index: 10;
  812. top: 0;
  813. left: 0;
  814. font-size: 12px;
  815. color: #fff;
  816. background: rgba(0, 0, 0, 0.7);
  817. border-radius: 5px 0 5px 0;
  818. padding: 0 8px;
  819. height: 15px;
  820. line-height: 15px;
  821. &.right-tag {
  822. right: 0;
  823. left: auto;
  824. border-radius: 0 5px 0 5px;
  825. }
  826. }
  827. .img-item {
  828. flex: 1;
  829. height: 120px;
  830. overflow: hidden;
  831. border: 0.5px solid rgba(0, 0, 0, 0.1);
  832. border-radius: 5px;
  833. img {
  834. width: 100%;
  835. height: 100%;
  836. border-radius: 5px;
  837. object-fit: cover;
  838. }
  839. }
  840. }
  841. }
  842. .unqualified-reason {
  843. color: #f74e4e;
  844. font-size: 12px;
  845. padding: 4px 10px;
  846. background: rgba(247, 78, 78, 0.08);
  847. border-radius: 4px;
  848. line-height: 18px;
  849. }
  850. .empty-tip {
  851. text-align: center;
  852. color: #86909c;
  853. font-size: 14px;
  854. padding: 20px 0;
  855. }
  856. }
  857. }
  858. </style>