riku
2025-04-22 45be153eaef9e1c1a3fe21515e9cbd785fba8e1f
线索任务

1. 添加线索任务功能(待完成)
已修改8个文件
已添加18个文件
572 ■■■■■ 文件已修改
app.json 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/clue/dataResponseLevel.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/clue/dataTravelMode.js 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/form/form-util.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/index.js 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
custom-tab-bar/data.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
custom-tab-bar/index.js 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
model/clue/clueQuestion.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
model/clue/clueTask.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/index.js 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/index.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/index.wxml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/index.wxss 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/options-proxy.js 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/options.wxml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/options.wxss 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/statistic-proxy.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/statistic.wxml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/statistic.wxss 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/tasks-proxy.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/tasks.wxml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/tasks.wxss 补丁 | 查看 | 原始文档 | blame | 历史
pages/inspection/scene/info/device-info-items.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/selfpatrol/components/patrol-record/index.wxml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
services/baseRequset.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
services/clue/fetchClue.js 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app.json
@@ -36,7 +36,8 @@
    "pages/inspection/scene/info/device-info/index",
    "pages/inspection/scene/info/device-status/index",
    "pages/simple-home/index",
    "pages/inspection/ranking/search/index"
    "pages/inspection/ranking/search/index",
    "pages/cluetask/home/index"
  ],
  "tabBar": {
    "custom": true,
@@ -54,6 +55,10 @@
        "text": "精细化监管"
      },
      {
        "pagePath": "pages/cluetask/home/index",
        "text": "应急线索"
      },
      {
        "pagePath": "pages/selfpatrol/index",
        "text": "应急自巡查"
      },
common/clue/dataResponseLevel.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
// åº”急线索任务响应级别
const ResponseLevels = [
  { value: null, label: '全部' },
  { value: 0, label: '当日' },
  { value: 1, label: '三天内' },
  { value: 2, label: '一周内' },
  { value: 3, label: '当月' },
];
export default {
  ResponseLevels,
  toLabel(value) {
    let r = ResponseLevels.find(item => {
      return item.value == value;
    });
    if (!r) r = ResponseLevels[0];
    return r.label;
  },
  toLabel2(value) {
    let l = this.toLabel(value);
    if (l == ResponseLevels[0].label) {
      l = '/';
    }
    return l;
  },
  toValue(label) {
    const r = ResponseLevels.find(item => {
      return item.label == label;
    });
    return r.value;
  },
};
common/clue/dataTravelMode.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
// åº”急线索任务响应级别
const TravelModes = [
  { value: null, label: '全部' },
  { value: 0, label: '驾车' },
  { value: 1, label: '公共交通' },
  { value: 2, label: '步行' },
];
export default {
  TravelModes,
  toLabel(value) {
    let r = TravelModes.find(item => {
      return item.value == value;
    });
    if (!r) r = TravelModes[0];
    return r.label;
  },
  toLabel2(value) {
    let l = this.toLabel(value);
    if (l == TravelModes[0].label) {
      l = '/';
    }
    return l;
  },
  toValue(label) {
    const r = TravelModes.find(item => {
      return item.label == label;
    });
    return r.value;
  },
};
components/form/form-util.js
@@ -2,8 +2,8 @@
 * ç”Ÿæˆä¸€æ¡è¡¨å•条目
 * @param {String} _label æ ‡ç­¾åç§°
 * @param {String} _name å­—段名称
 * @param {String} _type è¾“入类型 ï¼ˆtext: è¾“入框; switch: åˆ‡æ¢æŒ‰é’®; picker: ä¸‹æ‹‰æ¡†é€‰é¡¹; cascader: çº§è”选择)
 * @param {Boolean} _required æ˜¯å¦ä¸ºå¿…填项
 * @param {String} _type è¾“入类型 ï¼ˆtext: è¾“入框; switch: åˆ‡æ¢æŒ‰é’®; picker: ä¸‹æ‹‰æ¡†é€‰é¡¹; cascader: çº§è”选择)
 * @param {Array} _options å½“输入类型为picker或cascader时,提供可选项
 * @param {Array} cascaderTitles å½“输入类型为cascader时,提供每层选项的标题
 * @param {Array} referItems å½“输入类型为cascader时,提供关联的属性name
config/index.js
@@ -17,7 +17,24 @@
const inspectPicUrl = `${iu}/images/`;
// const inspectPicUrl = `${inspectUrl}/images/`;
// const mode = 'debug';
const mode = 'prod';
// é“路应急线索
// const clueUrl = 'https://fyami.com.cn:448/';
const clueUrl = 'http://192.168.0.110:8084/';
const cu = 'https://fyami.com.cn:448';
const cluePicUrl = `${cu}/images/`;
export { basePicUrl, baseUrl, baseFileUrl, baseIconUrl, inspectUrl, inspectPicUrl, mode };
// è¿è¡Œæ¨¡å¼
const mode = 'debug';
// const mode = 'prod';
export {
  basePicUrl,
  baseUrl,
  baseFileUrl,
  baseIconUrl,
  inspectUrl,
  inspectPicUrl,
  clueUrl,
  cluePicUrl,
  mode,
};
custom-tab-bar/data.js
@@ -12,6 +12,12 @@
    level: 2,
  },
  {
    icon: 'fact-check',
    text: '应急线索',
    url: 'pages/cluetask/home/index',
    level: 1,
  },
  {
    // icon: `${baseIconUrl}tab-slef-patrol.png`,
    icon: 'root-list',
    text: '应急自巡查',
custom-tab-bar/index.js
@@ -7,10 +7,16 @@
    list: TabMenu,
  },
  attached() {
    const menu = TabMenu.map(v => {
      v.visible = app.globalData.userInfo.usertypeid <= v.level;
      return v;
    const menu = []
    TabMenu.forEach(v => {
      if (app.globalData.userInfo.usertypeid <= v.level) {
        menu.push(v)
      }
    });
    // TabMenu.map(v => {
    //   v.visible = app.globalData.userInfo.usertypeid <= v.level;
    //   return v;
    // });
    this.setData({ list: menu });
  },
  methods: {
model/clue/clueQuestion.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
import { cluePicUrl } from '../../config/index';
function getClueQuestion(data) {
  data.cqFilePath = data.cqFilePath.split(';').map((val) => {
    return cluePicUrl + val;
  });
  return data;
}
function getClueQuestionList(dataList) {
  return dataList.map((v) => {
    return getClueQuestion(v);
  });
}
export { getClueQuestion, getClueQuestionList };
model/clue/clueTask.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
/**
 * çº¿ç´¢ç»Ÿè®¡
 */
export function getClueStatistic(dataList) {
  let total = dataList.length,
    finished = 0,
    unfinished = 0;
  dataList.forEach(d => {
    d.finished ? finished++ : unfinished++;
  });
  const p1 = Math.round((finished / total) * 1000) / 10;
  const p2 = Math.round((unfinished / total) * 1000) / 10;
  return [
    {
      name: '总计',
      value: total,
      diff: '',
    },
    {
      name: '已完成',
      value: finished,
      diff: total == 0 ? '0%' : `${p1}%`,
    },
    {
      name: '待完成',
      value: unfinished,
      diff: total == 0 ? '0%' : `${p2}%`,
    },
  ];
}
pages/cluetask/home/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
import { useLoading } from '../../../behaviors/loading';
import { useOptions } from './options-proxy.js';
import { useStatistic } from './statistic-proxy.js';
import { useTasks } from './tasks-proxy.js';
import clueApi from '../../../services/clue/fetchClue';
const app = getApp();
Page({
  behaviors: [useLoading, useOptions, useStatistic, useTasks],
  data: {
    userInfo: app.globalData.userInfo,
    clueTaskList: [],
  },
  onLoad(options) {},
  onShow() {
    this.getTabBar().init();
  },
  onPullDownRefresh() {
    this._startLoad();
  },
  onReachBottom() {
    this._loadMore();
  },
  /**
   * åˆå§‹åŠ è½½
   * å½“所有筛选条件都获取到初始值后,执行一次初始化加载
   * åŒ…括区域、时间两个选项,全部获取初始值后,执行加载
   * @see options-proxy.js
   */
  optionsCount: 0,
  init() {
    this.optionsCount++;
    if (this.optionsCount == 2) this._startLoad();
  },
  _fetchData(page) {
    return clueApi.fetchClueTask({}).then(res => {
      this.setData({ clueTaskList: res.data });
      this.calClueCount();
      this.formatClueTask();
    });
  },
});
pages/cluetask/home/index.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
{
  "navigationBarTitleText": "应急线索",
  "onReachBottomDistance": 10,
  "backgroundTextStyle": "light",
  "enablePullDownRefresh": true,
  "navigationBarTextStyle": "white",
  "navigationBarBackgroundColor": "#389AFF",
  "usingComponents": {
    "location-picker": "/components/picker/location-picker/index",
    "t-time-picker": "/components/time-picker/index",
    "stat-card": "/components/stat-card/index"
  }
}
pages/cluetask/home/index.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
<import src="/pages/common/template/template-loading.wxml" />
<view class="page">
  <template is="pulldown-loading" wx:if="{{pageLoading}}" />
  <view class="page-header">
  </view>
  <view class="page-container">
    <include src="./options.wxml" />
    <include src="./statistic.wxml" />
    <include src="./tasks.wxml" />
    <t-toast id="t-toast" />
  </view>
  <view class="page-footer"></view>
</view>
pages/cluetask/home/index.wxss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
@import './options.wxss';
@import './statistic.wxss';
@import './tasks.wxss';
page {
  --header-bottom-padding: 600rpx;
}
.page .page-header {
  background: linear-gradient(var(--td-primary-color-7), var(--td-bg-color));
  padding-bottom: var(--header-bottom-padding);
}
.page .page-container {
  margin-top: calc(0rpx - var(--header-bottom-padding));
}
pages/cluetask/home/options-proxy.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,64 @@
import dayjs from 'dayjs';
/**
 * ç­›é€‰æ¡ä»¶ç›¸å…³ä¿¡æ¯èŽ·å–é€»è¾‘
 * åŒ…括时间、行政区划
 */
export const useOptions = Behavior({
  data: {
    time:'',
  },
  methods: {
    setLocation(e) {
      const {
        provinceText: provinceName,
        cityText: cityName,
        districtText: districtName,
        townText: townName,
        provinceValue: provinceCode,
        cityValue: cityCode,
        districtValue: districtCode,
        townValue: townCode,
        locationValue,
      } = e.detail;
      this.setData({
        provinceName,
        cityName,
        districtName,
        townName,
        provinceCode,
        cityCode,
        districtCode,
        townCode,
        locationValue,
      });
    },
    initLocation(e) {
      this.setLocation(e);
      this.init();
    },
    // è¡Œæ”¿åŒºåˆ’切换时,获取新的线上监管信息以及线下巡查监管信息
    onLocationChange(e) {
      this.setLocation(e);
      this.fetchSupervision();
      this.fetchInspection();
    },
    // æ—¶é—´æ›´æ”¹
    setTimeValue(e) {
      const { timeValue } = e.detail;
      const p = dayjs(timeValue).format('YYYY-MM-DD HH:mm:ss');
      this.setData({
        time: p,
      });
    },
    initTime(e) {
      this.setTimeValue(e);
      this.init();
    },
    onTimePickerConfirm(e) {
      this.setTimeValue(e);
      this._startLoad();
    },
  },
});
pages/cluetask/home/options.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
<view class="home-supervision-title">
  <!-- <view> -->
  <!-- <text>线上监管</text> -->
  <!-- <text class="home-supervision-title__note">本月最新</text> -->
  <!-- </view> -->
  <!-- <view style="display: flex;align-items: center;"> -->
  <t-time-picker
    color="var(--td-font-white-1)"
    picker-class="picker-location"
    bind:timeInitValue="initTime"
    bind:timePickerChange="onTimePickerConfirm"
  >
  </t-time-picker>
  <location-picker
    color="var(--td-font-white-1)"
    style-mode="picker"
    picker-class="picker-location"
    bind:onChange="onLocationChange"
    bind:locationInitValue="initLocation"
  ></location-picker>
  <!-- </view> -->
</view>
pages/cluetask/home/options.wxss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
.home-supervision-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--td-font-size-m);
  margin-bottom: var(--td-spacer);
  font-weight: 550;
  color: var(--td-text-color-anti);
}
.picker-location {
  color: var(--td-font-white-1) !important;
  justify-content: flex-end !important;
  width: initial !important;
  max-width: 50vw;
  /* background-color: lawngreen; */
}
pages/cluetask/home/statistic-proxy.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
import { getClueStatistic } from '../../../model/clue/clueTask';
/**
 * çº¿ç´¢ä»»åŠ¡ç»Ÿè®¡ç›¸å…³ä¿¡æ¯èŽ·å–é€»è¾‘
 */
export const useStatistic = Behavior({
  data: {
    clueCountRes: [],
  },
  methods: {
    /**
     * è®¡ç®—线索巡查完成情况
     */
    calClueCount() {
      this.setData({
        clueCountRes: getClueStatistic(this.data.clueTaskList),
      });
    },
  },
});
pages/cluetask/home/statistic.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
<view>
</view>
<stat-card
  title="应急巡查"
  subTitle=""
  stats="{{clueCountRes}}"
  loading="{{pageLoading}}"
  bind:cardClick="navToEnterprise"
></stat-card>
pages/cluetask/home/statistic.wxss
pages/cluetask/home/tasks-proxy.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
import dayjs from 'dayjs';
import dataResponseLevel from '../../../common/clue/dataResponseLevel';
import dataTravelMode from '../../../common/clue/dataTravelMode';
/**
 * çº¿ç´¢ä»»åŠ¡åˆ—è¡¨å±•ç¤ºç›¸å…³é€»è¾‘
 */
export const useTasks = Behavior({
  data: {},
  methods: {
    /**
     * æ ¼å¼åŒ–
     */
    formatClueTask() {
      const { clueTaskList } = this.data;
      clueTaskList.forEach(t => {
        t._taskTime = dayjs(t.taskTime).format('YYYY-MM-DD')
        let note = '';
        note += t.provinceName ? t.provinceName : '';
        note += t.provinceName == t.cityName ? '' : t.cityName ? `/${t.cityName}` : '';
        note += t.districtName ? `/${t.districtName}` : '';
        note += t.townName ? `/${t.townName}` : '';
        t._location = note;
        t._resLevelTxt = dataResponseLevel.toLabel2(t.responseLevel);
        t._travelModeTxt = dataTravelMode.toLabel2(t.travelMode);
        t._hasUavTxt = t.hasUav ? '有' : '无';
      });
      this.setData({ clueTaskList });
    },
  },
});
pages/cluetask/home/tasks.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
<view>
  <view wx:for="{{clueTaskList}}" wx:key="index" wx:for-index="index">
  <t-cell title="任务时间" note="{{item._taskTime}}" />
  <t-cell title="任务位置" note="{{item._location}}"/>
  <t-cell title="响应时间" note="{{item._resLevelTxt}}"/>
  <t-cell title="出行方式" note="{{item._travelModeTxt}}"/>
  <t-cell title="无人机" note="{{item._hasUavTxt}}"/>
  </view>
</view>
pages/cluetask/home/tasks.wxss
pages/inspection/scene/info/device-info-items.js
@@ -159,7 +159,7 @@
    hideInputItem('更新时间', 'dlUpdateTime'),
    hideInputItem('设备位置', 'dlLocation', true),
    hideInputItem('经度', 'dlLongitude'),
    hideInputItem('维度', 'dlLatitude', true),
    hideInputItem('纬度', 'dlLatitude', true),
    baseInputItem('是否规范', 'dlStandard', true, 'switch'),
    baseInputItem('不规范原因', 'dlUnStandardReason'),
pages/selfpatrol/components/patrol-record/index.wxml
@@ -1,26 +1,22 @@
<view
  class="patrol-record-wrap {{item.ledgerFinished ? 'patrol-finished' : 'patrol-unfinished'}}"
>
<view class="patrol-record-wrap {{item.ledgerFinished ? 'patrol-finished' : 'patrol-unfinished'}}">
  <view class="patrol-record-text-wrap">
    <view> {{indexText}} </view>
    <view style="display: flex;flex-direction: column;justify-content: space-between;">
    <view style="display: flex; flex-direction: column; justify-content: space-between">
      <view class="patrol-record-text">{{item.ledgerName}}</view>
      <view class="patrol-record-tag"
        >{{item.updateDate ? item.updateDate : '未提交'}}</view
      >
      <view class="patrol-record-tag">{{item.updateDate ? item.updateDate : '未提交'}}</view>
    </view>
  </view>
  <view class="patrol-record-img-wrap">
    <t-image
      wx:if="{{item.path1 == null}}"
      t-class="t-class-image"
      src="{{null}}"
      mode="aspectFill"
      src="{{'/res/nodata.png'}}"
      mode="aspectFit"
      width="80rpx"
      height="80rpx"
      shape="round"
    >
      <text  slot="error">未提交</text>
      <text slot="error">未提交</text>
    </t-image>
    <block wx:else>
      <t-image
services/baseRequset.js
@@ -68,3 +68,8 @@
  fun['method'] = 'PUT';
  return request(fun, hostUrl);
}
export function _delete(fun, hostUrl) {
  fun['method'] = 'DELETE';
  return request(fun, hostUrl);
}
services/clue/fetchClue.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
/**
 * é“路应急线索巡查相关数据接口
 */
import Multipart from '../../utils/Multipart.min';
import { get, post, _delete } from '../baseRequset';
import { clueUrl, cluePicUrl } from '../../config/index';
import { getClueQuestionList } from '../../model/clue/clueQuestion';
export default {
  /******************************************************************************* */
  /**
   * æŸ¥è¯¢çº¿ç´¢ä»»åŠ¡
   * @param {*} clueTask
   * @returns
   */
  fetchClueTask(clueTask) {
    return post(
      {
        url: `clue/task/fetch`,
        data: clueTask,
      },
      clueUrl,
    ).then(res => {
      return res.data;
    });
  },
  /******************************************************************************* */
  /**
   * èŽ·å–çº¿ç´¢ç»“è®º
   * @param {string} clueId çº¿ç´¢id
   */
  getConclusion(clueId) {
    return get({
      url: `clue/conclusion/fetch`,
      params: {
        clueId: clueId,
      },
      clueUrl,
    }).then(res => res.data);
  },
  /**
   * æäº¤çº¿ç´¢ç»“论
   * @param {object} conclusion çº¿ç´¢
   * @returns
   */
  uploadConclusion(conclusion) {
    return post(
      {
        url: `clue/conclusion/upload`,
        data: conclusion,
      },
      clueUrl,
    ).then(res => res.data);
  },
  /******************************************************************************* */
  /**
   * èŽ·å–å·²æäº¤çš„çº¿ç´¢é—®é¢˜
   * @param {string} clueId çº¿ç´¢id
   */
  getQuestion(clueId) {
    return get({
      url: `clue/question/fetch`,
      params: {
        clueId: clueId,
      },
      clueUrl,
    }).then(res => {
      return getClueQuestionList(res.data);
    });
  },
  /**
   * ä¸Šä¼ çº¿ç´¢é—®é¢˜
   * @param {object} question é—®é¢˜æè¿°
   * @param {*} files é—®é¢˜å›¾ç‰‡
   * @returns
   */
  uploadQuestion(question, files) {
    const fields = [
      {
        name: 'question',
        value: JSON.stringify(question),
      },
    ];
    const images = files.map(f => {
      return {
        name: 'images',
        filePath: f,
      };
    });
    return new Multipart({
      fields,
      images,
    })
      .submit(clueUrl + `/clue/question/upload`)
      .then(res => {
        return res.data;
      });
  },
  deleteQuestion(questionId) {
    return _delete({
      url: `clue/question`,
      params: {
        questionId: questionId,
      },
      clueUrl,
    }).then(res => res.data);
  },
};