riku
2025-04-25 b515fae43490ab20977d559e19d4e5f63a4fd96d
应急线索模块

完成应急线索模块功能
已修改22个文件
已添加27个文件
1518 ■■■■ 文件已修改
app.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/clue/dataConclusionType.js 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
common/dataTowns.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list-item/index.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list-item/index.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list-item/index.wxml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list-item/index.wxss 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list/index.js 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list/index.json 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list/index.wxml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/descriptions-list/index.wxss 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/empty-page/index.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/empty-page/index.wxml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/form/form-util.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/form/index.js 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/form/index.wxml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/form/index.wxss 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/index.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
custom-tab-bar/data.js 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
custom-tab-bar/index.js 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
model/clue/clue.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
model/clue/clueQuestion.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
model/clue/clueTask.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/clue-item.js 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/conclusion/index.js 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/conclusion/index.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/conclusion/index.wxml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/conclusion/index.wxss 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/index.js 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/tasks-proxy.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/tasks.wxml 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/home/tasks.wxss 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/conclusion-proxy.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/conclusion.wxml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/index.js 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/index.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/index.wxml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/index.wxss 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/question-proxy.js 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/manage/question.wxml 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/question/index.js 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/question/index.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/question/index.wxml 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/cluetask/question/index.wxss 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/usercenter/index.js 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/usercenter/index.wxml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/usercenter/menu.js 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
project.private.config.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
services/clue/fetchClue.js 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app.json
@@ -3,6 +3,7 @@
    "pages/usercenter/login/login-home/index",
    "pages/home/index",
    "pages/supervision/index",
    "pages/cluetask/home/index",
    "pages/assist/index",
    "pages/usercenter/index",
    "pages/enterprise/search/index",
@@ -37,7 +38,9 @@
    "pages/inspection/scene/info/device-status/index",
    "pages/simple-home/index",
    "pages/inspection/ranking/search/index",
    "pages/cluetask/home/index"
    "pages/cluetask/manage/index",
    "pages/cluetask/question/index",
    "pages/cluetask/conclusion/index"
  ],
  "tabBar": {
    "custom": true,
@@ -53,10 +56,6 @@
      {
        "pagePath": "pages/supervision/index",
        "text": "精细化监管"
      },
      {
        "pagePath": "pages/cluetask/home/index",
        "text": "应急线索"
      },
      {
        "pagePath": "pages/selfpatrol/index",
common/clue/dataConclusionType.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
// åº”急线索任务响应级别
const ConclusionType = [
  { value: '有问题', label: '有问题' },
  { value: '无问题', label: '无问题' },
  { value: '已解决', label: '已解决' },
];
export default {
  ConclusionType,
  toLabel(value) {
    let r = ConclusionType.find(item => {
      return item.value == value;
    });
    if (!r) r = ConclusionType[0];
    return r.label;
  },
  toLabel2(value) {
    let l = this.toLabel(value);
    if (l == ConclusionType[0].label) {
      l = '/';
    }
    return l;
  },
  toValue(label) {
    const r = ConclusionType.find(item => {
      return item.label == label;
    });
    return r.value;
  },
};
common/dataTowns.js
@@ -119,8 +119,8 @@
}
function toLabel(value) {
  for (const iterator of Towns) {
    let r = iterator.value.find(item => {
  for (const key in Towns) {
    let r = Towns[key].find(item => {
      return item.value == value;
    });
    if (r) {
@@ -130,8 +130,8 @@
}
function toValue(label) {
  for (const iterator of Towns) {
    let r = iterator.value.find(item => {
  for (const key in Towns) {
    let r = Towns[key].find(item => {
      return item.label == label;
    });
    if (r) {
components/descriptions-list-item/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
// components/descriptions-list-item/index.js
Component({
  options: {
    addGlobalClass: true,
    multipleSlots: true,
  },
  /**
   * ç»„件的属性列表
   */
  properties: {
    label: {
      type: String,
      value: undefined,
    },
    content: {
      type: String,
      value: undefined,
    },
  },
  /**
   * ç»„件的初始数据
   */
  data: {},
  /**
   * ç»„件的方法列表
   */
  methods: {},
});
components/descriptions-list-item/index.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
{
  "component": true,
  "usingComponents": {}
}
components/descriptions-list-item/index.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
<view class="tr">
  <view wx:if="{{label}}" class="td td-1">{{ label }}</view>
  <view wx:else class="td td-1"><slot name="label"></slot></view>
  <view wx:if="{{content}}" class="td td-2">{{ content }}</view>
  <view wx:else class="td td-2"><slot name="content"></slot></view>
</view>
components/descriptions-list-item/index.wxss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
.tr {
  display: flex;
  font-size: var(--td-font-size-s);
  margin-top: -1px;
}
.td {
  border: 1px solid var(--td-gray-color-3);
  padding: 2px 6px;
}
.td-1 {
  width: 68px;
  background-color: var(--td-primary-color-1);
  color: var(--td-text-color-secondary);
}
.td-2 {
  display: inline-block;
  width: 100%;
  color: var(--td-text-color-primary);
  margin-left: -1px;
}
components/descriptions-list/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
// components/descriptions-list/index.js
Component({
  options: {
    addGlobalClass: true,
    multipleSlots: true,
  },
  /**
   * ç»„件的属性列表
   */
  properties: {
    title: {
      type: String,
      value: undefined,
    },
  },
  /**
   * ç»„件的初始数据
   */
  data: {},
  /**
   * ç»„件的方法列表
   */
  methods: {},
});
components/descriptions-list/index.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
{
  "component": true,
  "usingComponents": {}
}
components/descriptions-list/index.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
<view class="wrapper">
  <view class="title-wrapper">
    <view v-if="{{title}}" class="descriptions-list__title">{{ title }}</view>
    <slot name="extra"></slot>
  </view>
  <view>
    <slot></slot>
  </view>
</view>
components/descriptions-list/index.wxss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
.wrapper {
  background-color: var(--td-gray-color-1);
  padding: 4px;
  border-radius: var(--td-border-radius);
  box-shadow: var(--td-shadow-4);
  margin-bottom: 8px;
}
.title-wrapper {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.descriptions-list__title {
  padding: 8px 8px;
  font-size: var(--td-font-size-m);
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: 600;
}
components/empty-page/index.js
@@ -2,6 +2,7 @@
Component({
  options: {
    addGlobalClass: true,
    multipleSlots: true,
  },
  /**
   * ç»„件的属性列表
components/empty-page/index.wxml
@@ -1,6 +1,9 @@
<!--components/empty-page/index.wxml-->
<t-empty description="{{description}}" >
<t-empty description="{{description}}">
  <view slot="image">
    <t-image src="/res/nodata.png" mode="aspectFill" width="180rpx" height="180rpx"/>
    <t-image src="/res/nodata.png" mode="aspectFill" width="180rpx" height="180rpx" />
  </view>
  <view slot="action">
    <slot name="action" />
  </view>
</t-empty>
components/form/form-util.js
@@ -3,7 +3,7 @@
 * @param {String} _label æ ‡ç­¾åç§°
 * @param {String} _name å­—段名称
 * @param {Boolean} _required æ˜¯å¦ä¸ºå¿…填项
 * @param {String} _type è¾“入类型 ï¼ˆtext: è¾“入框; switch: åˆ‡æ¢æŒ‰é’®; picker: ä¸‹æ‹‰æ¡†é€‰é¡¹; cascader: çº§è”选择)
 * @param {String} _type è¾“入类型 ï¼ˆtext: è¾“入框; textarea: å¤šè¡Œè¾“入框; switch: åˆ‡æ¢æŒ‰é’®; picker: ä¸‹æ‹‰æ¡†é€‰é¡¹; cascader: çº§è”选择)
 * @param {Array} _options å½“输入类型为picker或cascader时,提供可选项
 * @param {Array} cascaderTitles å½“输入类型为cascader时,提供每层选项的标题
 * @param {Array} referItems å½“输入类型为cascader时,提供关联的属性name
@@ -20,7 +20,7 @@
  return {
    required: _required,
    label: _label,
    placeholder: (_type == 'text' ? '请输入' : '请选择') + _label,
    placeholder: (_type == 'text' || _type == 'textarea' ? '请输入' : '请选择') + _label,
    name: _name,
    value: null,
    status: 'success',
@@ -50,10 +50,10 @@
 * @param {Object} defaultValue åˆå§‹æ•°æ®
 */
function setDefaultValue(items, defaultValue) {
  if (typeof defaultValue === 'object') {
  if (defaultValue && typeof defaultValue === 'object') {
    items.forEach(e => {
      // æ–‡æœ¬å’Œå¼€å…³ç±»åž‹ç›´æŽ¥èµ‹å€¼
      if (e.inputType == 'text' || e.inputType == 'switch') {
      if (e.inputType == 'text' || e.inputType == 'switch' || e.inputType == 'textarea') {
        if (defaultValue.hasOwnProperty(e.name)) {
          e.value = defaultValue[e.name];
        }
components/form/index.js
@@ -26,13 +26,25 @@
        this.setData({ formArray: v });
      },
    },
    // ç¡®è®¤æ–‡æœ¬
    submitText: {
      type: String,
      value: '保存',
    },
    // å–消文本
    cancelText: {
      type: String,
      value: '取消',
    },
    // æ˜¯å¦æ‰§è¡Œæ ¡éªŒ
    validated: {
      type: Boolean,
      value: false,
    },
    // æ˜¯å¦å¯ç¼–辑
    editable: {
      type: Boolean,
      value: true,
    },
  },
@@ -101,7 +113,13 @@
    // ä¿å­˜
    onSubmit() {
      const formObj = {};
      let fail = false;
      this.data.formArray.forEach(e => {
        if (e.required && !e.value) {
          fail = true;
          this.setData({ validated: true });
          return;
        }
        if (e.inputType == 'picker') {
          formObj[e.name] = e.value;
        } else if (e.inputType == 'cascader') {
@@ -112,7 +130,9 @@
          formObj[e.name] = e.value;
        }
      });
      this.triggerEvent('submit', formObj);
      if (!fail) {
        this.triggerEvent('submit', formObj);
      }
    },
    // å–消
    onCancel() {
components/form/index.wxml
@@ -9,6 +9,19 @@
        bind:change="onInputChange"
      />
    </block>
    <block wx:elif="{{item.inputType == 'textarea'}}">
      <t-textarea
        label="{{item.label}}"
        model:value="{{item.value}}"
        bordered
        autofocus
        autosize
        confirm-type="next"
        data-index="{{index}}"
        placeholder="{{item.placeholder}}"
        bind:change="onInputChange"
      ></t-textarea>
    </block>
    <block wx:elif="{{item.inputType == 'switch'}}">
      <t-cell title="{{item.label}}">
        <t-switch
@@ -61,10 +74,13 @@
        bind:change="onCascaderChange"
      />
    </block>
    <block wx:if="{{validated && item.required && !item.value}}">
      <view class="tips">{{item.tips}}</view>
    </block>
  </block>
</block>
<slot></slot>
<view class="btn-group">
<view wx:if="editable" class="btn-group">
  <t-button
    block
    theme="light"
components/form/index.wxss
@@ -3,4 +3,10 @@
  justify-content: space-between;
  padding: 8px;
  gap: 8px;
}
.tips {
  margin-left: 16px;
  color: var(--td-error-color);
  font-size: var(--td-font-size-s);
}
config/index.js
@@ -18,14 +18,15 @@
// const inspectPicUrl = `${inspectUrl}/images/`;
// é“路应急线索
// const clueUrl = 'https://fyami.com.cn:448/';
const clueUrl = 'http://192.168.0.110:8084/';
const clueUrl = 'https://fyami.com.cn:448/';
// const clueUrl = 'http://192.168.0.110:8080/';
const cu = 'https://fyami.com.cn:448';
// const cu = 'http://192.168.0.110:8080';
const cluePicUrl = `${cu}/images/`;
// è¿è¡Œæ¨¡å¼
const mode = 'debug';
// const mode = 'prod';
// const mode = 'debug';
const mode = 'prod';
export {
  basePicUrl,
custom-tab-bar/data.js
@@ -11,14 +11,13 @@
    url: 'pages/supervision/index',
    level: 2,
  },
  // {
  //   icon: 'fact-check',
  //   text: '应急线索',
  //   url: 'pages/cluetask/home/index',
  //   level: 1,
  // },
  {
    icon: 'fact-check',
    text: '应急线索',
    url: 'pages/cluetask/home/index',
    level: 1,
  },
  {
    // icon: `${baseIconUrl}tab-slef-patrol.png`,
    icon: 'root-list',
    text: '应急自巡查',
    url: 'pages/selfpatrol/index',
@@ -37,3 +36,54 @@
    level: 2,
  },
];
// const menu = [
//   {
//     icon: 'home',
//     text: '首页',
//     url: 'pages/home/index',
//     level: 2,
//   },
//   {
//     icon: 'app',
//     text: '精细化监管',
//     url: 'pages/supervision/index',
//     level: 2,
//   },
//   {
//     icon: 'fact-check',
//     text: '应急线索',
//     url: 'pages/cluetask/home/index',
//     level: 1,
//   },
//   {
//     icon: 'root-list',
//     text: '应急自巡查',
//     url: 'pages/selfpatrol/index',
//     level: 2,
//   },
//   // {
//   //   icon: 'system-device',
//   //   text: '设备管理',
//   //   url: 'pages/inspection/scene/index',
//   //   level: 1,
//   // },
//   {
//     icon: 'user',
//     text: '个人中心',
//     url: 'pages/usercenter/index',
//     level: 2,
//   },
// ];
// function getMenu(usertypeid) {
//   const menu = [];
//   menu.forEach(v => {
//     if (app.globalData.userInfo.usertypeid <= v.level) {
//       menu.push(v);
//     }
//   });
//   return menu;
// }
// export { getMenu };
custom-tab-bar/index.js
@@ -1,23 +1,34 @@
import TabMenu from './data';
// import { getMenu } from './data';
const app = getApp();
Component({
  data: {
    active: 0,
    list: TabMenu,
    // list: getMenu(app.globalData.userInfo.usertypeid),
  },
  attached() {
    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;
    // const menu = [];
    // TabMenu.forEach(v => {
    //   if (app.globalData.userInfo.usertypeid <= v.level) {
    //     menu.push(v);
    //   }
    // });
    this.setData({ list: menu });
    // this.setData({ list: menu });
    // const index = [];
    // TabMenu.forEach((v, i) => {
    //   if (app.globalData.userInfo.usertypeid > v.level) {
    //     index.push(i);
    //   }
    // });
    // let offset = 0;
    // index.forEach(e => {
    //   TabMenu.splice(e + offset, 1);
    //   offset--;
    // });
    // this.setData({ list: TabMenu });
  },
  methods: {
    onChange(event) {
model/clue/clue.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
import dayjs from 'dayjs';
function getClue(data) {
  data._releaseTimeTxt = dayjs(data.creleaseTime).format('YYYY-MM-DD HH:mm:ss');
  return data;
}
function getClueList(dataList) {
  return dataList.map(v => {
    return getClue(v);
  });
}
export { getClue, getClueList };
model/clue/clueQuestion.js
@@ -1,7 +1,7 @@
import { cluePicUrl } from '../../config/index';
function getClueQuestion(data) {
  data.cqFilePath = data.cqFilePath.split(';').map((val) => {
  data._filePath = data.cqFilePath.split(';').map((val) => {
    return cluePicUrl + val;
  });
  return data;
model/clue/clueTask.js
@@ -17,16 +17,19 @@
      name: '总计',
      value: total,
      diff: '',
      clickable: false,
    },
    {
      name: '已完成',
      value: finished,
      diff: total == 0 ? '0%' : `${p1}%`,
      clickable: false,
    },
    {
      name: '待完成',
      value: unfinished,
      diff: total == 0 ? '0%' : `${p2}%`,
      clickable: false,
    },
  ];
}
pages/cluetask/clue-item.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
import { baseInputItem, hideInputItem, setDefaultValue } from '../../components/form/form-util.js';
import dataConclusionType from '../../common/clue/dataConclusionType';
import { Towns } from '../../common/dataTowns';
export function conclusionForm(defaultValue) {
  const items = [
    hideInputItem('id', 'ccId'),
    hideInputItem('线索id', 'cid'),
    baseInputItem('问题类型', 'ccQuestionType', true, 'picker', dataConclusionType.ConclusionType),
    baseInputItem('线索结论', 'ccConclusion', true),
    baseInputItem('详细描述', 'ccDetails', true, 'textarea'),
  ];
  // å¡«å……默认数据
  setDefaultValue(items, defaultValue);
  return items;
}
export function questionForm(defaultValue) {
  const items = [
    hideInputItem('id', 'cqId'),
    hideInputItem('问题编号', 'cqUid'),
    hideInputItem('线索id', 'cid'),
    hideInputItem('问题图片', 'cqFilePath'),
    baseInputItem('问题名称', 'cqName', true),
    baseInputItem('问题描述', 'cqDescription', true, 'textarea'),
    baseInputItem('所在街镇', 'cqStreet', true, 'picker', Towns[310106]),
    // baseInputItem('详细地址', 'cqAddress', true),
    // baseInputItem('坐标', '_coordinate', true),
  ];
  // å¡«å……默认数据
  setDefaultValue(items, defaultValue);
  return items;
}
pages/cluetask/conclusion/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
import { conclusionForm } from '../clue-item.js';
import { uploadConclusion } from '../../../services/clue/fetchClue';
Page({
  /**
   * é¡µé¢çš„初始数据
   */
  data: {
    formArray: [],
    // æ¨¡å¼ï¼Œadd: æ–°å¢žï¼›update:更新
    mode: 'add',
    submitText: '保存',
  },
  /**
   * ç”Ÿå‘½å‘¨æœŸå‡½æ•°--监听页面加载
   */
  onLoad(options) {
    this.getOpenerEventChannel().on('acceptConclusionData', data => {
      const { conclusion, clue, isInternal } = data;
      const formArray = conclusionForm(conclusion);
      const mode = conclusion ? 'update' : 'add';
      this.setData({ formArray, mode, clue, isInternal });
    });
  },
  // æäº¤è¡¨å•
  submit(e) {
    const { mode, clue, isInternal } = this.data;
    const formObj = e.detail;
    formObj.cid = clue.cid;
    formObj.ccInternal = isInternal;
    if (mode == 'add') {
      this.create(formObj);
    } else {
      this.update(formObj);
    }
  },
  // å–消表单
  cancel() {
    wx.navigateBack({
      delta: 1,
    });
  },
  // æ–°å¢ž
  create(formObj) {
    uploadConclusion(formObj).then(res => {
      if (res.success) {
        this.getOpenerEventChannel().emit('uploadOver');
        wx.navigateBack({
          delta: 1,
          success: () => {
            wx.showToast({
              title: '结论提交成功',
              duration: 2000,
              icon: 'success',
              mask: true,
            });
          },
        });
      } else {
        wx.showToast({
          title: res.message,
          duration: 2000,
          icon: 'error',
          mask: true,
        });
      }
    });
  },
  // æ›´æ–°
  update(formObj) {
    this.create(formObj);
  },
});
pages/cluetask/conclusion/index.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
{
  "navigationBarTitleText": "线索结论",
  "onReachBottomDistance": 10,
  "backgroundTextStyle": "light",
  "navigationBarTextStyle": "white",
  "navigationBarBackgroundColor": "#389AFF",
  "usingComponents": {
    "t-form": "/components/form/index"
  }
}
pages/cluetask/conclusion/index.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<view class="page">
  <view class="page-header"> </view>
  <view class="page-container">
    <t-form
      formArr="{{formArray}}"
      submit-text="{{submitText}}"
      bind:submit="submit"
      bind:cancel="cancel"
    ></t-form>
  </view>
  <view class="page-footer"></view>
</view>
pages/cluetask/conclusion/index.wxss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
.page .page-container {
  padding: 0;
}
pages/cluetask/home/index.js
@@ -3,7 +3,7 @@
import { useStatistic } from './statistic-proxy.js';
import { useTasks } from './tasks-proxy.js';
import clueApi from '../../../services/clue/fetchClue';
import { fetchClueTask } from '../../../services/clue/fetchClue';
const app = getApp();
@@ -18,7 +18,10 @@
  onLoad(options) {},
  onShow() {
    this.getTabBar().init();
    if (this.getTabBar()) {
      this.getTabBar().init();
    }
    this.refresh();
  },
  onPullDownRefresh() {
@@ -38,11 +41,15 @@
  optionsCount: 0,
  init() {
    this.optionsCount++;
    if (this.optionsCount == 2) this._startLoad();
    this.refresh();
  },
  refresh() {
    this._startLoad();
  },
  _fetchData(page) {
    return clueApi.fetchClueTask({}).then(res => {
    return fetchClueTask({}).then(res => {
      this.setData({ clueTaskList: res.data });
      this.calClueCount();
      this.formatClueTask();
pages/cluetask/home/tasks-proxy.js
@@ -14,7 +14,7 @@
    formatClueTask() {
      const { clueTaskList } = this.data;
      clueTaskList.forEach(t => {
        t._taskTime = dayjs(t.taskTime).format('YYYY-MM-DD')
        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}` : '';
@@ -27,5 +27,21 @@
      });
      this.setData({ clueTaskList });
    },
    handleClueTaskClick(e) {
      const { index } = e.currentTarget.dataset;
      const clueTask = this.data.clueTaskList[index];
      wx.navigateTo({
        url: '/pages/cluetask/manage/index',
        events: {
          uploadOver: () => {
            // çº¿ç´¢æäº¤å®ŒæˆåŽæ›´æ–°çŠ¶æ€
          },
        },
        success: res => {
          res.eventChannel.emit('acceptClueTask', clueTask);
        },
      });
    },
  },
});
pages/cluetask/home/tasks.wxml
@@ -1,10 +1,35 @@
<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
    class="clue-task__wrap"
    wx:for="{{clueTaskList}}"
    wx:key="index"
    wx:for-index="index"
    data-index="{{index}}"
    bindtap="handleClueTaskClick"
  >
    <view class="clue-task__title-wrap">
      <view class="clue-task__title">{{item._taskTime}}</view>
      <view class="clue-task__abstract">
        åº”急巡查
        <t-icon name="chevron-right" size="var(--icon-width)" color="black" />
      </view>
    </view>
    <view class="clue-task__abstract"> çº¿ç´¢ç¼–号:{{item.clueId}} </view>
    <view class="clue-task__abstract"> ä»»åŠ¡ä½ç½®ï¼š{{item._location}} </view>
    <t-divider />
    <view class="clue-task__tags">
      <view class="clue-task__tag-item">
        <span>{{item._resLevelTxt}}</span>
        <span class="clue-task__label">响应时间</span>
      </view>
      <view class="clue-task__tag-item">
        <span>{{item._travelModeTxt}}</span>
        <span class="clue-task__label">出行方式</span>
      </view>
      <view class="clue-task__tag-item">
        <span>{{item._hasUavTxt}}</span>
        <span class="clue-task__label">无人机</span>
      </view>
    </view>
  </view>
</view>
pages/cluetask/home/tasks.wxss
@@ -0,0 +1,41 @@
.clue-task__wrap {
  border-radius: var(--td-border-radius);
  background-color: var(--td-bg-color-block);
  padding: var(--td-spacer-1);
  margin-bottom: var(--td-spacer);
}
.clue-task__title-wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.clue-task__title {
  font-weight: 550;
  font-size: var(--td-font-size-m);
}
.clue-task__abstract {
  font-size: var(--td-font-size-s);
  color: var(--td-text-color-secondary);
  display: flex;
  align-items: center;
}
.clue-task__tags {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.clue-task__tags .clue-task__tag-item {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.clue-task__tag-item .clue-task__label {
  font-size: var(--td-font-size-s);
  color: var(--td-text-color-secondary);
}
pages/cluetask/manage/conclusion-proxy.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
import { getConclusion } from '../../../services/clue/fetchClue';
export const useConclusion = Behavior({
  data: {
    conclusion: undefined,
  },
  methods: {
    fetchClueConclusion() {
      const { clueTask } = this.data;
      getConclusion(clueTask.clueId, clueTask.internalTask).then(res => {
        this.setData({ conclusion: res.data });
      });
    },
    navToConclusion() {
      wx.navigateTo({
        url: '/pages/cluetask/conclusion/index',
        events: {
          uploadOver: () => {
            // ç»“论提交完成后更新状态
            this.fetchClueConclusion();
          },
        },
        success: res => {
          const { conclusion, clue, clueTask } = this.data;
          res.eventChannel.emit('acceptConclusionData', {
            conclusion: conclusion,
            clue: clue,
            isInternal: clueTask.internalTask,
          });
        },
      });
    },
  },
});
pages/cluetask/manage/conclusion.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
<DescriptionsList wx:if="{{conclusion}}" title="线索结论">
  <view slot="extra">
    <t-button
      theme="primary"
      size="extra-small"
      disabled="{{conclusion.ccUploaded}}"
      bind:tap="navToConclusion"
      >修改结论</t-button
    >
  </view>
  <DescriptionsListItem label="问题类型" content="{{conclusion.ccQuestionType}}" />
  <DescriptionsListItem label="线索结论" content="{{conclusion.ccConclusion}}" />
  <DescriptionsListItem label="详细描述" content="{{conclusion.ccDetails}}" />
</DescriptionsList>
<view wx:else class="dashed-border">
  <t-empty-page description="线索结论未上传">
    <t-button slot="action" size="small" theme="primary" bind:tap="navToConclusion"
      >反馈上报</t-button
    >
  </t-empty-page>
</view>
pages/cluetask/manage/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
import { useLoading } from '../../../behaviors/loading';
import { useConclusion } from './conclusion-proxy.js';
import { useQuestion } from './question-proxy.js';
import { fetchClue, fetchClueInternal } from '../../../services/clue/fetchClue';
Page({
  behaviors: [useLoading, useConclusion, useQuestion],
  data: {
    clue: {},
  },
  onLoad(options) {
    this.getOpenerEventChannel().on('acceptClueTask', data => {
      this.setData({
        clueTask: data,
      });
      this._startLoad();
    });
  },
  onPullDownRefresh() {
    this._startLoad();
  },
  onReachBottom() {
    this._loadMore();
  },
  /*********************************************************************************** */
  _fetchData(page) {
    const array = [];
    array.push(this.fetchClueQuestion());
    if (page == 1) {
      array.push(this.fetchClueData());
      array.push(this.fetchClueConclusion());
    }
    return Promise.all(array).then(res => {
      return res[0];
    });
  },
  fetchClueData() {
    const { clueTask } = this.data;
    const func = clueTask.internalTask ? fetchClueInternal : fetchClue;
    func({ cid: clueTask.clueId }).then(res => {
      if (res.data.length > 0) {
        this.setData({ clue: res.data[0] });
      }
    });
  },
  /*********************************************************************************** */
});
pages/cluetask/manage/index.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
{
  "navigationBarTitleText": "应急线索",
  "onReachBottomDistance": 10,
  "backgroundTextStyle": "light",
  "enablePullDownRefresh": true,
  "navigationBarTextStyle": "white",
  "navigationBarBackgroundColor": "#389AFF",
  "usingComponents": {
    "DescriptionsList": "/components/descriptions-list/index",
    "DescriptionsListItem": "/components/descriptions-list-item/index",
    "t-empty-page": "/components/empty-page/index"
  }
}
pages/cluetask/manage/index.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
<import src="/pages/common/template/template-loading.wxml" />
<view class="page">
  <template is="pulldown-loading" wx:if="{{pageLoading}}" />
  <view class="page-header">
    <DescriptionsList title="线索清单详情">
      <DescriptionsListItem label="是否上传" content="{{clue.cuploaded ? '已上传' : '未上传'}}" />
      <DescriptionsListItem label="线索编号" content="{{clue.cid}}" />
      <DescriptionsListItem label="线索名称" content="{{clue.cclueName}}" />
      <DescriptionsListItem label="下发时间" content="{{clue._releaseTimeTxt}}" />
    </DescriptionsList>
  </view>
  <view class="page-container">
    <include src="./conclusion.wxml" />
    <include src="./question.wxml" />
  </view>
  <view class="page-footer"></view>
</view>
pages/cluetask/manage/index.wxss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
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));
}
.btn-wrap {
  display: flex;
  justify-content: center;
  padding: 16px;
}
.dashed-border {
  border: 1px dashed var(--td-font-gray-3);
  border-radius: var(--td-border-radius);
  padding-bottom: 4px;
}
pages/cluetask/manage/question-proxy.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
import { getQuestion, deleteQuestion } from '../../../services/clue/fetchClue';
export const useQuestion = Behavior({
  data: {
    questionList: [],
  },
  methods: {
    fetchClueQuestion() {
      const { clueTask } = this.data;
      getQuestion(clueTask.clueId, clueTask.internalTask).then(res => {
        this.setData({ questionList: res.data });
      });
    },
    deleteQuestion(e) {
      const { index } = e.currentTarget.dataset;
      this.setData({ showDelete: true, currentIndex: index });
    },
    onDeleteConfirm() {
      const { currentIndex, questionList } = this.data;
      const q = questionList[currentIndex];
      deleteQuestion(q.cqId)
        .then(res => {
          if (res.success) {
            const index = questionList.indexOf(q);
            questionList.splice(index, 1);
            this.setData({ questionList });
          }
        })
        .finally(() => this.closeDialog());
    },
    checkQuestion(e) {
      const { index } = e.currentTarget.dataset;
      const question = this.data.questionList[index];
      wx.navigateTo({
        url: '/pages/cluetask/question/index',
        events: {
          uploadOver: () => {
            // é—®é¢˜ä¿®æ”¹å®ŒæˆåŽæ›´æ–°çŠ¶æ€
            this.fetchClueQuestion();
          },
        },
        success: res => {
          res.eventChannel.emit('acceptClueQuestionData', {
            question,
            clue: this.data.clue,
            isInternal: this.data.clueTask.internalTask,
          });
        },
      });
    },
    addQuestion() {
      wx.navigateTo({
        url: '/pages/cluetask/question/index',
        events: {
          uploadOver: () => {
            // é—®é¢˜æäº¤å®ŒæˆåŽæ›´æ–°çŠ¶æ€
            this.fetchClueQuestion();
          },
        },
        success: res => {
          res.eventChannel.emit('acceptClueQuestionData', {
            clue: this.data.clue,
            isInternal: this.data.clueTask.internalTask,
          });
        },
      });
    },
    closeDialog() {
      this.setData({ showDelete: false });
    },
  },
});
pages/cluetask/manage/question.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
<view class="title-primary-1">线索问题</view>
<block wx:if="{{questionList.length > 0}}">
  <block wx:for="{{questionList}}" wx:key="index" wx:for-index="index">
    <DescriptionsList title="{{item.cqUid}}">
      <view slot="extra">
        <t-button
          wx:if="{{!clue.cuploaded}}"
          theme="danger"
          size="extra-small"
          icon="delete"
          shape="square"
          data-index="{{index}}"
          bind:tap="deleteQuestion"
        ></t-button>
        <t-button
          style="margin-left: 4px"
          theme="primary"
          size="extra-small"
          data-index="{{index}}"
          bind:tap="checkQuestion"
          >{{ clue.cuploaded ? '问题详情' : '修改问题' }}</t-button
        >
      </view>
      <DescriptionsListItem label="问题名称" content="{{item.cqName}}" />
      <DescriptionsListItem label="问题描述" content="{{item.cqDescription}}" />
      <DescriptionsListItem label="详细地址" content="{{item.cqAddress}}" />
    </DescriptionsList>
  </block>
  <view class="btn-wrap">
    <t-button wx:if="{{!clue.cuploaded}}" size="extra-small" theme="primary" bind:tap="addQuestion"
      >添加问题</t-button
    >
  </view>
</block>
<view wx:else class="dashed-border">
  <t-empty-page description="无线索问题">
    <t-button slot="action" size="small" theme="primary" bind:tap="addQuestion">反馈上报</t-button>
  </t-empty-page>
</view>
<t-dialog
  visible="{{showDelete}}"
  title="删除确认"
  content=""
  confirm-btn="{{ { content: '确定', variant: 'base' } }}"
  cancel-btn="取消"
  bind:confirm="onDeleteConfirm"
  bind:cancel="closeDialog"
/>
pages/cluetask/question/index.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,220 @@
import { cluePicUrl } from '../../../config/index';
import { questionForm } from '../clue-item.js';
import { toLabel, toValue } from '../../../common/dataTowns';
import { uploadQuestion, updateQuestion } from '../../../services/clue/fetchClue';
Page({
  /**
   * é¡µé¢çš„初始数据
   */
  data: {
    validated: false,
    formArray: [],
    // æ¨¡å¼ï¼Œadd: æ–°å¢žï¼›update:更新
    mode: 'add',
    submitText: '保存',
    // å›¾ç‰‡ä¸Šä¼ åˆ—表
    fileList: [],
    // æ›´æ–°æ¨¡å¼ä¸‹ï¼Œè®°å½•被删除的原有图片
    deletedFileList: [],
    // å›¾ç‰‡å±•示方式
    gridConfig: {
      column: 3,
      width: 210,
      height: 210,
    },
    // å›¾ç‰‡é™åˆ¶å¤§å°
    sizeLimit: { size: 5, unit: 'MB', message: '图片大小不超过5MB' },
    // å®šä½ä¿¡æ¯
    locations: {
      // ä½ç½®åç§°
      name: '',
      address: '',
      // ä½¿ç”¨ gcj02 å›½æµ‹å±€åæ ‡ç³»
      longitude: '',
      latitude: '',
      coorTxt: '',
    },
  },
  /**
   * ç”Ÿå‘½å‘¨æœŸå‡½æ•°--监听页面加载
   */
  onLoad(options) {
    this.getOpenerEventChannel().on('acceptClueQuestionData', data => {
      const { question, clue, isInternal } = data;
      if (question) {
        this.setData({ uploaded: question.cqUploaded });
        question.cqStreet = toValue(question.cqStreet);
        this.setData({
          locations: {
            address: question.cqAddress,
            longitude: question.cqLongitude,
            latitude: question.cqLatitude,
            coorTxt: question.cqLongitude + ',' + question.cqLatitude,
          },
          fileList: question._filePath.map(v => {
            return {
              url: v,
            };
          }),
        });
      }
      const formArray = questionForm(question);
      const mode = question ? 'update' : 'add';
      this.setData({ formArray, mode, clue, isInternal });
    });
  },
  // æäº¤è¡¨å•
  submit(e) {
    // é¢å¤–的定位信息和图片校验
    this.setData({ validated: true });
    const { fileList, locations } = this.data;
    if (locations.address == '' || !fileList || fileList.length == 0) {
      return;
    }
    const formObj = e.detail;
    formObj.cId = this.data.clue.cid;
    formObj.cqStreet = toLabel(formObj.cqStreet);
    formObj.cqAddress = locations.address;
    formObj.cqLongitude = locations.longitude;
    formObj.cqLatitude = locations.latitude;
    formObj.cqInternal = this.data.isInternal;
    const images = [];
    // ç­›é€‰æ–°å¢žçš„æœ¬åœ°å›¾ç‰‡ä½œä¸ºä¸Šä¼ å›¾ç‰‡
    fileList.forEach(v => {
      if (v.url.indexOf(cluePicUrl) == -1) {
        images.push(v.url);
      }
    });
    const { mode } = this.data;
    if (mode == 'add') {
      this.create(formObj, images);
    } else {
      this.update(formObj, images);
    }
  },
  // å–消表单
  cancel() {
    wx.navigateBack({
      delta: 1,
    });
  },
  // æ–°å¢ž
  create(formObj, images) {
    uploadQuestion(formObj, images).then(res => this.modifySuccess(res));
  },
  // æ›´æ–°
  update(formObj, images) {
    // wx.showToast({
    //   title: '问题不允许更新',
    //   duration: 2000,
    //   icon: 'error',
    //   mask: true,
    // });
    // return;
    const deleteImgUrl = this.data.deletedFileList.join(';');
    updateQuestion(formObj, deleteImgUrl, images).then(res => this.modifySuccess(res));
  },
  // é—®é¢˜æ–°å¢žæˆ–修改成功后
  modifySuccess(res) {
    if (res.success) {
      this.getOpenerEventChannel().emit('uploadOver');
      wx.navigateBack({
        delta: 1,
        success: () => {
          wx.showToast({
            title: this.data.mode == 'add' ? '问题提交成功' : '问题更新成功',
            duration: 2000,
            icon: 'success',
            mask: true,
          });
        },
      });
    } else {
      wx.showToast({
        title: res.message,
        duration: 2000,
        icon: 'error',
        mask: true,
      });
    }
  },
  /************************************************************** */
  // æ‰‹åŠ¨ä¿®æ”¹å®šä½ä½ç½®æè¿°
  handleLocation(e) {
    const { value } = e.detail;
    // console.log(e);
    // console.log(this.data.locations);
    const { location } = this.data;
    location.address = value;
    this.setData({ locations });
  },
  // handleCoorTxt(e) {
  //   console.log(e);
  //   const { value } = e.detail;
  // },
  // ä»Žåœ°å›¾é€‰æ‹©å®šä½
  chooseLocation() {
    const { locations } = this.data;
    wx.chooseLocation({
      longitude: locations.longitude,
      latitude: locations.latitude,
      success: res => {
        this.setData({
          locations: {
            ...res,
            coorTxt: `${res.longitude},${res.latitude}`,
          },
        });
      },
    });
  },
  // æ·»åŠ å›¾ç‰‡
  handleAddImg(e) {
    const { fileList } = this.data;
    const { files } = e.detail;
    this.setData({
      fileList: [...fileList, ...files],
    });
  },
  // ç§»é™¤å›¾ç‰‡
  handleRemoveImg(e) {
    const { index } = e.detail;
    const { fileList, mode } = this.data;
    const [deletedfile] = fileList.splice(index, 1);
    // åœ¨æ›´æ–°æ¨¡å¼ä¸‹ï¼Œè®°å½•删除的原有图片,用于更新问题接口的参数
    if (mode == 'update') {
      // é€šè¿‡æ–‡ä»¶è·¯å¾„判断是否为远程http路径,
      if (deletedfile.url.indexOf(cluePicUrl) != -1) {
        const originUrl = deletedfile.url.replace(cluePicUrl, '');
        const { deletedFileList } = this.data;
        deletedFileList.push(originUrl);
        console.log(deletedFileList);
      }
    }
    this.setData({
      fileList,
    });
  },
});
pages/cluetask/question/index.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
{
  "navigationBarTitleText": "线索问题",
  "onReachBottomDistance": 10,
  "backgroundTextStyle": "light",
  "navigationBarTextStyle": "white",
  "navigationBarBackgroundColor": "#389AFF",
  "usingComponents": {
    "t-form": "/components/form/index"
  }
}
pages/cluetask/question/index.wxml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
<view class="page">
  <view class="page-header"> </view>
  <view class="page-container">
    <t-form
      editable="{{!uploaded}}"
      formArr="{{formArray}}"
      submit-text="{{submitText}}"
      bind:submit="submit"
      bind:cancel="cancel"
    >
      <t-input
        label="问题定位"
        placeholder="请输入问题定位"
        value="{{locations.address}}"
        bind:change="handleLocation"
      >
      </t-input>
      <t-input
        disabled
        label="问题坐标"
        placeholder="请输入问题坐标,用逗号分割"
        value="{{locations.coorTxt}}"
      >
        <t-button slot="suffix" theme="primary" size="extra-small" bindtap="chooseLocation">
          åœ°å›¾å®šä½
        </t-button>
      </t-input>
      <view wx:if="{{validated && locations.address == ''}}" class="tips"> é—®é¢˜å®šä½ä¸èƒ½ä¸ºç©º </view>
      <t-cell title="问题图片">
        <t-upload
          slot="description"
          media-type="{{['image']}}"
          files="{{fileList}}"
          gridConfig="{{gridConfig}}"
          max="{{3}}"
          size-limit="{{sizeLimit}}"
          bind:add="handleAddImg"
          bind:remove="handleRemoveImg"
        />
      </t-cell>
      <view wx:if="{{validated && (!fileList || fileList.length == 0)}}" class="tips">
          é—®é¢˜å›¾ç‰‡è‡³å°‘选一张
        </view>
    </t-form>
  </view>
  <view class="page-footer"></view>
</view>
pages/cluetask/question/index.wxss
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
.page .page-container {
  padding: 0;
}
.tips {
  margin-left: 16px;
  color: var(--td-error-color);
  font-size: var(--td-font-size-s);
}
pages/usercenter/index.js
@@ -49,9 +49,14 @@
  },
  onClickCell({ currentTarget }) {
    const { type } = currentTarget.dataset;
    const { type, url } = currentTarget.dataset;
    wx.navigateTo({ url });
    return
    switch (type) {
      case 'clue': {
        wx.navigateTo({ url: '/pages/cluetask/home/index' });
        break;
      }
      case 'supervision': {
        wx.navigateTo({ url: '/pages/inspection/scene/index' });
        break;
pages/usercenter/index.wxml
@@ -14,6 +14,7 @@
            arrow="{{!xitem.icon}}"
            note="{{xitem.tit}}"
            data-type="{{xitem.type}}"
            data-url="{{xitem.url}}"
            bordered="{{false}}"
            bind:click="onClickCell"
            t-class="t-cell-padding"
pages/usercenter/menu.js
@@ -1,55 +1,62 @@
export const menuData = [
  [
    {
      title: '应急线索',
      tit: '',
      url: '/pages/cluetask/home/index',
      type: 'clue',
      level: 1,
    },
    {
      title: '设备管理',
      tit: '',
      url: '',
      url: '/pages/inspection/scene/index',
      type: 'supervision',
      level: 1
      level: 1,
    },
    {
      title: '通知管理',
      tit: '',
      url: '',
      url: '/pages/usercenter/notice/index',
      type: 'notice',
      level: 2
      level: 2,
    },
    {
      title: '执法动态',
      tit: '',
      url: '',
      url: '/pages/news/law-enforcement/index',
      type: 'info',
      level: 2
      level: 2,
    },
    // {
    //   title: '自巡查管理',
    //   tit: '',
    //   url: '',
    //   url: '/pages/selfpatrol/index',
    //   type: 'patrol',
    // },
    {
      title: '环保日程',
      tit: '',
      url: '',
      type: 'schedule',
      level: 2
    },
    // {
    //   title: '环保日程',
    //   tit: '',
    //   url: '',
    //   type: 'schedule',
    //   level: 2
    // },
  ],
  [
    {
      title: '技术支持',
      tit: '',
      url: '',
      url: '/pages/usercenter/support/index',
      type: 'support',
      level: 3
      level: 3,
    },
    {
      title: '关于',
      tit: '',
      url: '',
      url: '/pages/usercenter/about/index',
      type: 'about',
      // icon: 'service',
      level: 3
      level: 3,
    },
  ],
];
project.private.config.json
@@ -2,8 +2,8 @@
  "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json ä¸­çš„相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
  "projectname": "ep-law-abiding-manage-weixin",
  "setting": {
    "compileHotReLoad": true,
    "urlCheck": false
    "compileHotReLoad": false,
    "urlCheck": true
  },
  "libVersion": "3.5.3"
  "libVersion": "3.7.12"
}
services/clue/fetchClue.js
@@ -5,110 +5,200 @@
import { get, post, _delete } from '../baseRequset';
import { clueUrl, cluePicUrl } from '../../config/index';
import { getClueQuestionList } from '../../model/clue/clueQuestion';
import { getClueList } from '../../model/clue/clue';
export default {
  /******************************************************************************* */
  /**
   * æŸ¥è¯¢çº¿ç´¢ä»»åŠ¡
   * @param {*} clueTask
   * @returns
   */
  fetchClueTask(clueTask) {
    return post(
      {
        url: `clue/task/fetch`,
        data: clueTask,
      },
      clueUrl,
    ).then(res => {
      return res.data;
    });
  },
/******************************************************************************* */
/**
 * æŸ¥è¯¢çº¿ç´¢
 * @param {*} clue
 * @returns
 */
function fetchClue(clue) {
  return post(
    {
      url: `clue/search`,
      data: clue,
    },
    clueUrl,
  ).then(res => {
    res.data.data = getClueList(res.data.data);
    return res.data;
  });
}
  /******************************************************************************* */
  /**
   * èŽ·å–çº¿ç´¢ç»“è®º
   * @param {string} clueId çº¿ç´¢id
   */
  getConclusion(clueId) {
    return get({
/**
 * æŸ¥è¯¢çº¿ç´¢
 * @param {*} clue
 * @returns
 */
function fetchClueInternal(clue) {
  return post(
    {
      url: `clue/internal/search`,
      data: clue,
    },
    clueUrl,
  ).then(res => {
    res.data.data = getClueList(res.data.data);
    return res.data;
  });
}
/******************************************************************************* */
/**
 * æŸ¥è¯¢çº¿ç´¢ä»»åŠ¡
 * @param {*} clueTask
 * @returns
 */
function fetchClueTask(clueTask) {
  return post(
    {
      url: `clue/task/fetch`,
      data: clueTask,
    },
    clueUrl,
  ).then(res => {
    return res.data;
  });
}
/******************************************************************************* */
/**
 * èŽ·å–çº¿ç´¢ç»“è®º
 * @param {string} clueId çº¿ç´¢id
 */
function getConclusion(clueId, internal) {
  return get(
    {
      url: `clue/conclusion/fetch`,
      params: {
        clueId: clueId,
        clueId,
        internal,
      },
      clueUrl,
    }).then(res => res.data);
  },
    },
    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 {object} conclusion çº¿ç´¢
 * @returns
 */
function uploadConclusion(conclusion) {
  return post(
    {
      url: `clue/conclusion/upload`,
      data: conclusion,
    },
    clueUrl,
  ).then(res => res.data);
}
  /******************************************************************************* */
  /**
   * èŽ·å–å·²æäº¤çš„çº¿ç´¢é—®é¢˜
   * @param {string} clueId çº¿ç´¢id
   */
  getQuestion(clueId) {
    return get({
/******************************************************************************* */
/**
 * èŽ·å–å·²æäº¤çš„çº¿ç´¢é—®é¢˜
 * @param {string} clueId çº¿ç´¢id
 */
function getQuestion(clueId, internal) {
  return get(
    {
      url: `clue/question/fetch`,
      params: {
        clueId: clueId,
        clueId,
        internal,
      },
      clueUrl,
    }).then(res => {
      return getClueQuestionList(res.data);
    },
    clueUrl,
  ).then(res => {
    res.data.data = getClueQuestionList(res.data.data);
    return res.data;
  });
}
/**
 * ä¸Šä¼ çº¿ç´¢é—®é¢˜
 * @param {object} question é—®é¢˜æè¿°
 * @param {*} files é—®é¢˜å›¾ç‰‡
 * @returns
 */
function 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,
    files: images,
  })
    .submit(clueUrl + `/clue/question/upload`)
    .then(res => {
      return 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,
      };
/**
 * æ›´æ–°çº¿ç´¢é—®é¢˜
 * @param {object} question é—®é¢˜æè¿°
 * @param {Sting} deleteImgUrl åˆ é™¤çš„图片相对路径,用;分割
 * @param {*} files é—®é¢˜å›¾ç‰‡
 * @returns
 */
function updateQuestion(question, deleteImgUrl, files) {
  const fields = [
    {
      name: 'question',
      value: JSON.stringify(question),
    },
    {
      name: 'deleteImg',
      value: deleteImgUrl,
    },
  ];
  const images = files.map(f => {
    return {
      name: 'images',
      filePath: f,
    };
  });
  return new Multipart({
    fields,
    files: images,
  })
    .submit(clueUrl + `/clue/question/update`)
    .then(res => {
      return res.data;
    });
}
    return new Multipart({
      fields,
      images,
    })
      .submit(clueUrl + `/clue/question/upload`)
      .then(res => {
        return res.data;
      });
  },
  deleteQuestion(questionId) {
    return _delete({
function deleteQuestion(questionId) {
  return _delete(
    {
      url: `clue/question`,
      params: {
        questionId: questionId,
      },
      clueUrl,
    }).then(res => res.data);
  },
    },
    clueUrl,
  ).then(res => res.data);
}
export {
  fetchClue,
  fetchClueInternal,
  fetchClueTask,
  getConclusion,
  uploadConclusion,
  updateQuestion,
  getQuestion,
  uploadQuestion,
  deleteQuestion,
};