index.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. <template>
  2. <div class="task-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
  3. <farm-header class="task-page__header" />
  4. <div class="task-top">
  5. <div class="map-container" ref="mapContainer"></div>
  6. </div>
  7. <div class="calendar-container">
  8. <customCalendar ref="calendarRef" @dateSelect="handleDateSelect"></customCalendar>
  9. </div>
  10. <div class="task-list">
  11. <div class="task-tabs">
  12. <div class="tab-item" :class="{ active: activeTab === 'pending' }" @click="handleTabChange('pending')">
  13. 待执行({{ pendingCount }})
  14. </div>
  15. <div class="tab-item" :class="{ active: activeTab === 'completed' }" @click="handleTabChange('completed')">
  16. 已完成({{ completedCount }})
  17. </div>
  18. </div>
  19. <div class="work-task-list">
  20. <div
  21. v-for="item in displayTaskList"
  22. :key="item.id"
  23. class="task-card"
  24. @click.stop="handleViewDetail(item)"
  25. :class="{ 'is-risk': item.isHighRisk }"
  26. >
  27. <div v-if="item.status === 'pending' && item.cornerTip" class="task-card__corner">{{ item.cornerTip }}</div>
  28. <div v-if="item.status === 'completed' && item.cornerTip" class="task-card__corner done-corner" :class="{ 'myself-corner': item.isByMyself }">
  29. <el-icon size="14" class="myself-corner__icon" :class="{ 'myself-corner__icon-success': item.isByMyself }"><SuccessFilled /></el-icon>
  30. {{ item.cornerTip }}
  31. </div>
  32. <div class="task-card__header">
  33. <!-- <img class="task-card__icon" src="@/assets/img/home/task.png" alt="" /> -->
  34. <span class="task-card__title van-ellipsis">{{ item.executeDate + ' ' + item.title }}</span>
  35. <span v-if="item.isNormal" class="badge-tag normal-badge">标准类</span>
  36. <span v-if="item.isAbnormal" class="badge-tag abnormal-badge">异常类</span>
  37. <span v-if="item.isWeather" class="badge-tag weather-badge">气象类</span>
  38. <span v-if="item.isHighRisk" class="badge-tag risk-badge">{{ t("workExecute.highRisk") }}</span>
  39. </div>
  40. <div class="task-card__body">
  41. <!-- <div class="info-line">
  42. 触发条件:{{ item.fw_trigger || '--' }}
  43. </div> -->
  44. <div class="info-line">
  45. {{ t("workExecute.farmSituation") }}{{ item.analysisSummary }}
  46. </div>
  47. <div class="info-line">
  48. {{ t("workExecute.executeAreaLabel") }}{{ item.areaCode }}
  49. </div>
  50. </div>
  51. <div class="task-card__footer">
  52. <!-- <div class="footer-l">
  53. <img class="footer-l__icon" src="@/assets/img/home/user-icon-fill.png" alt="">
  54. 外包农事
  55. </div> -->
  56. <div class="footer-r">
  57. <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
  58. {{ t("workExecute.forward") }}
  59. </div>
  60. <div class="card-btn card-btn--primary" @click.stop="handleViewDetail(item)">
  61. 我已完成
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. <div v-if="!displayTaskList.length" class="empty-tip">{{ t("workExecute.noData") }}</div>
  67. </div>
  68. </div>
  69. <farm-calendar-popup v-model:show="showFarmCalendarPopup" @confirm="goCompleteFarmInfo" />
  70. <survey-ask-popup v-model:show="showSurveyAskPopup" @confirm="handleSurveyConfirm" />
  71. <!-- 诊断报告弹窗:组件内接口判断 exists=2 且本地未展示过 -->
  72. <diagnosis-report-popup />
  73. <!-- 恢复种植弹窗 -->
  74. <restore-planting-popup />
  75. </div>
  76. </template>
  77. <script setup>
  78. import { computed, nextTick, onActivated, onDeactivated, onMounted, ref } from "vue";
  79. import { useStore } from "vuex";
  80. import { useRouter } from "vue-router";
  81. import IndexMap from "./index";
  82. import customCalendar from "./components/calendar.vue";
  83. import farmHeader from "@/components/farmHeader.vue";
  84. import farmCalendarPopup from "@/components/popup/farmCalendarPopup.vue";
  85. import surveyAskPopup from "@/components/popup/surveyAskPopup.vue";
  86. import diagnosisReportPopup from "@/components/popup/diagnosisReportPopup.vue";
  87. import restorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
  88. import { useI18n } from "@/i18n";
  89. import wx from "weixin-js-sdk";
  90. // ---------- 常量 ----------
  91. /** 任务类型:fw_tasks 标准农事 / weather_work 气象农事 / abnormal_fw_tasks 异常农事 */
  92. const TASK_KIND = {
  93. NORMAL: "normal",
  94. WEATHER: "weather",
  95. ABNORMAL: "abnormal",
  96. };
  97. /** 方案文案优先取人工,接口里标准任务是字符串,气象/异常是按方案拆开的对象 */
  98. const SCHEME_TEXT_KEYS = ["人工方案", "机械方案", "人工", "机械"];
  99. /** 列表进详情:整条任务缓存,详情页不再打 fw_task_details */
  100. const FW_TASK_CACHE_KEY = "WORK_EXECUTE_FW_TASK_DETAIL";
  101. const COMPLETED_STATUS_RE = /已完成|已执行|已认证|本人完成/;
  102. const { t } = useI18n();
  103. const store = useStore();
  104. const router = useRouter();
  105. const indexMap = new IndexMap();
  106. const mapContainer = ref(null);
  107. const calendarRef = ref(null);
  108. const showFarmCalendarPopup = ref(false);
  109. /** 调查问卷弹窗:后续由接口决定是否展示 */
  110. const showSurveyAskPopup = ref(false);
  111. const tabBarHeight = computed(() => store.state.home.tabBarHeight);
  112. // ---------- 工具函数 ----------
  113. function getMiniUserId() {
  114. return store.state.home.miniUserId || localStorage.getItem("MINI_USER_ID");
  115. }
  116. function getTodayDate() {
  117. const now = new Date();
  118. const y = now.getFullYear();
  119. const m = String(now.getMonth() + 1).padStart(2, "0");
  120. const d = String(now.getDate()).padStart(2, "0");
  121. return `${y}-${m}-${d}`;
  122. }
  123. function getSelectedFarm() {
  124. try {
  125. return JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
  126. } catch {
  127. return {};
  128. }
  129. }
  130. function getSelectedFarmId() {
  131. const stored = localStorage.getItem("selectedFarmId");
  132. if (stored != null && stored !== "") return Number(stored);
  133. const farm = getSelectedFarm();
  134. const id = farm.farm_id ?? farm.id;
  135. return id != null && id !== "" ? Number(id) : null;
  136. }
  137. function getSelectedZoneId() {
  138. const farm = getSelectedFarm();
  139. const id = farm.zone_id ?? farm.zoneId;
  140. return id != null && id !== "" ? Number(id) : null;
  141. }
  142. /** crop_type 取作物编码,如 LCH;各入口字段不统一 */
  143. function getSelectedCropType() {
  144. const farm = getSelectedFarm();
  145. return (
  146. farm.category_code ||
  147. farm.crop_code ||
  148. farm.crop_type ||
  149. farm.farm_category ||
  150. farm.farm_variety ||
  151. ""
  152. );
  153. }
  154. // ---------- 页面状态 ----------
  155. const filterDate = ref(getTodayDate()); // 当前选中日期
  156. const taskList = ref([]); // 已映射的农事卡片(归属接口 date)
  157. const mapCenterPoint = ref(null); // 接口返回的分区中心点 WKT
  158. const activeTab = ref("pending");
  159. // ---------- 计算属性 ----------
  160. const dayTaskList = computed(() =>
  161. taskList.value.filter((item) => item.executeDateFull === filterDate.value)
  162. );
  163. const pendingCount = computed(() => dayTaskList.value.filter((item) => item.status === "pending").length);
  164. const completedCount = computed(() => dayTaskList.value.filter((item) => item.status === "completed").length);
  165. const displayTaskList = computed(() => dayTaskList.value.filter((item) => item.status === activeTab.value));
  166. /** zone_id / farm_id / crop_type 取当前选中农场,trigger_date 取日历选中日 */
  167. const resolveQueryParams = () => {
  168. const params = {
  169. zone_id: getSelectedZoneId(),
  170. farm_id: getSelectedFarmId(),
  171. trigger_date: filterDate.value || getTodayDate(),
  172. };
  173. const cropType = getSelectedCropType();
  174. if (cropType) params.crop_type = cropType;
  175. return params;
  176. };
  177. const isCompletedStatus = (status) => COMPLETED_STATUS_RE.test(String(status || ""));
  178. const formatExecuteDate = (dateStr) => {
  179. if (!dateStr) return "";
  180. return String(dateStr).replace(/^\d{4}-(\d{2})-(\d{2})$/, "$1/$2");
  181. };
  182. const buildPointWkt = (lng, lat, index = 0) => {
  183. if (lng == null || lat == null || Number.isNaN(Number(lng)) || Number.isNaN(Number(lat))) return "";
  184. const offset = (index % 6) * 0.00025;
  185. return `POINT(${Number(lng) + offset} ${Number(lat) + offset * 0.6})`;
  186. };
  187. /** 名称/说明可能是字符串,也可能是 { 人工方案, 机械方案 } / { 人工, 机械 } */
  188. const pickSchemeText = (value) => {
  189. if (value == null || value === "") return "";
  190. if (typeof value === "string" || typeof value === "number") return String(value);
  191. if (typeof value !== "object") return "";
  192. for (const key of SCHEME_TEXT_KEYS) {
  193. if (value[key]) return String(value[key]);
  194. }
  195. const first = Object.values(value).find((item) => item != null && item !== "");
  196. return first != null ? String(first) : "";
  197. };
  198. /** 单条任务映射为卡片;kind 区分标准 / 气象 / 异常 */
  199. const mapTaskItem = (task, index, { dateStr, lat, lng, zoneId, kind }) => {
  200. const executeDateFull = task.date || dateStr;
  201. const isWeather = kind === TASK_KIND.WEATHER;
  202. const isAbnormal = kind === TASK_KIND.ABNORMAL;
  203. const statusText = pickSchemeText(task.fw_status || task.status_text);
  204. const completed = isCompletedStatus(statusText);
  205. const prefix = isAbnormal ? "abnormal" : isWeather ? "weather" : "fw";
  206. const title = pickSchemeText(task.fw_name);
  207. const trigger = pickSchemeText(task.fw_trigger) || (isAbnormal ? task.abnormal_type || "" : "");
  208. return {
  209. id: `${prefix}_${task.manual_id || task.mechanical_id || pickSchemeText(task.fw_code) || index}_${executeDateFull}_${index}`,
  210. status: completed ? "completed" : "pending",
  211. title,
  212. isHighRisk: false,
  213. isNormal: kind === TASK_KIND.NORMAL,
  214. isAbnormal,
  215. isWeather,
  216. cornerTip: statusText || (completed ? "已完成" : "待执行"),
  217. analysisSummary:
  218. pickSchemeText(task.fw_explain_simple) ||
  219. pickSchemeText(task.fw_simple_explain) ||
  220. pickSchemeText(task.fw_explain),
  221. fw_trigger: trigger,
  222. areaCode: zoneId ? `分区 ${zoneId}` : "",
  223. farmName: title,
  224. executeDate: formatExecuteDate(executeDateFull),
  225. executeDateFull,
  226. zoneId,
  227. manualId: task.manual_id,
  228. mechanicalId: task.mechanical_id,
  229. latitude: lat,
  230. longitude: lng,
  231. wkt: buildPointWkt(lng, lat, index),
  232. // 详情页直接消费原始列表项,避免再请求 fw_task_details
  233. rawTask: task,
  234. };
  235. };
  236. /** 合并三类农事;无单条日期时归到本次请求日 */
  237. const mapZoneTaskList = (data, requestDate) => {
  238. const dateStr = requestDate || resolveRangeStart(data);
  239. const lat = data.latitude ?? 22.970229;
  240. const lng = data.longitude ?? 113.746646;
  241. const zoneId = data.zone_id ?? getSelectedZoneId();
  242. const ctx = { dateStr, lat, lng, zoneId };
  243. const mapList = (raw, kind, offset) =>
  244. (Array.isArray(raw) ? raw : [])
  245. .filter(Boolean)
  246. .map((task, index) => mapTaskItem(task, offset + index, { ...ctx, kind }));
  247. const normalList = mapList(data.fw_tasks, TASK_KIND.NORMAL, 0);
  248. const weatherList = mapList(data.weather_work, TASK_KIND.WEATHER, normalList.length);
  249. const abnormalList = mapList(
  250. data.abnormal_fw_tasks,
  251. TASK_KIND.ABNORMAL,
  252. normalList.length + weatherList.length
  253. );
  254. return { list: [...normalList, ...weatherList, ...abnormalList], lat, lng, dateStr, zoneId };
  255. };
  256. const resolveRangeStart = (data) => {
  257. if (data?.start_date) return data.start_date;
  258. if (data?.date) return data.date;
  259. return getTodayDate();
  260. };
  261. // ---------- 业务逻辑 ----------
  262. /** 只同步选中态和农事角标,不改日历起始日(窗口固定为今天起 7 天) */
  263. const syncCalendar = () => {
  264. nextTick(() => {
  265. calendarRef.value?.selectDateByValue?.(filterDate.value);
  266. calendarRef.value?.setSolarTerm?.(taskList.value);
  267. });
  268. };
  269. /** 无果园信息时展示「完善农场信息 / 专属农事日历」弹窗 */
  270. async function checkFarmCalendarPopup() {
  271. const id = getMiniUserId();
  272. if (!id) {
  273. showFarmCalendarPopup.value = false;
  274. return;
  275. }
  276. try {
  277. const res = await VE_API.questionnaire.getFarms({ id });
  278. const list = Array.isArray(res?.data) ? res.data : [];
  279. showFarmCalendarPopup.value = list.length === 0;
  280. } catch (e) {
  281. console.warn("[work_execute] getFarms failed", e);
  282. showFarmCalendarPopup.value = false;
  283. }
  284. }
  285. const refreshMapMarkers = () => {
  286. if (!indexMap.kmap) return;
  287. indexMap.initData(getMapMarkerList(), "farmName", "wkt");
  288. };
  289. /** 地图展示全部日期的待执行农事 */
  290. const getMapMarkerList = () =>
  291. taskList.value.filter((item) => item.status === "pending" && item.wkt);
  292. const fetchZoneFwTaskList = async () => {
  293. try {
  294. const params = resolveQueryParams();
  295. const res = await VE_API.questionnaire.getZoneFwTaskList(params);
  296. const data = res?.data;
  297. if (!data) {
  298. taskList.value = [];
  299. mapCenterPoint.value = null;
  300. calendarRef.value?.selectDateByValue?.(filterDate.value);
  301. calendarRef.value?.setSolarTerm?.([]);
  302. refreshMapMarkers();
  303. return;
  304. }
  305. const { list, lat, lng } = mapZoneTaskList(data, params.trigger_date);
  306. mapCenterPoint.value = `POINT(${lng} ${lat})`;
  307. taskList.value = list;
  308. syncCalendar();
  309. nextTick(() => refreshMapMarkers());
  310. } catch (e) {
  311. console.error("获取农事任务失败", e);
  312. taskList.value = [];
  313. }
  314. };
  315. // ---------- 事件处理 ----------
  316. const handleTabChange = (key) => {
  317. if (activeTab.value === key) return;
  318. activeTab.value = key;
  319. };
  320. const handleDateSelect = (date) => {
  321. const next = date ?? getTodayDate();
  322. if (filterDate.value === next) return;
  323. filterDate.value = next;
  324. fetchZoneFwTaskList();
  325. };
  326. const handleForward = (item) => {
  327. const workName = item?.title || item?.farmName || "农事详情";
  328. const query = {
  329. askInfo: { title: "转发农事", content: "是否分享给好友" },
  330. shareText: `${workName} 已触发,请在最佳农时内及时执行`,
  331. targetUrl: "work_detail",
  332. paramsPage: JSON.stringify({
  333. ...buildFwTaskQuery(item),
  334. fromShare: 1,
  335. }),
  336. imageUrl: "https://birdseye-img.sysuimars.com/birdseye-look-mini/work_img.png",
  337. };
  338. wx.miniProgram.navigateTo({
  339. url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
  340. });
  341. };
  342. const buildFwTaskQuery = (item) => ({
  343. zone_id: item?.zoneId ?? getSelectedZoneId(),
  344. date: item?.executeDateFull || getTodayDate(),
  345. manual_id: item?.manualId,
  346. mechanical_id: item?.mechanicalId,
  347. statusVal: item?.status,
  348. lat: item?.latitude,
  349. lng: item?.longitude,
  350. fw_name: item?.title || item?.farmName || "",
  351. isNormal: item?.isNormal ? 1 : 0,
  352. isAbnormal: item?.isAbnormal ? 1 : 0,
  353. isWeather: item?.isWeather ? 1 : 0,
  354. isHighRisk: item?.isHighRisk ? 1 : 0,
  355. fromZoneList: 1,
  356. });
  357. /** 写入详情页缓存:原始接口字段 + 列表侧元信息 */
  358. const saveFwTaskCache = (item) => {
  359. if (!item?.rawTask) return;
  360. sessionStorage.setItem(
  361. FW_TASK_CACHE_KEY,
  362. JSON.stringify({
  363. raw: item.rawTask,
  364. meta: {
  365. zone_id: item.zoneId ?? getSelectedZoneId(),
  366. date: item.executeDateFull || getTodayDate(),
  367. lat: item.latitude,
  368. lng: item.longitude,
  369. statusVal: item.status,
  370. statusText: item.cornerTip || "",
  371. fw_name: item.title || item.farmName || "",
  372. isNormal: item.isNormal ? 1 : 0,
  373. isAbnormal: item.isAbnormal ? 1 : 0,
  374. isWeather: item.isWeather ? 1 : 0,
  375. isHighRisk: item.isHighRisk ? 1 : 0,
  376. },
  377. })
  378. );
  379. };
  380. const handleViewDetail = (item) => {
  381. saveFwTaskCache(item);
  382. router.push({
  383. path: "/work_detail",
  384. query: buildFwTaskQuery(item),
  385. });
  386. };
  387. const goCompleteFarmInfo = () => {
  388. router.push("/entry_information");
  389. };
  390. const handleSurveyConfirm = () => {
  391. showSurveyAskPopup.value = false;
  392. router.push("/survey_entry");
  393. };
  394. /** 当前选中农场的 farm_location(POINT) */
  395. function resolveSelectedFarmLocationPoint() {
  396. const farm = getSelectedFarm();
  397. const candidates = [
  398. farm.farm_location,
  399. farm.wkt,
  400. farm.geom_wkt,
  401. localStorage.getItem("selectedFarmPoint"),
  402. ];
  403. for (const item of candidates) {
  404. if (typeof item === "string" && /^POINT\s*\(/i.test(item.trim())) {
  405. return item.trim();
  406. }
  407. }
  408. return "";
  409. }
  410. const initMap = () => {
  411. if (!mapContainer.value) return;
  412. // 中心点:接口分区中心 → 选中农场 farm_location → 登录定位 → 兜底
  413. const fallback = store.state.home.miniUserLocationPoint || "POINT(113.746646 22.970229)";
  414. const point = mapCenterPoint.value || resolveSelectedFarmLocationPoint() || fallback;
  415. if (!indexMap.kmap) {
  416. indexMap.initMap(point, mapContainer.value, true);
  417. }
  418. refreshMapMarkers();
  419. };
  420. onMounted(() => {
  421. checkFarmCalendarPopup();
  422. nextTick(async () => {
  423. await fetchZoneFwTaskList();
  424. initMap();
  425. });
  426. });
  427. onActivated(() => {
  428. checkFarmCalendarPopup();
  429. nextTick(() => {
  430. if (!indexMap.kmap) {
  431. initMap();
  432. return;
  433. }
  434. if (mapContainer.value && indexMap.kmap.map) {
  435. const checkAndUpdateSize = () => {
  436. const container = mapContainer.value;
  437. if (!container) return;
  438. const rect = container.getBoundingClientRect();
  439. if (rect.width > 0 && rect.height > 0) {
  440. indexMap.kmap.map.updateSize();
  441. refreshMapMarkers();
  442. } else {
  443. setTimeout(checkAndUpdateSize, 100);
  444. }
  445. };
  446. setTimeout(checkAndUpdateSize, 200);
  447. }
  448. });
  449. });
  450. onDeactivated(() => {
  451. showFarmCalendarPopup.value = false;
  452. });
  453. </script>
  454. <style lang="scss" scoped>
  455. .task-page {
  456. width: 100%;
  457. height: calc(100vh - 50px);
  458. overflow: auto;
  459. box-sizing: border-box;
  460. background: linear-gradient(180deg, #d1ebff 0%, #ffffff 100%);
  461. .map-container {
  462. width: 100%;
  463. height: 162px;
  464. clip-path: inset(0 round 8px);
  465. }
  466. .calendar-container {
  467. padding: 4px 12px 8px;
  468. }
  469. .task-top {
  470. padding: 10px 12px 0;
  471. }
  472. .task-list {
  473. padding: 0 12px 20px;
  474. }
  475. .task-tabs {
  476. display: flex;
  477. align-items: center;
  478. justify-content: center;
  479. gap: 50px;
  480. margin: 2px 0 26px 0;
  481. .tab-item {
  482. color: #1D2129;
  483. position: relative;
  484. &.active {
  485. color: #2199F8;
  486. font-weight: 500;
  487. &::after {
  488. content: '';
  489. position: absolute;
  490. bottom: -10px;
  491. left: 0;
  492. width: 100%;
  493. height: 2px;
  494. background: #2199F8;
  495. }
  496. }
  497. }
  498. }
  499. .work-task-list {
  500. display: flex;
  501. flex-direction: column;
  502. gap: 10px;
  503. }
  504. .task-card {
  505. position: relative;
  506. padding: 14px 12px 12px;
  507. border-radius: 8px;
  508. background: #fff;
  509. box-sizing: border-box;
  510. overflow: hidden;
  511. box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.04);
  512. &__corner {
  513. position: absolute;
  514. top: 0;
  515. right: 0;
  516. max-width: 78px;
  517. padding: 2px 8px;
  518. border-radius: 0 8px 0 8px;
  519. background: rgba(33, 153, 248, 0.1);
  520. color: #2199F8;
  521. // background: rgba(255, 106, 106, 0.1);
  522. // color: #FF6A6A;
  523. font-size: 12px;
  524. line-height: 16px;
  525. white-space: wrap;
  526. box-sizing: border-box;
  527. text-align: center;
  528. // overflow: hidden;
  529. // text-overflow: ellipsis;
  530. &.done-corner {
  531. max-width: 86px;
  532. height: 24px;
  533. background: rgba(118, 126, 116, 0.1);
  534. display: flex;
  535. align-items: center;
  536. gap: 4px;
  537. color: #959595;
  538. &.myself-corner {
  539. background: rgba(74, 191, 50, 0.1);
  540. color: #4ABF32;
  541. }
  542. .myself-corner__icon {
  543. color: #959595;
  544. flex-shrink: 0;
  545. }
  546. .myself-corner__icon-success {
  547. color: #4ABF32;
  548. }
  549. }
  550. }
  551. &__header {
  552. display: flex;
  553. align-items: center;
  554. gap: 8px;
  555. padding-right: 8px;
  556. min-width: 0;
  557. }
  558. &__icon {
  559. width: 16px;
  560. height: 16px;
  561. flex-shrink: 0;
  562. }
  563. &__title {
  564. max-width: 160px;
  565. font-size: 16px;
  566. font-weight: 500;
  567. color: #000;
  568. }
  569. .badge-tag {
  570. flex-shrink: 0;
  571. height: 18px;
  572. padding: 0 6px;
  573. border-radius: 2px;
  574. font-size: 12px;
  575. }
  576. .badge-tag + .badge-tag {
  577. margin-left: 4px;
  578. }
  579. .risk-badge {
  580. background: #FF6A6A;
  581. color: #FFFFFF;
  582. }
  583. .normal-badge {
  584. background: rgba(33, 153, 248, 0.1);
  585. color: #2199F8;
  586. }
  587. .abnormal-badge {
  588. background: rgba(255, 106, 106, 0.1);
  589. color: #FF6A6A;
  590. }
  591. .weather-badge {
  592. background: rgba(255, 149, 61, 0.1);
  593. color: #FDA202;
  594. }
  595. &__body {
  596. margin-top: 10px;
  597. padding-top: 10px;
  598. border-top: 0.5px solid rgba(0, 0, 0, 0.05);
  599. }
  600. .info-line {
  601. font-size: 14px;
  602. line-height: 20px;
  603. color: rgba(111, 114, 116, 0.6);
  604. & + .info-line {
  605. margin-top: 6px;
  606. }
  607. }
  608. &__footer {
  609. display: flex;
  610. align-items: center;
  611. justify-content: space-between;
  612. gap: 12px;
  613. margin-top: 14px;
  614. .footer-l {
  615. background: rgba(33, 153, 248, 0.1);
  616. height: 20px;
  617. display: flex;
  618. align-items: center;
  619. gap: 6px;
  620. padding: 0 6px;
  621. border-radius: 2px;
  622. font-size: 12px;
  623. color: #2199F8;
  624. .footer-l__icon {
  625. width: 14px;
  626. height: 14px;
  627. flex-shrink: 0;
  628. }
  629. }
  630. .footer-r {
  631. display: flex;
  632. align-items: center;
  633. gap: 10px;
  634. }
  635. }
  636. .card-btn {
  637. height: 32px;
  638. border-radius: 20px;
  639. text-align: center;
  640. line-height: 32px;
  641. font-size: 12px;
  642. &--ghost {
  643. background: #F6F6F6;
  644. color: #000000;
  645. padding: 0 20px;
  646. }
  647. &--primary {
  648. background: #2199F8;
  649. color: #FFFFFF;
  650. padding: 0 16px;
  651. }
  652. }
  653. }
  654. .empty-tip {
  655. padding: 40px 0;
  656. text-align: center;
  657. color: #86909c;
  658. font-size: 14px;
  659. }
  660. }
  661. </style>