Browse Source

first commit

master
wxc 8 months ago
commit
fb62116df8
  1. 6
      .gitignore
  2. 82
      App.vue
  3. 129
      README.md
  4. 5
      api/auth.js
  5. 5
      api/book.js
  6. 17
      api/comfort.js
  7. 9
      api/depart.js
  8. 7
      api/dict.js
  9. 5
      api/feedback.js
  10. 9
      api/file.js
  11. 17
      api/inspection.js
  12. 13
      api/negative.js
  13. 9
      api/photo.js
  14. 5
      api/police.js
  15. 5
      api/problemType.js
  16. 131
      api/request.js
  17. 13
      api/selfexamination.js
  18. 13
      api/task.js
  19. 13
      api/taskProblem.js
  20. 13
      api/testingAlcohol.js
  21. 0
      changelog.md
  22. 13
      common/auth.js
  23. 12
      common/dict.js
  24. 97
      common/graceChecker.js
  25. 352
      common/html-parser.js
  26. 209
      common/permission.js
  27. 136
      common/uni-nvue.css
  28. 1685
      common/uni.scss
  29. 154
      common/util.js
  30. 31
      components/empty.vue
  31. 91
      components/file-list.vue
  32. 60
      components/filter-radio.vue
  33. 134
      components/inspection-task-data-picker.vue
  34. 110
      components/modal.vue
  35. 36
      components/n-button.vue
  36. 420
      components/negative.vue
  37. 30
      components/net-image.vue
  38. 214
      components/police-picker.vue
  39. 21
      components/problem-picker.vue
  40. 52
      components/skeleton.vue
  41. 87
      components/tabs.vue
  42. 182
      components/upload.vue
  43. 88
      hybrid/html/local.html
  44. 22
      index.html
  45. 11
      jest.config.js
  46. 45
      main.js
  47. 200
      manifest.json
  48. 81
      package.json
  49. 215
      pages.json
  50. 121
      pages/books/index.vue
  51. 31
      pages/center/about.vue
  52. 65
      pages/center/feedback.vue
  53. 21
      pages/center/manual.vue
  54. 176
      pages/comfort/action.vue
  55. 513
      pages/comfort/add.vue
  56. 209
      pages/comfort/list.vue
  57. 296
      pages/common/camera.nvue
  58. 92
      pages/duty/index.vue
  59. 442
      pages/index/index.vue
  60. 198
      pages/negative/action.vue
  61. 226
      pages/negative/index.vue
  62. 41
      pages/negative/info.vue
  63. 60
      pages/photo/index.vue
  64. 392
      pages/task/index.vue
  65. 42
      pages/task/inspection/info.vue
  66. 131
      pages/task/inspection/list.vue
  67. 138
      pages/task/problem/add.vue
  68. 89
      pages/task/problem/index.vue
  69. 201
      pages/task/problem/list.vue
  70. 106
      pages/task/selfexamination/add.vue
  71. 85
      pages/task/selfexamination/index.vue
  72. 82
      pages/task/selfexamination/info.vue
  73. 193
      pages/task/selfexamination/list.vue
  74. 193
      pages/task/testingAlcohol/add.vue
  75. 104
      pages/task/testingAlcohol/info.vue
  76. 293
      pages/task/testingAlcohol/people.vue
  77. 232
      platforms/app-plus/feedback/feedback.vue
  78. 64
      platforms/app-plus/orientation/orientation.vue
  79. 69
      platforms/app-plus/proximity/proximity.vue
  80. 81
      platforms/app-plus/push/push.vue
  81. 106
      platforms/app-plus/shake/shake.vue
  82. 105
      platforms/app-plus/speech/speech.vue
  83. BIN
      static/camer.png
  84. BIN
      static/center-selected.png
  85. BIN
      static/center.png
  86. 12
      static/customicons.css
  87. BIN
      static/edit.png
  88. BIN
      static/empty.png
  89. BIN
      static/font/Pacifico-Regular.ttf
  90. BIN
      static/icon/doc.png
  91. BIN
      static/icon/ic_apply.png
  92. BIN
      static/icon/ic_book.png
  93. BIN
      static/icon/ic_c_app.png
  94. BIN
      static/icon/ic_c_book.png
  95. BIN
      static/icon/ic_c_message.png
  96. BIN
      static/icon/ic_camera.png
  97. BIN
      static/icon/ic_card.png
  98. BIN
      static/icon/ic_question.png
  99. BIN
      static/icon/ic_selfexamination.png
  100. BIN
      static/icon/ic_task.png
  101. Some files were not shown because too many files have changed in this diff Show More

6
.gitignore vendored

@ -0,0 +1,6 @@
.history/
.idea
.vscode
/node_modules/
/unpackage/

82
App.vue

@ -0,0 +1,82 @@
<script>
import {
mapMutations
} from 'vuex'
import {
version
} from './package.json'
import store from '@/store'
import { getToken, setToken } from '@/common/auth'
import { login } from '@/api/auth'
export default {
onLaunch: async function() {
const url = 'http://127.0.0.1:8080/app/';
//---------------------------------------------------------------
// #ifdef H5
if (!store.state.requestUrl) {
store.commit('setRequestUrl', url + 'forward')
}
if (!getToken() || !store.state.hasLogin) {
const userData = await login({ empNo: '012893' });
setToken(userData.token);
store.commit('setUser', userData.user)
}
// #endif
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
globalData: {
test: ''
},
methods: {
...mapMutations(['setUniverifyErrorMsg', 'setUniverifyLogin'])
}
}
</script>
<style lang="scss">
@import '@/uni_modules/uni-scss/index.scss';
/* #ifndef APP-PLUS-NVUE */
/* uni.css - 通用组件、模板样式库,可以当作一套ui库应用 */
@import './common/uni.scss';
@import '@/static/customicons.css';
/* H5 兼容 pc 所需 */
/* #ifdef H5 */
@media screen and (min-width: 768px) {
body {
overflow-y: scroll;
}
}
uni-page-body {
min-height: 100% !important;
height: auto !important;
}
.uni-top-window uni-tabbar .uni-tabbar {
background-color: #fff !important;
}
.uni-app--showleftwindow .hideOnPc {
display: none !important;
}
/* #endif */
/* 以下样式用于 hello uni-app 演示所需 */
page {
height: 100%;
font-size: 28rpx;
/* line-height: 1.8; */
}
/* #endif*/
</style>

129
README.md

@ -0,0 +1,129 @@
# hello-uniapp
`uni-app`框架示例,一套代码,同时发行到iOS、Android、H5、小程序等多个平台,请使用手机在下方扫码快速体验`uni-app`的强大功能。[官方文档](https://uniapp.dcloud.net.cn/)
## 快速上手
hello-uniapp 示例工程可以通过两种方式创建, 一种是 HBuilderX, 配套 IDE,集成开发;另一种是 CLI 创建;推荐前者。
### 通过 HBuilderX 可视化界面创建(推荐)
可视化的方式比较简单,HBuilderX内置相关环境,开箱即用,无需配置nodejs。
开始之前,开发者需先下载安装如下工具:
- HBuilderX:[官方IDE下载地址](https://www.dcloud.io/hbuilderx.html)
HBuilderX是通用的前端开发工具,但为`uni-app`做了特别强化,请下载App开发版。
由于截图在 github 不便浏览,参见官方文档 [HBuilderX 可视化界面创建](https://uniapp.dcloud.net.cn/quickstart?id=_1-%e9%80%9a%e8%bf%87-hbuilderx-%e5%8f%af%e8%a7%86%e5%8c%96%e7%95%8c%e9%9d%a2)
### 通过 vue-cli 创建
```
npm install -g @vue/cli
```
#### 创建uni-app
**使用正式版**(对应HBuilderX最新正式版)
```
vue create -p dcloudio/uni-preset-vue my-project
```
**使用alpha版**(对应HBuilderX最新alpha版)
```
vue create -p dcloudio/uni-preset-vue#alpha my-alpha-project
```
此时,会提示选择项目模板,选择 `hello uni-app` 项目模板,如下所示:
<div>
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/h5-cli-01.png" width="300">
</div>
创建好后,进入项目目录
```
cd my-project
```
执行该命令运行到 h5 端
```
npm run dev:h5
```
欢迎提 issues,推荐到[官方社区](https://ask.dcloud.net.cn/explore/)提问。
## 扫码体验
<div class="quick">
<p>一套代码编到10个平台,这不是梦想。眼见为实,扫描10个二维码,亲自体验最全面的跨平台效果!</p>
<div style="display: flex;">
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://web-assets.dcloud.net.cn/unidoc/zh/uni-android.png" width="160" />
</div>
<b>Android版</b>
</a>
<a href="https://itunes.apple.com/cn/app/hello-uni-app/id1417078253?mt=8" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://web-assets.dcloud.net.cn/unidoc/zh/uni-h5.png" width="160" />
</div>
<b>iOS版</b>
</a>
<a href="https://hellouniapp.dcloud.net.cn/" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/uni-h5-hosting-qr.png" width="160" />
</div>
<b>H5版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box"><img src="//img.cdn.aliyun.dcloud.net.cn/guide/uniapp/gh_33446d7f7a26_430.jpg" width="160" /></div>
<b>微信小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box"><img src="https://web-assets.dcloud.net.cn/unidoc/zh/alipay1.png" width="160" /></div>
<b>支付宝小程序版</b>
</a>
</div>
<div class="flex-img-group-view" style="margin-top: 20px;">
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box"><img src="https://web-assets.dcloud.net.cn/unidoc/zh/baidu-uniapp.png" width="160" /></div>
<b>百度小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/mp-toutiao.png" width="160" />
</div>
<b>字节跳动小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-qq.png" width="160" />
</div>
<b>QQ小程序版</b>
</a>
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-qa-union.png" width="160" />
</div>
<b>快应用</b>
</a>
<a href="https://so.mp.360.cn/mp.html?appid=qh4j181qqtru354st6" target="_blank" class="clear-style barcode-view">
<div class="barcode-img-box">
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-mp-360-qr.png" width="160" />
</div>
<b>360小程序</b>
</a>
</div>
<p>
<em>注:某些平台不能提交简单demo,故补充了一些其他功能;hello uni-app示例代码可从[github](https://github.com/dcloudio/hello-uniapp)获取</em></br>
<em>快应用仅支持 vivo 、oppo、华为</em></br>
<em>360小程序仅 windows平台支持,需要在360浏览器中打开</em></br>
</p>
</div>
`uni-app`官网文档详见[https://uniapp.dcloud.io](https://uniapp.dcloud.io)
更多uni-app的模板、示例详见[插件市场](https://ext.dcloud.net.cn/)

5
api/auth.js

@ -0,0 +1,5 @@
import request from './request'
export function login(query) {
return request.post({ url: '/app/login', query })
}

5
api/book.js

@ -0,0 +1,5 @@
import request from './request'
export function listBook(query) {
return request.get({ url: '/book', query })
}

17
api/comfort.js

@ -0,0 +1,17 @@
import request from './request'
export function listComfort(query) {
return request.get({ url: '/comfort', query })
}
export function getComfort(id) {
return request.get({ url: '/comfort/' + id })
}
export function addComfort(body) {
return request.post({ url: '/comfort', body })
}
export function listRightPersonByDepartId(departId) {
return request.get({ url: '/rightPerson/depart/' + departId })
}

9
api/depart.js

@ -0,0 +1,9 @@
import request from './request'
export function departTree() {
return request.get({ url: '/depart/tree' })
}
export function secondList() {
return request.get({ url: '/depart/second' })
}

7
api/dict.js

@ -0,0 +1,7 @@
import request from './request'
export function listDictDataAll(dictType) {
return request.get({
url: `/dict/data/${dictType}`
});
}

5
api/feedback.js

@ -0,0 +1,5 @@
import request from './request'
export function addFeedback(body) {
return request.post({ url: '/feedback', body })
}

9
api/file.js

@ -0,0 +1,9 @@
import request from './request'
export function uploadFileBase64(body) {
return request.post({ url: '/file/upload/base64', body })
}
export function getFileBase64(filepath) {
return request.get({ url: '/file/base64?filepath=' + filepath })
}

17
api/inspection.js

@ -0,0 +1,17 @@
import request from './request'
export function listInspection(query) {
return request.get({ url: `/task/inspection`, query })
}
export function listInspectionProblem(taskId, query) {
return request.get({ url: `/task/inspection/${taskId}/problem`, query })
}
export function signInspection(taskId) {
return request.post({ url: `/task/inspection/${taskId}/sign` })
}
export function getInspection(taskId) {
return request.get({ url: `/task/inspection/${taskId}` })
}

13
api/negative.js

@ -0,0 +1,13 @@
import request from './request'
export function listNegative(query) {
return request.get({ url: `/negative`, query })
}
export function getNegative(id, workId) {
return request.get({ url: `/negative/${id}?workId=${workId || ''}` })
}
export function executeNegative(id, body) {
return request.post({ url: `/negative/${id}/execute`, body})
}

9
api/photo.js

@ -0,0 +1,9 @@
import request from './request'
export function listPhoto() {
return request.get({ url: '/photo' })
}
export function addPhoto(body) {
return request.post({ url: '/photo', body })
}

5
api/police.js

@ -0,0 +1,5 @@
import request from './request'
export function listPolice(query) {
return request.get({ url: '/police', query })
}

5
api/problemType.js

@ -0,0 +1,5 @@
import request from './request'
export function problemTypeTree() {
return request.get({ url: '/dict/content/tree' })
}

131
api/request.js

@ -0,0 +1,131 @@
import { getToken, clearToken } from '@/common/auth'
import store from '@/store'
function get(options) {
options.method = 'GET';
return ajax(options.url, options)
}
function post(options) {
options.method = 'POST';
return ajax(options.url, options)
}
function put(options) {
options.method = 'PUT';
return ajax(options.url, options)
}
function del(options) {
options.method = 'DELETE';
return ajax(options.url, options)
}
function ajax(url, options) {
let body;
if (options?.params && Object.keys(options.params).length > 0) {
if (options.method === 'GET') {
options.query = options.params;
} else {
body = JSON.stringify(options.params);
}
}
if (options?.query) {
const queryParams = []
for (const key in options.query) {
queryParams.push(key + '=' + options.query[key])
}
url += (url.indexOf('?') > -1 ? '' : '?') + queryParams.join('&')
}
if (options?.body) {
if (options.body === 'string') {
body = options.body;
} else {
if (Object.keys(options.body).length > 0) {
body = JSON.stringify(options.body);
}
}
}
return new Promise((resolve, reject) => {
const requestUrl = store.state.requestUrl;
if (!requestUrl) {
uni.showToast({
title: '未找到资源',
icon: 'none',
duration: 5000
})
reject('未找到资源')
return
}
uni.request({
url: requestUrl,
method: 'POST',
data: {
url,
method: options.method,
token: getToken(),
body
},
header: {
"Content-Type": "application/json",
messageId: generateUUID(),
appCredential: store.state.appCredential,
userCredential: store.state.userCredential
},
success: function(response) {
if (response.statusCode !== 200) {
uni.showToast({
title: 'response信息:' + JSON.stringify(response),
icon: 'none',
duration: 5000
});
return
}
const res = response.data;
if (res.code === 200) {
resolve(res.data)
} else {
let message = res.message;
if (res.code === 401) {
message = '未授权登录'
}
uni.showToast({
title: message || '系统异常',
icon: 'none',
duration: 5000
});
reject(res)
}
},
fail: function(err) {
uni.showToast({
title: err,
icon: 'none',
duration: 5000
})
resolve()
}
})
})
}
function generateUUID() {
var d = new Date().getTime();
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
}
const request = {
get,
post,
put,
del
}
export default request;

13
api/selfexamination.js

@ -0,0 +1,13 @@
import request from './request'
export function getTaskSelfexamination(taskId) {
return request.get({ url: '/task/selfexamination/' + taskId })
}
export function signTaskSelfexamination(taskId) {
return request.post({ url: `/task/selfexamination/${taskId}/sign` })
}
export function listTaskSelfexamination(query) {
return request.get({ url: `/task/selfexamination`, query })
}

13
api/task.js

@ -0,0 +1,13 @@
import request from './request'
export function listTask(query) {
return request.get({ url: '/task', query })
}
export function getTaskCount() {
return request.get({ url: '/task/count' })
}
export function submitTask(taskId) {
return request.post({ url: `/task/${taskId}/submit` })
}

13
api/taskProblem.js

@ -0,0 +1,13 @@
import request from './request'
export function getProblem(id) {
return request.get({ url: '/task/problem/' + id })
}
export function listProblem(query) {
return request.get({ url: '/task/problem', query })
}
export function addProblem(body) {
return request.post({ url: '/task/problem', body })
}

13
api/testingAlcohol.js

@ -0,0 +1,13 @@
import request from './request'
export function listTestingAlcoholPeople(taskId, query) {
return request.get({ url: `/task/testingAlcohol/${taskId}/people`, query })
}
export function updateTestingAlcoholPeople(taskId, body) {
return request.put({ url: `/task/testingAlcohol/${taskId}/people`, body })
}
export function countTestingAlcoholPeople(taskId) {
return request.get({ url: `/task/testingAlcohol/${taskId}/people/count` })
}

0
changelog.md

13
common/auth.js

@ -0,0 +1,13 @@
const TOKEN_KEY = "token";
export function setToken(token) {
uni.setStorageSync(TOKEN_KEY, token);
}
export function getToken() {
return uni.getStorageSync(TOKEN_KEY);
}
export function clearToken() {
uni.removeStorageSync(TOKEN_KEY);
}

12
common/dict.js

@ -0,0 +1,12 @@
import {
listDictDataAll
} from '@/api/dict'
import { ref } from 'vue'
export const getDictOptions = (dictType) => {
const result = ref([])
listDictDataAll(dictType).then(data => {
result.value = data
})
return result;
}

97
common/graceChecker.js

@ -0,0 +1,97 @@
/**
数据验证表单验证
来自 grace.hcoder.net
作者 hcoder 深海
*/
export default {
error:'',
check : function (data, rule){
for(var i = 0; i < rule.length; i++){
if (!rule[i].checkType){return true;}
if (!rule[i].name) {return true;}
if (!rule[i].errorMsg) {return true;}
if (!data[rule[i].name]) {this.error = rule[i].errorMsg; return false;}
switch (rule[i].checkType){
case 'string':
var reg = new RegExp('^.{' + rule[i].checkRule + '}$');
if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
break;
case 'int':
var reg = new RegExp('^(-[1-9]|[1-9])[0-9]{' + rule[i].checkRule + '}$');
if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
break;
break;
case 'between':
if (!this.isNumber(data[rule[i].name])){
this.error = rule[i].errorMsg;
return false;
}
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'betweenD':
var reg = /^-?[1-9][0-9]?$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'betweenF':
var reg = /^-?[0-9][0-9]?.+[0-9]+$/;
if (!reg.test(data[rule[i].name])){this.error = rule[i].errorMsg; return false;}
var minMax = rule[i].checkRule.split(',');
minMax[0] = Number(minMax[0]);
minMax[1] = Number(minMax[1]);
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
this.error = rule[i].errorMsg;
return false;
}
break;
case 'same':
if (data[rule[i].name] != rule[i].checkRule) { this.error = rule[i].errorMsg; return false;}
break;
case 'notsame':
if (data[rule[i].name] == rule[i].checkRule) { this.error = rule[i].errorMsg; return false; }
break;
case 'email':
var reg = /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'phoneno':
var reg = /^1[0-9]{10,10}$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'zipcode':
var reg = /^[0-9]{6}$/;
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'reg':
var reg = new RegExp(rule[i].checkRule);
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
break;
case 'in':
if(rule[i].checkRule.indexOf(data[rule[i].name]) == -1){
this.error = rule[i].errorMsg; return false;
}
break;
case 'notnull':
if(data[rule[i].name] == null || data[rule[i].name].length < 1){this.error = rule[i].errorMsg; return false;}
break;
}
}
return true;
},
isNumber : function (checkVal){
var reg = /^-?[1-9][0-9]?.?[0-9]*$/;
return reg.test(checkVal);
}
}

352
common/html-parser.js

@ -0,0 +1,352 @@
/*
* HTML5 Parser By Sam Blowes
*
* Designed for HTML5 documents
*
* Original code by John Resig (ejohn.org)
* http://ejohn.org/blog/pure-javascript-html-parser/
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* ----------------------------------------------------------------------------
* License
* ----------------------------------------------------------------------------
*
* This code is triple licensed using Apache Software License 2.0,
* Mozilla Public License or GNU Public License
*
* ////////////////////////////////////////////////////////////////////////////
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* ////////////////////////////////////////////////////////////////////////////
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Simple HTML Parser.
*
* The Initial Developer of the Original Code is Erik Arvidsson.
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
* Reserved.
*
* ////////////////////////////////////////////////////////////////////////////
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* ----------------------------------------------------------------------------
* Usage
* ----------------------------------------------------------------------------
*
* // Use like so:
* HTMLParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* // or to get an XML string:
* HTMLtoXML(htmlString);
*
* // or to get an XML DOM Document
* HTMLtoDOM(htmlString);
*
* // or to inject into an existing document/DOM node
* HTMLtoDOM(htmlString, document);
* HTMLtoDOM(htmlString, document.body);
*
*/
// Regular Expressions for parsing tags and attributes
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
// fixed by xxx 将 ins 标签从块级名单中移除
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
var special = makeMap('script,style');
function HTMLParser(html, handler) {
var index;
var chars;
var match;
var stack = [];
var last = html;
stack.last = function () {
return this[this.length - 1];
};
while (html) {
chars = true; // Make sure we're not in a script or style element
if (!stack.last() || !special[stack.last()]) {
// Comment
if (html.indexOf('<!--') == 0) {
index = html.indexOf('-->');
if (index >= 0) {
if (handler.comment) {
handler.comment(html.substring(4, index));
}
html = html.substring(index + 3);
chars = false;
} // end tag
} else if (html.indexOf('</') == 0) {
match = html.match(endTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(endTag, parseEndTag);
chars = false;
} // start tag
} else if (html.indexOf('<') == 0) {
match = html.match(startTag);
if (match) {
html = html.substring(match[0].length);
match[0].replace(startTag, parseStartTag);
chars = false;
}
}
if (chars) {
index = html.indexOf('<');
var text = index < 0 ? html : html.substring(0, index);
html = index < 0 ? '' : html.substring(index);
if (handler.chars) {
handler.chars(text);
}
}
} else {
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
if (handler.chars) {
handler.chars(text);
}
return '';
});
parseEndTag('', stack.last());
}
if (html == last) {
throw 'Parse Error: ' + html;
}
last = html;
} // Clean up any remaining tags
parseEndTag();
function parseStartTag(tag, tagName, rest, unary) {
tagName = tagName.toLowerCase();
if (block[tagName]) {
while (stack.last() && inline[stack.last()]) {
parseEndTag('', stack.last());
}
}
if (closeSelf[tagName] && stack.last() == tagName) {
parseEndTag('', tagName);
}
unary = empty[tagName] || !!unary;
if (!unary) {
stack.push(tagName);
}
if (handler.start) {
var attrs = [];
rest.replace(attr, function (match, name) {
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
attrs.push({
name: name,
value: value,
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
});
});
if (handler.start) {
handler.start(tagName, attrs, unary);
}
}
}
function parseEndTag(tag, tagName) {
// If no tag name is provided, clean shop
if (!tagName) {
var pos = 0;
} // Find the closest opened tag of the same type
else {
for (var pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos] == tagName) {
break;
}
}
}
if (pos >= 0) {
// Close all the open elements, up the stack
for (var i = stack.length - 1; i >= pos; i--) {
if (handler.end) {
handler.end(stack[i]);
}
} // Remove the open elements from the stack
stack.length = pos;
}
}
}
function makeMap(str) {
var obj = {};
var items = str.split(',');
for (var i = 0; i < items.length; i++) {
obj[items[i]] = true;
}
return obj;
}
function removeDOCTYPE(html) {
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
}
function parseAttrs(attrs) {
return attrs.reduce(function (pre, attr) {
var value = attr.value;
var name = attr.name;
if (pre[name]) {
pre[name] = pre[name] + " " + value;
} else {
pre[name] = value;
}
return pre;
}, {});
}
function parseHtml(html) {
html = removeDOCTYPE(html);
var stacks = [];
var results = {
node: 'root',
children: []
};
HTMLParser(html, {
start: function start(tag, attrs, unary) {
var node = {
name: tag
};
if (attrs.length !== 0) {
node.attrs = parseAttrs(attrs);
}
if (unary) {
var parent = stacks[0] || results;
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
} else {
stacks.unshift(node);
}
},
end: function end(tag) {
var node = stacks.shift();
if (node.name !== tag) console.error('invalid state: mismatch end tag');
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
chars: function chars(text) {
var node = {
type: 'text',
text: text
};
if (stacks.length === 0) {
results.children.push(node);
} else {
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
},
comment: function comment(text) {
var node = {
node: 'comment',
text: text
};
var parent = stacks[0];
if (!parent.children) {
parent.children = [];
}
parent.children.push(node);
}
});
return results.children;
}
export default parseHtml;

209
common/permission.js

@ -0,0 +1,209 @@
/// null = 未请求,1 = 已允许,0 = 拒绝|受限, 2 = 系统未开启
var isIOS
function album() {
var result = 0;
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
var authStatus = PHPhotoLibrary.authorizationStatus();
if (authStatus === 0) {
result = null;
} else if (authStatus == 3) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(PHPhotoLibrary);
return result;
}
function camera() {
var result = 0;
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
if (authStatus === 0) {
result = null;
} else if (authStatus == 3) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(AVCaptureDevice);
return result;
}
function location() {
var result = 0;
var cllocationManger = plus.ios.import("CLLocationManager");
var enable = cllocationManger.locationServicesEnabled();
var status = cllocationManger.authorizationStatus();
if (!enable) {
result = 2;
} else if (status === 0) {
result = null;
} else if (status === 3 || status === 4) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(cllocationManger);
return result;
}
function push() {
var result = 0;
var UIApplication = plus.ios.import("UIApplication");
var app = UIApplication.sharedApplication();
var enabledTypes = 0;
if (app.currentUserNotificationSettings) {
var settings = app.currentUserNotificationSettings();
enabledTypes = settings.plusGetAttribute("types");
if (enabledTypes == 0) {
result = 0;
console.log("推送权限没有开启");
} else {
result = 1;
console.log("已经开启推送功能!")
}
plus.ios.deleteObject(settings);
} else {
enabledTypes = app.enabledRemoteNotificationTypes();
if (enabledTypes == 0) {
result = 3;
console.log("推送权限没有开启!");
} else {
result = 4;
console.log("已经开启推送功能!")
}
}
plus.ios.deleteObject(app);
plus.ios.deleteObject(UIApplication);
return result;
}
function contact() {
var result = 0;
var CNContactStore = plus.ios.import("CNContactStore");
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
if (cnAuthStatus === 0) {
result = null;
} else if (cnAuthStatus == 3) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(CNContactStore);
return result;
}
function record() {
var result = null;
var avaudiosession = plus.ios.import("AVAudioSession");
var avaudio = avaudiosession.sharedInstance();
var status = avaudio.recordPermission();
console.log("permissionStatus:" + status);
if (status === 1970168948) {
result = null;
} else if (status === 1735552628) {
result = 1;
} else {
result = 0;
}
plus.ios.deleteObject(avaudiosession);
return result;
}
function calendar() {
var result = null;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
if (ekAuthStatus == 3) {
result = 1;
console.log("日历权限已经开启");
} else {
console.log("日历权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
function memo() {
var result = null;
var EKEventStore = plus.ios.import("EKEventStore");
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
if (ekAuthStatus == 3) {
result = 1;
console.log("备忘录权限已经开启");
} else {
console.log("备忘录权限没有开启");
}
plus.ios.deleteObject(EKEventStore);
return result;
}
function requestAndroid(permissionID) {
return new Promise((resolve, reject) => {
plus.android.requestPermissions(
[permissionID],
function(resultObj) {
var result = 0;
for (var i = 0; i < resultObj.granted.length; i++) {
var grantedPermission = resultObj.granted[i];
console.log('已获取的权限:' + grantedPermission);
result = 1
}
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
var deniedPresentPermission = resultObj.deniedPresent[i];
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
result = 0
}
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
var deniedAlwaysPermission = resultObj.deniedAlways[i];
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
result = -1
}
resolve(result);
},
function(error) {
console.log('result error: ' + error.message)
resolve({
code: error.code,
message: error.message
});
}
);
});
}
function gotoAppPermissionSetting() {
if (permission.isIOS) {
var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL");
var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2);
plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2);
} else {
var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri);
mainActivity.startActivity(intent);
}
}
const permission = {
get isIOS(){
return typeof isIOS === 'boolean' ? isIOS : (isIOS = uni.getSystemInfoSync().platform === 'ios')
},
requestAndroid: requestAndroid,
gotoAppSetting: gotoAppPermissionSetting
}
export default permission

136
common/uni-nvue.css

@ -0,0 +1,136 @@
/* #ifndef APP-PLUS-NVUE */
page {
min-height: 100%;
height: auto;
}
/* #endif */
/* 解决头条小程序字体图标不显示问题,因为头条运行时自动插入了span标签,且有全局字体 */
/* #ifdef MP-TOUTIAO */
/* text :not(view) {
font-family: uniicons;
} */
/* #endif */
.uni-icon {
font-family: uniicons;
font-weight: normal;
}
.uni-container {
padding: 15px;
background-color: #f8f8f8;
}
.uni-header-logo {
/* #ifdef H5 */
display: flex;
/* #endif */
padding: 15px 15px;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 10rpx;
}
.uni-header-image {
width: 80px;
height: 80px;
}
.uni-hello-text {
margin-bottom: 20px;
}
.hello-text {
color: #7A7E83;
font-size: 14px;
line-height: 20px;
}
.hello-link {
color: #7A7E83;
font-size: 14px;
line-height: 20px;
}
.uni-panel {
margin-bottom: 12px;
}
.uni-panel-h {
/* #ifdef H5 */
display: flex;
/* #endif */
background-color: #ffffff;
flex-direction: row !important;
/* justify-content: space-between !important; */
align-items: center !important;
padding: 12px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
/*
.uni-panel-h:active {
background-color: #f8f8f8;
}
*/
.uni-panel-h-on {
background-color: #f0f0f0;
}
.uni-panel-text {
flex: 1;
color: #000000;
font-size: 14px;
font-weight: normal;
}
.uni-panel-icon {
margin-left: 15px;
color: #999999;
font-size: 14px;
font-weight: normal;
transform: rotate(0deg);
transition-duration: 0s;
transition-property: transform;
}
.uni-panel-icon-on {
transform: rotate(180deg);
}
.uni-navigate-item {
/* #ifdef H5 */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
background-color: #FFFFFF;
border-top-style: solid;
border-top-color: #f0f0f0;
border-top-width: 1px;
padding: 12px;
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.uni-navigate-item:active {
background-color: #f8f8f8;
}
.uni-navigate-text {
flex: 1;
color: #000000;
font-size: 14px;
font-weight: normal;
}
.uni-navigate-icon {
margin-left: 15px;
color: #999999;
font-size: 14px;
font-weight: normal;
}

1685
common/uni.scss

File diff suppressed because it is too large Load Diff

154
common/util.js

@ -0,0 +1,154 @@
import moment from 'moment'
export function formatTime(time) {
if (typeof time !== 'number' || time < 0) {
return time
}
var hour = parseInt(time / 3600)
time = time % 3600
var minute = parseInt(time / 60)
time = time % 60
var second = time
return ([hour, minute, second]).map(function(n) {
n = n.toString()
return n[1] ? n : '0' + n
}).join(':')
}
export function formatLocation(longitude, latitude) {
if (typeof longitude === 'string' && typeof latitude === 'string') {
longitude = parseFloat(longitude)
latitude = parseFloat(latitude)
}
longitude = longitude.toFixed(2)
latitude = latitude.toFixed(2)
return {
longitude: longitude.toString().split('.'),
latitude: latitude.toString().split('.')
}
}
export var dateUtils = {
UNITS: {
'年': 31557600000,
'月': 2629800000,
'天': 86400000,
'小时': 3600000,
'分钟': 60000,
'秒': 1000
},
humanize: function(milliseconds) {
var humanize = '';
for (var key in this.UNITS) {
if (milliseconds >= this.UNITS[key]) {
humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
break;
}
}
return humanize || '刚刚';
},
format: function(dateStr) {
var date = this.parse(dateStr)
var diff = Date.now() - date.getTime();
if (diff < this.UNITS['天']) {
return this.humanize(diff);
}
var _format = function(number) {
return (number < 10 ? ('0' + number) : number);
};
return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +
_format(date.getHours()) + ':' + _format(date.getMinutes());
},
parse: function(str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串,转化为一个Date对象
var a = str.split(/[^0-9]/);
return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
}
};
export function now(format) {
return moment().format(format)
}
export function getDictLabel(dicts, value) {
if (!value) {
return ''
}
if (!dicts || !dicts.length) {
return ''
}
const obj = dicts.find(item => item.dictValue === value)
if (!obj) {
return ''
}
return obj.dictLabel;
}
export function getDictLabelByArray(dicts, value) {
if (!value) {
return ''
}
if (!dicts || !dicts.length) {
return ''
}
const arr = dicts.filter(item => value.split(',').indexOf(item.dictValue) > -1)
if (arr.length === 0) {
return ''
}
return arr.map(item => item.dictLabel).join('、');
}
export const formatTimeText = (seconds) => {
if (!seconds) {
return ''
}
if (seconds < 0) {
return formatTimeText(-seconds);
}
// 秒
if (seconds < 60) {
return seconds + '秒'
}
// 分钟
if (seconds < 3600) {
return `${Math.floor(seconds / 60)}${seconds % 60}`
}
// 小时
if (seconds < 86400) {
const remainder = seconds % 3600;
return `${Math.floor(seconds / 3600)}${parseInt(seconds % 3600 / 60)}`
}
// 天
const remainder = seconds % 86400;
return `${Math.floor(seconds / 86400)}${parseInt(seconds % 86400 / 3600)}`
}
export const getFileType = (fileName) => {
if (!fileName || fileName.indexOf('.') === -1) {
return 'txt';
}
const fileSuffix = fileName.substr(fileName.lastIndexOf('.') + 1).toLowerCase();
const imgSuffix = ['png', 'jpg', 'jpeg', 'gif'];
if (imgSuffix.indexOf(fileSuffix) > -1) {
return 'img'
}
if (fileSuffix === 'doc' || fileSuffix === 'docx' ) {
return 'doc'
}
if (fileSuffix === 'xls' || fileSuffix === 'xlsx' ) {
return 'xls'
}
if (fileSuffix === 'pdf' ) {
return 'pdf'
}
if (fileSuffix === 'mp3') {
return 'mp3'
}
if (fileSuffix === 'mp4') {
return 'mp4'
}
return 'txt';
}

31
components/empty.vue

@ -0,0 +1,31 @@
<template>
<view class="empty-container">
<image src="/static/empty.png"></image>
<view class="empty-description">{{ description }}</view>
</view>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps({
description: {
type: String,
default: '无数据'
}
})
</script>
<style lang="scss" scoped>
.empty-container {
text-align: center;
padding: 40rpx 0;
image {
width: 200rpx;
height: 200rpx;
}
.empty-description {
color: #999;
font-size: 12px;
}
}
</style>

91
components/file-list.vue

@ -0,0 +1,91 @@
<template>
<view class="flex gap-8 wrap">
<view v-for="(file, index) in files" class="col-8 file-item">
<view class="file flex wrap flex-col items-center" v-if="getFileType(file.fileName) !== 'img'">
<view class="mt-10">
<image :src="`/static/icon/${ getFileType(file.fileName) }.png`" mode="widthFix" style="width: 78rpx"></image>
</view>
<view class="uni-text-nowrap filename">{{ file.fileName }}</view>
</view>
<net-image :filepath="file.filePath" v-else class="img" />
</view>
</view>
<!-- <view class="file-preview-wrapper" v-if="previewFlag" @tap="previewFlag = false" >
<view class="file-preview-container">
<template v-if="activeFile.type.indexOf('image') > -1">
<image :src="imgSrc" mode="widthFix"></image>
</template>
<template v-else-if="activeFile.type.indexOf('audio') > -1">
<view class="audio-container flex column">
<view class="audio-title mb-20">{{ activeFile.orgiinFilename }}</view>
<view @tap.stop="playFlag = !playFlag" class="flex center">
<fui-icon name="suspend" color="#fff" :size="120" v-if="!playFlag"></fui-icon>
<fui-icon name="play" color="#fff" :size="120" v-else></fui-icon>
</view>
</view>
</template>
<template v-else-if="activeFile.type.indexOf('video') > -1">
<video :src="videoSrc" controls @tap.stop style="width: 100vw; height: 60vh"></video>
</template>
<template v-else-if="activeFile.type.indexOf('word') > -1 ||
activeFile.type.indexOf('excel') > -1 || activeFile.type.indexOf('spreadsheetml.sheet') > -1">
<view class="docx-container">
<view class="docx-content">
<rich-text :nodes="htmlString"></rich-text>
</view>
</view>
</template>
<template v-else>
<view class="unsupported flex column">
<view class="mb-20">{{ activeFile.orgiinFilename }}</view>
<view class="text-center tips mb-20">暂不支持该文件格式的预览</view>
</view>
</template>
</view>
<view class="file-dot-container flex gap" v-if="files.length > 1">
<view v-for="(file, index) in files" class="dot" :active="files.indexOf(activeFile) === index"></view>
</view>
<view class="tools flex gap-6">
<view class="tool-btn" @tap.stop="download">
<fui-icon name="dropdown" color="#fff" :size="40"></fui-icon>
</view>
<template v-if="files.length > 1">
<view class="tool-btn" @tap.stop="prev">
<fui-icon name="arrowleft" color="#fff" :size="40"></fui-icon>
</view>
<view class="tool-btn" @tap.stop="next">
<fui-icon name="arrowright" color="#fff" :size="40"></fui-icon>
</view>
</template>
</view>
</view> -->
</template>
<script setup>
import { ref, defineProps, defineEmits, onMounted } from 'vue';
import { getFileType } from '@/common/util';
defineProps({
files: {
type: Array,
default: []
}
})
</script>
<style lang="scss">
.file-item {
width: 178rpx;
height: 178rpx;
background: #F3FAFF;
overflow: hidden;
.filename {
font-size: 12px;
}
.img {
width: 100%;
height: 100%;
}
}
</style>

60
components/filter-radio.vue

@ -0,0 +1,60 @@
<template>
<view class="radio-group-container">
<view :class="modelValue === item[prop.value]? 'radio-group-item active' : 'radio-group-item'" v-for="item in data" @tap="change(item[prop.value])">{{ item[prop.text] }}</view>
</view>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
data: {
type: Array,
default: []
},
modelValue: {
type: String
},
prop: {
type: Object,
default: {
text: 'text',
value: 'value'
}
}
})
const emit = defineEmits(['update:modelValue', 'change'])
function change(val) {
if (props.modelValue === val) {
emit('update:modelValue', '')
} else {
emit('update:modelValue', val)
}
emit('change')
}
</script>
<style lang="scss" scoped>
.radio-group-container {
display: flex;
flex-wrap: wrap;
gap: 24rpx;
margin-bottom: 48rpx;
.radio-group-item {
height: 60rpx;
line-height: 60rpx;
width: 212rpx;
text-align: center;
background: #F6F6F6;
border-radius: 27rpx;
font-size: 15px;
border: 1px solid #F6F6F6;
&.active {
background: #E7F2FF;
border-color: var(--primary-color);
}
}
}
</style>

134
components/inspection-task-data-picker.vue

@ -0,0 +1,134 @@
<template>
<view class="inspection-task-input" @tap="show = true">
<view>
<view v-if="text">{{ text }}</view>
<view class="inspection-task-input_placeholder" v-else>请关联督察任务</view>
</view>
<view class="inspection-task-input_icon">
<uni-icons type="down" v-if="!text"></uni-icons>
</view>
</view>
<view class="inspection-task-dialog" v-if="show">
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="flex gap-8 search-item-conent">
<uni-easyinput prefixIcon="search" type="text" placeholder="搜索" :inputBorder="false" v-model="query.taskName" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag">
<view class="flex gap-8 search-item-conent">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="cancleSearch">取消</view>
</view>
</view>
<view class="inspection-task-container">
<view v-for="item in tasks" class="inspection-task-item" @tap="handleSelect(item)">
<view class="row">
<view class="col col-24">
<view class="label">任务名称</view>
<view class="content">{{ item.taskName }}</view>
</view>
<view class="col col-24">
<view class="label">参与人员</view>
<view class="content">{{ item.persons }}</view>
</view>
<view class="col col-24">
<view class="label">督察单位</view>
<view class="content">{{ item.supDepartName }}</view>
</view>
<view class="col col-24">
<view class="label">任务类型</view>
<view class="content">{{ item.supervisionType }}</view>
</view>
<view class="col col-24">
<view class="label">任务内容</view>
<view class="content">{{ item.taskContent }}</view>
</view>
<view class="col col-24">
<view class="label">督察时间</view>
<view class="content">{{ item.beginTime }} ~ {{ item.endTime }}</view>
</view>
</view>
</view>
</view>
<view class="footer col-24">
<button type="primary" @click="handleAdd">创建督察任务</button>
</view>
</view>
</template>
<script setup>
import { ref, defineProps, defineEmits, onMounted } from 'vue';
import { listInspection } from '@/api/inspection'
defineProps({
modelValue: {
type: Object
}
})
const emit = defineEmits(['update:modelValue']);
const show = ref(false);
const query = ref({});
const searchFlag = ref(false)
const tasks = ref([]);
const text = ref('')
function cancleSearch() {
searchFlag.value = false
}
onMounted(() => {
listInspection(query.value).then(data => {
tasks.value = data.records
})
})
function handleSelect(item) {
text.value = item.taskName;
emit('update:modelValue', item.id);
show.value = false
}
</script>
<style lang="scss">
.inspection-task-input {
width: 100%;
display: flex;
justify-content: space-between;
padding-right: 10px;
.inspection-task-input_placeholder {
font-size: 12px;
color: grey;
padding-left: 6px;
}
.inspection-task-input_icon {
.uni-icons {
color: #666 !important;
}
}
}
.inspection-task-dialog {
background-color: #fff;
position: fixed;
top: 0;
/* #ifdef H5 */
top: 44px;
/* #endif */
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
}
.inspection-task-container {
padding: 0 24rpx;
.inspection-task-item {
padding: 24rpx 0;
box-shadow: inset 0 -1px 0 0 #DDDDDD;
}
}
</style>

110
components/modal.vue

@ -0,0 +1,110 @@
<template>
<view class="wrapper" v-if="show">
<view class="modal-container">
<view class="modal-body">
<view class="model-title">{{ title }}</view>
<view>
<uni-forms ref="formRef" :modelValue="formData" :rules="rules">
<uni-forms-item name="value">
<uni-easyinput type="textarea" :placeholder="placeholderText" v-model="formData.value" />
</uni-forms-item>
</uni-forms>
</view>
</view>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="close">取消</button>
<button :type="confirmType" @tap="confirm" class="col-12">{{ confirmText }}</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, defineProps, defineEmits, defineExpose } from 'vue'
const props = defineProps({
title: {
type: String,
default: '温馨提示'
},
placeholderText: {
type: String,
default: ''
},
confirmText: {
type: String,
default: '提交'
},
confirmType: {
type: String,
default: 'primary'
}
})
const emit = defineEmits(['confirm'])
const formData = ref({});
const rules = {
value: {
rules: [{
required: true,
errorMessage: props.placeholderText,
}]
},
}
const formRef = ref()
const show = ref(false)
function confirm() {
formRef.value.validate().then(res => {
emit('confirm', formData.value.value)
close()
})
}
function open() {
show.value = true
}
function close() {
show.value = false
}
defineExpose({
open,
close
});
</script>
<style lang="scss" scoped>
.wrapper {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .6);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
.modal-container {
background-color: #fff;
border-radius: 16rpx;
width: 80vw;
.modal-body {
padding: 24rpx 24rpx 0 24rpx;
.model-title {
font-size: 18px;
font-weight: bold;
line-height: 50rpx;
text-align: center;
margin-bottom: 12rpx;
}
}
.footer {
position: static;
padding-top: 0;
}
}
}
</style>

36
components/n-button.vue

@ -0,0 +1,36 @@
<template>
<button :type="type" :plain="plain" :class="`button ${type}`">{{ label }}</button>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps({
label: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
plain: {
type: Boolean,
default: true
}
})
</script>
<style lang="scss" scoped>
.button {
font-size: 26rpx;
border-radius: 4rpx;
padding-left: 24rpx;
padding-right: 24rpx;
&.danger {
background-color: var(--danger-color);
border-color: var(--danger-color);
color: #fff;
}
}
</style>

420
components/negative.vue

@ -0,0 +1,420 @@
<template>
<view class="wrapper">
<template v-if="!loading">
<view class="header">
<view class="flex">
<view v-for="(item, index) in steps" class="step text-center" :active="index == activeIndex" :completed="index < activeIndex">
<text>{{ index + 1 }}</text>
<text>{{ item }}</text>
</view>
</view>
<view class="timer" v-if="!remainingDuration || remainingDuration >= 0">
<view class="flex between mb-8">
<text>剩余处理时间</text>
<text>{{ formatTimeText(remainingDuration) }}</text>
</view>
<!-- <view class="progress">
<view class="progress-action" :style="{width: `${percentage}%`}" :type="getType()"></view>
</view> -->
</view>
<view class="timer-danger flex between" v-else>
<text>超时</text>
<text>{{ formatTimeText(remainingDuration) }}</text>
</view>
</view>
<tabs :options="tabOptions" :labelFull="true" v-model="current" />
<template v-if="current === 'info'">
<view class="container">
<view class="h1">问题信息</view>
<view class="row" style="--label-width: 170rpx">
<view class="col col-24">
<view class="label">源头编号</view>
<view class="content">{{ negative.originId }}</view>
</view>
<view class="col col-24">
<view class="label">问题发现时间</view>
<view class="content">{{ negative.discoveryTime }}</view>
</view>
<view class="col col-24" v-if="negative.happenTime">
<view class="label">问题发生时间</view>
<view class="content">{{ negative.happenTime }}</view>
</view>
<view class="col col-24">
<view class="label">问题来源</view>
<view class="content">{{ negative.problemSources }}</view>
</view>
<view class="col col-24">
<view class="label">业务类别</view>
<view class="content">{{ negative.businessTypeName }}</view>
</view>
<view class="col col-12" v-if="negative.responderName">
<view class="label">投诉反映人</view>
<view class="content">{{ negative.responderName }}</view>
</view>
<view class="col col-12" v-if="negative.responderName">
<view class="label">联系电话</view>
<view class="content">{{ negative.contactPhone }}</view>
</view>
<view class="col col-24">
<view class="label">涉嫌问题</view>
<view class="content">{{ getDictLabelByArray(suspectProblems, negative.involveProblem) || '/' }}</view>
</view>
<view class="col col-24">
<view class="label">涉及单位</view>
<view class="content">{{ negative.involveDepartName }}</view>
</view>
<view class="col col-24">
<view class="label" style="text-align: left;">事情简要描述</view>
</view>
<view class="col col-24 c">{{ negative.thingDesc }}</view>
</view>
</view>
<view class="container" v-if="negative.checkStatusName">
<view class="h1">核查办理</view>
<view class="row" style="--label-width: 170rpx">
<view class="col col-12">
<view class="label">核查情况</view>
<view class="content">{{ negative.checkStatusName }}</view>
</view>
<view class="col col-12" v-if="negative.isRectifyName">
<view class="label">是否已整改</view>
<view class="content">{{ negative.isRectifyName }}</view>
</view>
<view class="col col-12" v-if="negative.happenTime">
<view class="label">追责对象</view>
<view class="content">{{ getDictLabel(accountabilityTarget, negative.accountabilityTarget) }}</view>
</view>
<view class="col col-12" v-if="negative.rectifyRestrictionDays">
<view class="label">整改限制</view>
<view class="content">{{ negative.rectifyRestrictionDays }}</view>
</view>
<view class="col col-24">
<view class="label" style="text-align: left;">问题核查情况</view>
</view>
<view class="col col-24 c">{{ negative.checkStatusDesc }}</view>
<view class="col col-24" v-if="negative.rectifyDesc">
<view class="label" style="text-align: left;">问题整改情况</view>
</view>
<view class="col col-24 c" v-if="negative.rectifyDesc">{{ negative.rectifyDesc }}</view>
<view class="col col-24" v-if="negative.unrectifyReason">
<view class="label" style="text-align: left;">问题未整改原因</view>
</view>
<view class="col col-24 c" v-if="negative.rectifyDesc">{{ negative.unrectifyReason }}</view>
</view>
</view>
<view class="container" v-if="negative.checkStatusName" v-for="(item, index) in negative.blames">
<view class="h1">涉及人员{{ index + 1 }}</view>
<view class="row" style="--label-width: 170rpx">
<view class="col col-12">
<view class="label">姓名</view>
<view class="content">{{ item.blameName }}</view>
</view>
<view class="col col-12">
<view class="label">警号</view>
<view class="content">{{ item.blameEmpNo }}</view>
</view>
<view class="col col-24">
<view class="label">身份证</view>
<view class="content">{{ item.blameIdCode }}</view>
</view>
<view class="col col-24">
<view class="label">人员属性</view>
<view class="content">{{ getDictLabel(personTypes, item.ivPersonTypeCode) }}</view>
</view>
<view class="col col-24" v-for="(problem, index) in item.problems">
<view class="label">问题类型{{ index + 1 }}</view>
<view class="content">{{ problem.oneLevelContent }} / {{ problem.twoLevelContent }} / {{ problem.threeLevelContent }}</view>
</view>
<template v-if="negative.checkStatus === '1' || negative.checkStatus === '2'">
<view class="col col-12" v-if="item.responsibilityTypeName">
<view class="label">责任类别</view>
<view class="content">{{ item.responsibilityTypeName }}</view>
</view>
<view class="col col-12" >
<view class="label">主观方面</view>
<view class="content">{{ item.subjectiveAspectName }}</view>
</view>
<view class="col col-12" v-if="item.protectRightsName">
<view class="label">维权容错</view>
<view class="content">{{ item.protectRightsName }}</view>
</view>
<view class="col col-12" v-if="item.superviseMeasuresName">
<view class="label">督察措施</view>
<view class="content">{{ item.superviseMeasuresName }}</view>
</view>
<view class="col col-24">
<view class="label">处置结果</view>
<view class="content">{{ item.handleResultName }} {{ item.handleResultNameOther }}</view>
</view>
</template>
</view>
<view class="h1">涉及领导</view>
<view class="row" style="--label-width: 180rpx">
<view class="col col-12">
<view class="label">领导姓名</view>
<view class="content">{{ item.leadName }}</view>
</view>
<view class="col col-24">
<view class="label">身份证</view>
<view class="content">{{ item.leadIdCode }}</view>
</view>
<template v-if="negative.checkStatus === '1' || negative.checkStatus === '2'">
<view class="col col-12">
<view class="label">责任类别</view>
<view class="content">{{ item.responsibilityTypeName }}</view>
</view>
<view class="col col-24">
<view class="label">处置结果</view>
<view class="content">{{ item.leadHandleResultName }} {{ item.leadHandleResultNameOther }}</view>
</view>
</template>
</view>
</view>
<view class="container" v-if="negative.files?.length">
<view class="h1">办结佐证材料</view>
<file-list :files="negative.files" />
</view>
</template>
<template v-else>
<view class="container">
<!-- <view class="action-history-header">
<text>总耗时</text>
<text class="ml-8">{{ getTotalTime() }}</text>
</view> -->
<view class="body" >
<view v-for="(item, index) in actionHistory" class="action-history">
<view class="action-history-info">
<text class="time mr-12">{{ item.crtTime }}</text>
<text class="mr-4">{{ item.departName }}</text>
<text class="mr-12">{{ item.crtName }}</text>
<text class="primary uni-bold">{{ item.actionName }}</text>
</view>
<view class="flex-inline gap action-history-time">
<text class="info mr-12" v-if="index > 0">用时</text>
<text class="primary" v-if="index > 0">{{ getTime(index) }}</text>
</view>
</view>
</view>
</view>
</template>
</template>
<template v-else>
<view class="header">
<view class="flex">
<view v-for="(item, index) in steps" class="step text-center">
<text>{{ index + 1 }}</text>
<text>{{ item }}</text>
</view>
</view>
<view class="timer">
<view class="flex mb-8">
<text>剩余处理时间 0</text>
</view>
</view>
</view>
<tabs :options="tabOptions" :labelFull="true" v-model="current" />
<view class="container">
<view class="h1">问题信息</view>
<view class="row" style="--label-width: 170rpx">
<view class="col col-24">
<view class="label">源头编号</view>
<view class="content">
<skeleton width="50%" />
</view>
</view>
<view class="col col-24">
<view class="label">问题发现时间</view>
<view class="content"><skeleton width="50%" /></view>
</view>
<view class="col col-24">
<view class="label">问题来源</view>
<view class="content"><skeleton width="50%" /></view>
</view>
<view class="col col-24">
<view class="label">业务类别</view>
<view class="content"><skeleton width="50%" /></view>
</view>
<view class="col col-24">
<view class="label">涉嫌问题</view>
<view class="content"><skeleton /></view>
</view>
<view class="col col-24">
<view class="label">涉及单位</view>
<view class="content"><skeleton width="50%" /></view>
</view>
<view class="col col-24">
<view class="label" style="text-align: left;">事情简要描述</view>
</view>
<skeleton width="100%" height="120rpx" />
</view>
</view>
</template>
</view>
</template>
<script setup>
import moment from 'moment'
import { ref, defineProps, watch } from 'vue'
import { formatTimeText, getDictLabel, getDictLabelByArray } from '@/common/util'
import { getDictOptions } from '@/common/dict'
const personTypes = getDictOptions('personType');
const suspectProblems = getDictOptions('suspectProblem');
const accountabilityTarget = getDictOptions('accountabilityTarget');
const steps = ['问题签收', '核查办理', '办结审批', '认定办结'];
const activeIndex = ref(1)
const tabOptions = [
{
text: '问题详情',
value: 'info'
},
{
text: '流转记录',
value: 'flow'
}
]
const current = ref('info')
const props = defineProps({
data: {
type: Object,
default: {}
},
loading: {
type: Boolean,
default: false
}
});
const negative = ref({})
const remainingDuration = ref(0)
const actionHistory = ref([])
watch(() => props.data, (data) => {
negative.value = data.negative
remainingDuration.value = data.remainingDuration
actionHistory.value = data.actionHistory
})
function getTime(index) {
console.log(moment(actionHistory.value[index].crtTime, 'YYYY-MM-DD HH:mm:ss'))
return formatTimeText(moment(actionHistory.value[index].crtTime, 'YYYY-MM-DD HH:mm:ss').diff(moment(actionHistory.value[index - 1].crtTime, 'YYYY-MM-DD HH:mm:ss'), 'seconds'))
}
</script>
<style lang="scss" scoped>
.header {
background-color: var(--primary-color);
}
.step {
--background-color: #004CB5;
width: 25%;
color: #ACB7FF;
height: 40rpx;
line-height: 38rpx;
border-top: 1rpx solid #4B60E4;
border-bottom: 1rpx solid #4B60E4;
box-sizing: border-box;
position: relative;
background-color: var(--background-color);
text-align: center;
font-size: 12px;
&:before {
display: block;
content: '';
height: 25.87rpx;
width: 25.87rpx;
position: absolute;
top: 5.5rpx;
right: -13.43rpx;
border-top: 1rpx solid #4B60E4;
border-right: 1rpx solid #4B60E4;
transform: rotate(45deg);
z-index: 1;
background-color: var(--background-color);;
}
&:last-child::before {
display: none;
}
&[active=true] {
--background-color: #f40000;
border-color: #FF7474;
color: #fff;
&:before {
border-color: #FF7474;
}
}
&[completed=true] {
color: #fff;
}
}
.timer {
padding: 12px;
}
.timer-danger {
background: linear-gradient( 180deg, #FF5C37 0%, #E03021 100%);
color: #fff;
height: 45px;
line-height: 45px;
margin-top: 10px;
padding: 0 10px;
}
.h1 {
font-weight: 700;
margin-bottom: 24rpx;
}
.c {
background: #E7F2FF;
padding: 24rpx;
}
.container {
background-color: #fff;
margin-bottom: 12rpx;
}
.wrapper {
background-color: #f5f5f5;
}
.action-history-header {
height: 52rpx;
line-height: 52rpx;
margin-bottom: 24rpx;
}
.action-history {
margin-bottom: 16rpx;
.action-history-info {
margin-bottom: 16rpx;
padding-left: 36rpx;
position: relative;
display: flex;
align-items: center;
&:before {
content: '';
display: block;
position: absolute;
left: 0;
top: 50%;
transform: translate(-50%, -50%);
width: 20rpx;
height: 20rpx;
background-color: #bfbfbf;
border: 1px solid #f1fff1;
border-radius: 50%;
}
.time {
color: #999;
font-size: 12px;
}
}
.action-history-time {
height: 56rpx;
line-height: 56rpx;
padding-left: 32rpx;
padding-right: 40rpx;
background: #F5F6FF;
border-left: 2px solid #00D050;
border-radius: 0 24rpx 24rpx 0;
}
}
</style>

30
components/net-image.vue

@ -0,0 +1,30 @@
<template>
<image :src="src"></image>
</template>
<script setup>
import { onMounted, ref, defineProps } from 'vue';
import store from '@/store'
import { getFileBase64 } from '@/api/file'
const props = defineProps({
filepath: {
type: String
}
});
const src = ref('');
console.log('filepath', props.filepath)
onMounted(() => {
getFileBase64(props.filepath).then(data => {
src.value = data;
})
})
</script>
<style scoped>
image {
background-color: #eee;
}
</style>

214
components/police-picker.vue

@ -0,0 +1,214 @@
<template>
<view class="police-c">
<view class="police-item-tag-box">
<view v-for="item in activePolices" class="police-item-tag police-item-tag_small">{{ item.name.substring(item.name.length - 2) }}</view>
</view>
<button size="small" @tap="show = true" class="add-btn">
<uni-icons type="plusempty" size="20"></uni-icons>
</button>
</view>
<view class="police-picker-container" v-if="show">
<view class="header">
<view class="hidden-btn" @tap="show = false">
<uni-icons type="left" size="20"></uni-icons>
</view>
<uni-easyinput prefixIcon="search" type="text" placeholder="搜索" v-model="query.name" />
</view>
<!-- <uni-data-picker :localdata="departs" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="query.departId" /> -->
<view class="police-container">
<view class="police-item" v-for="item in polices" @tap="handleChange(item)">
<checkbox :checked="activePolices.findIndex(police => item.empNo === police.empNo) !== -1" @tap.stop="handleChange(item)" />
<view class="police-item-tag">{{ item.name.substring(item.name.length - 2) }}</view>
<view>{{ item.name }}</view>
</view>
</view>
<view class="footer">
<view class="footer-left">
<view>已选择{{ activePolices.length }}</view>
<view class="police-item-tag-container">
<view class="police-item-tag-box">
<view v-for="item in activePolices" class="police-item-tag police-item-tag_small">{{ item.name.substring(item.name.length - 2) }}</view>
</view>
</view>
</view>
<button type="primary" @tap="show = false">确定</button>
</view>
</view>
</template>
<script setup>
import { onMounted, ref, watch, defineProps, defineEmits } from 'vue';
import { listPolice } from '@/api/police'
import {
departTree
} from '@/api/depart'
const props = defineProps({
modelValue: {
type: Array,
default: []
},
departId: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
const show = ref(false);
const polices = ref([]);
const query = ref({
current: 1,
size: 16,
departBranch: true,
})
watch(() => props.modelValue, (val) => {
if (val) {
polices.value = val
} else {
polices.value = []
}
})
watch(() => props.departId, (val) => {
query.value.departId = val;
getPolice()
})
const activePolices = ref(props.modelValue || []);
function handleChange(police) {
const index = activePolices.value.findIndex(item => item.empNo === police.empNo);
if (index === -1) {
activePolices.value.push({
name: police.name,
empNo: police.empNo,
})
emit('update:modelValue', activePolices.value)
} else {
activePolices.value.splice(index, 1)
emit('update:modelValue', activePolices.value)
}
}
const departs = ref([])
const departId = ref('')
onMounted(() => {
getPolice()
departTree().then(data => {
departs.value = data[0].children
})
})
watch(() => query.value.name, () => {
getPolice()
})
function getPolice() {
listPolice(query.value).then(data => {
polices.value = data.records
})
}
</script>
<style lang="scss" scoped>
.police-picker-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #f5f5f5;
z-index: 9999;
.header {
display: flex;
padding: 24rpx;
padding-left: 0;
display: flex;
align-items: center;
gap: 0 12rpx;
background-color: #fff;
margin-bottom: 12rpx;
position: relative;
.hidden-btn {
height: 37px;
width: 37px;
display: flex;
align-items: center;
justify-content: center;
}
}
.police-item {
background-color: #fff;
height: 88rpx;
line-height: 88rpx;
display: flex;
gap: 0 24rpx;
align-items: center;
padding-left: 24rpx;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: #fff;
button {
margin: 0;
}
.footer-left {
display: flex;
align-items: center;
width: calc(100% - 64px);
}
}
}
.police-container {
max-height: 82vh;
overflow: auto;
}
.police-item-tag {
background-color: var(--primary-color);
color: #fff;
height: 72rpx;
width: 72rpx;
line-height: 72rpx;
text-align: center;
border-radius: 10rpx;
&_small {
height: 52rpx;
width: 52rpx;
line-height: 52rpx;
font-size: 12px;
}
}
.add-btn {
height: 52rpx;
width: 52rpx;
line-height: 52rpx;
padding: 0;
}
.police-item-tag-container {
width: calc(100% - 200rpx);
overflow: hidden;
}
.police-item-tag-box {
display: flex;
gap: 0 12rpx;
width: max-content;
}
.police-c {
display: flex;
gap: 0 12rpx;
}
</style>

21
components/problem-picker.vue

@ -0,0 +1,21 @@
<template>
<uni-data-picker :localdata="localdata" placeholder="请选择问题类型" :border="false" :map="{text:'name', value: 'id'}" />
</template>
<script setup>
import { onMounted, ref} from 'vue'
import { problemTypeTree } from '@/api/problemType'
const localdata = ref([]);
onMounted(() => {
problemTypeTree().then(data => {
localdata.value = data;
})
})
</script>
<style lang="scss" scoped>
</style>

52
components/skeleton.vue

@ -0,0 +1,52 @@
<template>
<view class="skeleton" :style="{width, height}"></view>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps({
width: {
type: String,
default: ''
},
height: {
type: String,
default: ''
}
})
</script>
<style lang="scss" scoped>
.skeleton {
height: 100%;
background: #f0f0f0;
position: relative;
border-radius: 24rpx;
display: block;
}
.skeleton::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.5),
transparent
);
animation: skeleton-shimmer 1.5s infinite;
}
@keyframes skeleton-shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
</style>

87
components/tabs.vue

@ -0,0 +1,87 @@
<template>
<view class="tabs" ref="tab">
<view v-for="item in options" :class="modelValue === item.value ? 'tabs-item active' : 'tabs-item'" @tap="changeTab(item.value)">
<view class="tabs-item-label" :style="{width: labelFull ? '100%' : 'auto'}">{{ item.text }}</view>
</view>
<view class="tab-active-bar" :style="{width: `${width}px`, transform: `translateX(${left}px)`}"></view>
</view>
</template>
<script setup>
import { defineProps, defineEmits, nextTick, ref, onMounted } from 'vue'
defineProps({
options: {
type: Array,
default: []
},
modelValue: {
type: String,
default: ''
},
labelFull: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const tab = ref()
const width = ref(0)
const left = ref(0)
function changeTab(value) {
emit('update:modelValue', value);
nextTick(() => {
getActiveBarStyle()
})
}
onMounted(() => {
nextTick(() => {
getActiveBarStyle()
})
})
function getActiveBarStyle() {
const query = uni.createSelectorQuery().in(this);
query
.select(".tabs-item.active>.tabs-item-label")
.boundingClientRect((data) => {
width.value = data.width;
left.value = data.left;
})
.exec();
}
</script>
<style lang="scss" scoped>
.tabs {
display: flex;
box-shadow: inset 0 -1px 0 0 #eee;
position: relative;
.tabs-item {
width: 50%;
display: flex;
justify-content: center;
.tabs-item-label {
text-align: center;
height: 84rpx;
line-height: 84rpx;
color: #333;
}
&.active {
.tabs-item-label {
color: var(--primary-color);
}
}
}
.tab-active-bar {
height: 2px;
position: absolute;
bottom: 0;
transition: all .3s;
background-color: var(--primary-color);
}
}
</style>

182
components/upload.vue

@ -0,0 +1,182 @@
<template>
<view class="upload-container">
<view v-for="(item, index) in files" class="upload-item">
<net-image :filepath="item.filePath" />
<button class="remove-btn" @tap="handleRemove(index)">
<uni-icons type="closeempty" color="#fff" size="18" />
</button>
</view>
<view class="upload-btn upload-item" @tap="chooseImage">
<uni-icons type="plusempty" size="32" color="#162582" />
</view>
</view>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue'
import store from '@/store'
import { pathToBase64 } from 'image-tools'
import {
uploadFileBase64
} from '@/api/file'
import permission from '@/common/permission'
var sourceType = [
['camera'],
['album'],
['camera', 'album']
]
var sizeType = [
['compressed'],
['original'],
['compressed', 'original']
]
const props = defineProps({
modelValue: {
type: Array,
default: []
}
});
const emit = defineEmits(['update:modelValue']);
const files = ref(props.modelValue || [])
async function chooseImage() {
// #ifdef APP-PLUS
// TODO actionsheet
// if (this.sourceTypeIndex !== 2) {
// let status = await this.checkPermission();
// if (status !== 1) {
// return;
// }
// }
// #endif
uni.chooseImage({
sourceType: ['camera'],
sizeType: ['original'],
count: 3,
success: (res) => {
uni.showLoading({
title: '文件上传中, 请稍等'
});
res.tempFiles.forEach(async (file) => {
const base64 = await pathToBase64(file.path);
const filename = file.path.substring(file.path.lastIndexOf('/') + 1)
const data = await uploadFileBase64({
base64,
originalFilename: filename
})
files.value.push({
filePath: data.filePath,
fileName: filename
});
emit('update:modelValue', files.value)
uni.hideLoading();
})
},
fail: (err) => {
console.log("err: ", err);
// #ifdef APP-PLUS
if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
this.checkPermission(err.code);
}
// #endif
// #ifdef MP
if (err.errMsg.indexOf('cancel') !== '-1') {
return;
}
uni.getSetting({
success: (res) => {
let authStatus = false;
switch (this.sourceTypeIndex) {
case 0:
authStatus = res.authSetting['scope.camera'];
break;
case 1:
authStatus = res.authSetting['scope.album'];
break;
case 2:
authStatus = res.authSetting['scope.album'] && res
.authSetting['scope.camera'];
break;
default:
break;
}
if (!authStatus) {
uni.showModal({
title: '授权失败',
content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
}
}
})
// #endif
}
})
}
async function checkPermission(code) {
let type = code ? code - 1 : this.sourceTypeIndex;
let status = await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
'android.permission.READ_EXTERNAL_STORAGE');
if (status === null || status === 1) {
status = 1;
} else {
uni.showModal({
content: "没有开启权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
return status;
}
function handleRemove(index) {
files.value.splice(index, 1)
}
</script>
<style lang="scss" scoped>
.upload-container {
display: flex;
gap: 38rpx;
flex-wrap: wrap;
.upload-item {
height: 178rpx;
width: 178rpx;
position: relative;
image {
width: 100%;
height: 100%;
}
.remove-btn {
height: 46rpx;
width: 46rpx;
line-height: 46rpx;
border-radius: 50%;
position: absolute;
top: -20rpx;
right: -20rpx;
padding: 0;
background-color: #fff;
background-color: $uni-color-error;
}
}
}
.upload-btn {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #162582;
}
</style>

88
hybrid/html/local.html

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>本地网页</title>
<style type="text/css">
.btn {
display: block;
margin: 20px auto;
padding: 5px;
background-color: #007aff;
border: 0;
color: #ffffff;
height: 40px;
width: 200px;
}
.btn-red {
background-color: #dd524d;
}
.btn-yellow {
background-color: #f0ad4e;
}
.desc {
padding: 10px;
color: #999999;
}
</style>
</head>
<body>
<p class="desc">web-view 组件加载本地 html 示例,仅在 App 环境下生效。点击下列按钮,跳转至其它页面。</p>
<div class="btn-list">
<button class="btn" type="button" data-action="navigateTo">navigateTo</button>
<button class="btn" type="button" data-action="redirectTo">redirectTo</button>
<button class="btn" type="button" data-action="navigateBack">navigateBack</button>
<button class="btn" type="button" data-action="reLaunch">reLaunch</button>
<button class="btn" type="button" data-action="switchTab">switchTab</button>
</div>
<p class="desc">网页向应用发送消息。注意:小程序端应用会在此页面后退时接收到消息。</p>
<div class="btn-list">
<button class="btn btn-red" type="button" id="postMessage">postMessage</button>
</div>
<!-- uni 的 SDK -->
<script type="text/javascript" src="https://unpkg.com/@dcloudio/uni-webview-js@0.0.1/index.js"></script>
<script type="text/javascript">
document.addEventListener('UniAppJSBridgeReady', function() {
document.querySelector('.btn-list').addEventListener('click', function(evt) {
var target = evt.target;
if (target.tagName === 'BUTTON') {
var action = target.getAttribute('data-action');
switch (action) {
case 'switchTab':
uni.switchTab({
url: '/pages/tabBar/API/API'
});
break;
case 'reLaunch':
uni.reLaunch({
url: '/pages/tabBar/API/API'
});
break;
case 'navigateBack':
uni.navigateBack({
delta: 1
});
break;
default:
uni[action]({
url: '/pages/component/button/button'
});
break;
}
}
});
document.querySelector("#postMessage").addEventListener('click', function() {
uni.postMessage({
data: {
action: 'message'
}
});
})
});
</script>
</body>
</html>

22
index.html

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<script src="/static/image-resize-3.0.1.min.js"></script>
<script src="/static/quill-1.3.7.min.js"></script>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

11
jest.config.js

@ -0,0 +1,11 @@
module.exports = {
testTimeout: 20000,
reporters: [
'default'
],
watchPathIgnorePatterns: ['/node_modules/', '/dist/', '/.git/'],
moduleFileExtensions: ['js', 'json'],
rootDir: __dirname,
testMatch: ["<rootDir>/pages/**/*test.[jt]s?(x)"],
testPathIgnorePatterns: ['/node_modules/']
}

45
main.js

@ -0,0 +1,45 @@
import App from './App'
import store from './store'
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
Vue.prototype.$store = store
Vue.prototype.$adpid = "1111111111"
Vue.prototype.$backgroundAudioData = {
playing: false,
playTime: 0,
formatedPlayTime: '00:00:00'
}
App.mpType = 'app'
const app = new Vue({
store,
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
createSSRApp
} from 'vue'
import * as Pinia from 'pinia';
import Vuex from "vuex";
export function createApp() {
const app = createSSRApp(App)
app.use(store)
app.use(Pinia.createPinia());
app.config.globalProperties.$adpid = "1111111111"
app.config.globalProperties.$backgroundAudioData = {
playing: false,
playTime: 0,
formatedPlayTime: '00:00:00'
}
return {
app,
Vuex, // 如果 nvue 使用 vuex 的各种map工具方法时,必须 return Vuex
Pinia // 此处必须将 Pinia 返回
}
}
// #endif

200
manifest.json

@ -0,0 +1,200 @@
{
"name" : "supervision-app",
"appid" : "__UNI__3A60B92",
"description" : "应用描述",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
"usingComponents" : true,
"nvueCompiler" : "uni-app",
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"nvueLaunchMode" : "fast",
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"compatible" : {
//uni-app
"ignoreVersion" : true
},
"modules" : {
"OAuth" : {},
"Payment" : {},
"Push" : {},
"Share" : {},
"Speech" : {},
"VideoPlayer" : {},
"LivePusher" : {}
},
"distribute" : {
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.READ_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
]
},
"ios" : {},
"sdkConfigs" : {
"speech" : {
"ifly" : {}
}
},
"orientation" : [ "portrait-primary" ],
"splashscreen" : {
"androidStyle" : "default",
"iosStyle" : "common",
"android" : {
"hdpi" : "static/splash.9.png"
}
}
},
"uniStatistics" : {
"enable" : true
}
},
"quickapp" : {},
"quickapp-native" : {
"icon" : "/static/logo.png",
"package" : "com.example.demo",
"features" : [
{
"name" : "system.clipboard"
}
]
},
"quickapp-webview" : {
"icon" : "/static/logo.png",
"package" : "com.example.demo",
"minPlatformVersion" : 1070,
"versionName" : "1.0.0",
"versionCode" : 100
},
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "演示定位能力"
}
},
"uniStatistics" : {
"enable" : true
}
},
"mp-alipay" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-baidu" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
},
"dynamicLib" : {
"editorLib" : {
"provider" : "swan-editor"
}
}
},
"mp-toutiao" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"mp-jd" : {
"usingComponents" : true,
"uniStatistics" : {
"enable" : true
}
},
"h5" : {
"template" : "template.h5.html",
"router" : {
"mode" : "history",
"base" : ""
},
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : "TKUBZ-D24AF-GJ4JY-JDVM2-IBYKK-KEBCU"
}
}
},
"async" : {
"timeout" : 20000
},
"uniStatistics" : {
"enable" : true
}
},
"vueVersion" : "3",
"mp-kuaishou" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-lark" : {
"uniStatistics" : {
"enable" : true
}
},
"mp-qq" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-huawei" : {
"uniStatistics" : {
"enable" : true
}
},
"quickapp-webview-union" : {
"uniStatistics" : {
"enable" : true
}
},
"uniStatistics" : {
"version" : "2",
"enable" : true
}
}

81
package.json

@ -0,0 +1,81 @@
{
"id": "supervision-app",
"name": "supervision-app",
"displayName": "supervision-app",
"version": "3.4.8",
"description": "",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": "https://github.com/dcloudio/hello-uniapp.git",
"keywords": [],
"author": "",
"license": "MIT",
"homepage": "https://github.com/dcloudio/hello-uniapp#readme",
"dependencies": {
"image-tools": "^1.4.0",
"moment": "^2.30.1"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "uniapp-template-project"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y",
"alipay": "n"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y",
"app-harmony": "u",
"app-uvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
},
"uni-app": {
"scripts": {
}
}
}

215
pages.json

@ -0,0 +1,215 @@
{
"pages": [
// pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "移动督察",
"enablePullDownRefresh": true,
"app-plus": {
"pullToRefresh": {
"support": true,
"color": "#2979ff", //
"style": "circle" //
}
}
}
},
{
"path": "pages/common/camera",
"style": {
"navigationBarTitleText": "随手拍",
"app-plus": {
}
}
},
{
"path": "pages/photo/index",
"style": {
"navigationBarTitleText": "问题照片"
}
},
{
"path": "pages/negative/index",
"style": {
"navigationBarTitleText": "问题清单"
}
},
{
"path": "pages/negative/info",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/negative/action",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/duty/index",
"style": {
"navigationBarTitleText": "值班报备"
}
},
{
"path": "pages/comfort/list",
"style": {
"navigationBarTitleText": "抚慰申请"
}
},
{
"path": "pages/comfort/action",
"style": {
"navigationBarTitleText": "抚慰申请"
}
},
{
"path": "pages/comfort/add",
"style": {
"navigationBarTitleText": "抚慰申请"
}
},
{
"path": "pages/books/index",
"style": {
"navigationBarTitleText": "知识库"
}
},
{
"path": "pages/center/manual",
"style": {
"navigationBarTitleText": "使用手册"
}
},
{
"path": "pages/center/about",
"style": {
"navigationBarTitleText": "关于APP"
}
},
{
"path": "pages/center/feedback",
"style": {
"navigationBarTitleText": "意见反馈"
}
}
],
"subPackages": [
{
"root": "pages/task",
"pages": [{
"path": "index",
"style": {
"navigationBarTitleText": "我的任务"
}
},
{
"path": "testingAlcohol/people",
"style": {
"navigationBarTitleText": "工作日测酒"
}
},
{
"path": "testingAlcohol/info",
"style": {
"navigationBarTitleText": "工作日测酒"
}
},
{
"path": "testingAlcohol/add",
"style": {
"navigationBarTitleText": "测酒情况录入"
}
},
{
"path": "inspection/info",
"style": {
"navigationBarTitleText": "督察任务要求"
}
},
{
"path": "inspection/list",
"style": {
"navigationBarTitleText": "督察任务"
}
},
{
"path": "selfexamination/index",
"style": {
"navigationBarTitleText": "所队自查任务"
}
},
{
"path": "selfexamination/add",
"style": {
"navigationBarTitleText": "所队自查任务"
}
},
{
"path": "selfexamination/list",
"style": {
"navigationBarTitleText": "所队自查任务"
}
},
{
"path": "selfexamination/info",
"style": {
"navigationBarTitleText": "所队自查任务"
}
},
{
"path": "problem/index",
"style": {
"navigationBarTitleText": "问题详情"
}
},
{
"path": "problem/list",
"style": {
"navigationBarTitleText": "问题详情"
}
},
{
"path": "problem/add",
"style": {
"navigationBarTitleText": "新增督察任务"
}
}
]
}
],
"globalStyle": {
"pageOrientation": "portrait",
"navigationBarTitleText": "移动督察",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#0e79f2",
"h5": {
"maxWidth": 1190,
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "#0e79f2"
}
},
"easycom": {
"autoscan": true,
"custom": {
"tabs": "@/components/tabs.vue",
"upload": "@/components/upload.vue",
"police-picker": "@/components/police-picker.vue",
"problem-picker": "@/components/problem-picker.vue",
"inspection-task-data-picker": "@/components/inspection-task-data-picker.vue",
"net-image": "@/components/net-image.vue",
"empty": "@/components/empty.vue",
"skeleton": "@/components/skeleton.vue",
"file-list": "@/components/file-list.vue",
"filter-radio": "@/components/filter-radio.vue",
"negative": "@/components/negative.vue",
"n-button": "@/components/n-button.vue",
"modal": "@/components/modal.vue"
}
}
}

121
pages/books/index.vue

@ -0,0 +1,121 @@
<template>
<view class="books-container" v-if="!searchFlag">
<view class="flex justify-center">
<image src="/static/image/police-badge.png" style="width: 238rpx" mode="widthFix"></image>
</view>
<view class="title">督察知识库</view>
<view class="flex search-box">
<input class="search-input" v-model="query.fileName" placeholder="搜索词" />
<button type="primary" class="search-btn" @tap="search">搜索</button>
</view>
</view>
<view v-else>
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="search-item-conent">
<uni-easyinput prefixIcon="search" type="text" placeholder="搜索" :inputBorder="false" v-model="query.fileName" />
</view>
</view>
<view>
<view class="cancel-btn" @tap="cancleSearch">取消</view>
</view>
</view>
<view class="container">
<view v-for="item in books" class="flex items-center gap-8 file-item">
<view>
<image :src="`/static/icon/${ getFileType(item.fileName) }.png`" mode="widthFix" style="width: 120rpx"></image>
</view>
<view class="filename">{{ item.fileName }}</view>
</view>
<empty description="无知识数据" v-if="books.length === 0" />
</view>
</view>
</template>
<script>
import { getFileType } from '@/common/util';
import { listBook } from '@/api/book'
export default {
data() {
return {
searchFlag: false,
books: [],
query: {
size: 20,
current: 1
}
}
},
watch: {
'query.fileName': function(val) {
if (this.searchFlag && val) {
this.search()
}
},
},
setup() {
return {
getFileType
}
},
onLoad() {
},
methods: {
search() {
if (!this.query.fileName) {
return
}
this.searchFlag = true
listBook(this.query).then(data => {
this.books = data.records
})
},
cancleSearch() {
this.searchFlag = false
this.query.fileName = ''
}
}
}
</script>
<style lang="scss" scoped>
.books-container {
padding: 36rpx;
padding-top: 6vh;
}
.title {
font-size: 24px;
font-weight: bold;
color: #444;
margin-top: 20rpx;
margin-bottom: 60rpx;
text-align: center;
}
.search-box {
border-radius: 8rpx;
border: 1rpx solid #B6D5FE;
padding: 12rpx;
gap: 16rpx;
.search-input {
height: 92rpx;
line-height: 92rpx;
background: rgba(0,0,0,0.04);
border-radius: 8rpx;
flex-grow: 1;
padding-left: 12rpx;
}
.search-btn {
height: 92rpx;
line-height: 92rpx;
margin: 0;
padding: 0 40rpx;
}
}
.file-item {
padding: 12rpx 0;
.filename {
color: #555;
}
}
</style>

31
pages/center/about.vue

@ -0,0 +1,31 @@
<template>
<view class="container">
<view class="flex justify-center">
<image src="/static/image/logo-p.png" style="width: 238rpx" mode="widthFix"></image>
</view>
<view class="h1">移动督察</view>
<view class="version">版本 v6.2.1</view>
</view>
</template>
<script>
</script>
<style lang="scss" scoped>
.container {
padding-top: 10vh;
}
.h1 {
margin-top: 28rpx;
text-align: center;
font-size: 22px;
font-weight: bold;
color: #555;
}
.version {
margin-top: 12rpx;
text-align: center;
font-size: 16px;
color: #777;
}
</style>

65
pages/center/feedback.vue

@ -0,0 +1,65 @@
<template>
<uni-forms ref="form" :modelValue="formData" :rules="rules" style="margin: 12px">
<uni-forms-item label="意见反馈" name="content" label-position="top" required>
<uni-easyinput type="textarea" :input-border="false" placeholder="请输入具体意见反馈" v-model="formData.content" />
</uni-forms-item>
</uni-forms>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="back">取消</button>
<button type="primary" @tap="submit" class="col-12">确定</button>
</view>
</template>
<script>
import {
addFeedback
} from '@/api/feedback.js'
let _this;
export default {
data() {
return {
formData: {
},
rules: {
thingDesc: {
rules: [{
required: true,
errorMessage: '请输入具体意见反馈',
}]
}
},
}
},
onLoad() {
_this = this;
},
methods: {
submit() {
this.$refs.form.validate().then(res => {
addFeedback(this.formData).then(data => {
_this.formData = {}
uni.showToast({
title: '操作成功',
icon: 'none',
duration: 3000
})
uni.navigateBack({
});
})
}).catch(err => {
console.log('表单错误信息:', err, this.formData);
})
},
back() {
uni.navigateBack({
});
}
}
}
</script>
<style>
</style>

21
pages/center/manual.vue

@ -0,0 +1,21 @@
<template>
<view class="container">
<uni-title type="h1" title="工作日测酒"></uni-title>
<view>
<image src="/static/image/1.png" style="height: 200vh" mode="heightFix"></image>
</view>
<uni-title type="h1" title="督察任务"></uni-title>
<view>
<image src="/static/image/2.png" style="height: 200vh" mode="heightFix"></image>
</view>
</view>
</template>
<script>
</script>
<style lang="scss" scoped>
.container {
width: auto;
}
</style>

176
pages/comfort/action.vue

@ -0,0 +1,176 @@
<template>
<view class="wrapper">
<view class="container">
<view class="h1">办理信息</view>
<view class="row" style="--label-width: 160rpx">
<view class="col col-24">
<view class="label">申请时间</view>
<view class="content">{{ comfort.apply?.applyDate }}</view>
</view>
<view class="col col-24">
<view class="label">是否本人</view>
<view class="content">{{ comfort.apply?.isSelf === '1'? '是' : '否' }}</view>
</view>
<view class="col col-12" v-if="comfort.apply?.isSelf === '0'">
<view class="label">代理人姓名</view>
<view class="content">{{ comfort.apply?.agentName }}</view>
</view>
<view class="col col-12" v-if="comfort.apply?.isSelf === '0'">
<view class="label">关系</view>
<view class="content">{{ comfort.apply?.relation }}</view>
</view>
</view>
</view>
<view class="container">
<view class="h1">申请人信息</view>
<view class="row" style="--label-width: 160rpx">
<view class="col col-12">
<view class="label">申请人姓名</view>
<view class="content">{{ comfort.apply?.applicantEmpName }}</view>
</view>
<view class="col col-12">
<view class="label">性别</view>
<view class="content">{{ comfort.apply?.sex }}</view>
</view>
<view class="col col-12">
<view class="label">警号</view>
<view class="content">{{ comfort.person?.empNo }}</view>
</view>
<view class="col col-12">
<view class="label">联系电话</view>
<view class="content">{{ comfort.person?.mobile }}</view>
</view>
<view class="col col-24">
<view class="label">单位</view>
<view class="content">{{ comfort.apply?.departName }}</view>
</view>
<view class="col col-24">
<view class="label">身份证</view>
<view class="content">{{ comfort.person?.idCode }}</view>
</view>
<view class="col col-12">
<view class="label">出生年月</view>
<view class="content">{{ comfort.person?.birthday }}</view>
</view>
<view class="col col-12">
<view class="label">职务</view>
<view class="content">{{ comfort.person?.job || '/' }}</view>
</view>
<view class="col col-12">
<view class="label">警衔</view>
<view class="content">{{ comfort.person?.policeRank || '/' }}</view>
</view>
<view class="col col-12">
<view class="label">文化程度</view>
<view class="content">{{ comfort.person?.levelEducation || '/' }}</view>
</view>
<view class="col col-12">
<view class="label">政治面貌</view>
<view class="content">{{ comfort.person?.politicCountenance || '/' }}</view>
</view>
<view class="col col-24">
<view class="label">开户行</view>
<view class="content">{{ comfort.person?.bankCardAccount }}{{ comfort.person?.bankBranch }}</view>
</view>
<view class="col col-24">
<view class="label">银行账号</view>
<view class="content">{{ comfort.person?.bankCardAccount }}</view>
</view>
</view>
</view>
<view class="container">
<view class="h1">案发情况</view>
<view class="row" style="--label-width: 160rpx">
<view class="col col-24">
<view class="label">事发时间</view>
<view class="content">{{ comfort.apply?.happenTime }}</view>
</view>
<view class="col col-24">
<view class="label">事实与理由</view>
<view class="content">{{ comfort.apply?.factReason }}</view>
</view>
<view class="col col-12">
<view class="label">案发环节</view>
<view class="content">{{ comfort.apply?.incidentLink }}</view>
</view>
<view class="col col-12">
<view class="label">受伤程度</view>
<view class="content">{{ comfort.apply?.injurySeverity }}</view>
</view>
<view class="col col-24">
<view class="label">侵权形式</view>
<view class="content">{{ comfort.apply?.formsOfTort }}</view>
</view>
<view class="col col-12">
<view class="label">侵权人姓名</view>
<view class="content">{{ comfort.apply?.infringerName || '/' }}</view>
</view>
<view class="col col-12">
<view class="label">处理方式</view>
<view class="content">{{ comfort.apply?.infringerHandle }}</view>
</view>
</view>
</view>
<view class="container">
<view class="h1">附件</view>
<file-list :files="comfort.apply?.documentFile? JSON.parse(comfort.apply?.documentFile) : []" />
<empty description="无附件" v-if="!comfort.apply?.documentFile" />
</view>
</view>
</template>
<script>
import { getDictOptions } from '@/common/dict'
import { getComfort } from '@/api/comfort'
let _this;
export default {
data() {
return {
comfort: {}
}
},
watch : {
},
setup() {
const formsOfTort = getDictOptions('formsOfTort')
const injurySeverity = getDictOptions('injurySeverity')
const incidentLink = getDictOptions('incidentLink')
const bank = getDictOptions('bank')
return {
formsOfTort,
injurySeverity,
incidentLink,
bank
}
},
onLoad() {
_this = this;
getComfort(this.$page.options.id).then(data => {
this.comfort = data
})
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #fff;
margin-bottom: 12rpx;
}
.wrapper {
background-color: #f5f5f5;
min-height: 100vh;
}
.h1 {
font-weight: 700;
margin-bottom: 24rpx;
}
</style>

513
pages/comfort/add.vue

@ -0,0 +1,513 @@
<template>
<uni-forms ref="form" :modelValue="formData" :rules="rules" label-width="210rpx" class="wrapper">
<view class="container">
<view class="title">办理信息</view>
<uni-forms-item label="申请时间" name="applyDate" required>
<uni-datetime-picker type="date" v-model="formData.applyDate" :border="false" placeholder="请选择申请时间" />
</uni-forms-item>
<uni-forms-item label="是否本人申请" name="isSelf" required>
<uni-data-checkbox v-model="formData.isSelf" :localdata="isSelf" @change="handleIsSelfChange" />
</uni-forms-item>
<uni-forms-item label="代理人姓名" name="agentName" required v-if="formData.isSelf === '0'">
<uni-easyinput :input-border="false" placeholder="代理人姓名" v-model="formData.agentName" />
</uni-forms-item>
<uni-forms-item label="关系" name="relation" required v-if="formData.isSelf === '0'">
<uni-data-checkbox v-model="formData.relation" :localdata="relation" />
</uni-forms-item>
</view>
<view class="container">
<view class="title">申请人信息</view>
<uni-forms-item label="申请人姓名" name="applicantEmpName" required>
<uni-easyinput :input-border="false" placeholder="申请人姓名" v-model="formData.applicantEmpName" />
</uni-forms-item>
<uni-forms-item label="性别" name="sex" required>
<uni-data-checkbox v-model="formData.sex" :localdata="sex" />
</uni-forms-item>
<uni-forms-item label="警号" name="empNo" required>
<uni-easyinput :input-border="false" placeholder="警号" v-model="formData.empNo" />
</uni-forms-item>
<uni-forms-item label="单位" name="departId" required>
<uni-data-picker :localdata="departs" placeholder="请选择单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="formData.departId" />
</uni-forms-item>
<uni-forms-item label="身份证" name="idCode" required>
<uni-easyinput :input-border="false" placeholder="身份证" v-model="formData.idCode" />
</uni-forms-item>
<uni-forms-item label="出生年月" name="birthday">
<uni-datetime-picker type="date" v-model="formData.birthday" :border="false" placeholder="请选择出生年月" />
</uni-forms-item>
<uni-forms-item label="联系电话" name="mobile" required>
<uni-easyinput :input-border="false" placeholder="联系电话" v-model="formData.mobile" />
</uni-forms-item>
<uni-forms-item label="职务" name="job" >
<uni-easyinput :input-border="false" placeholder="职务" v-model="formData.job" />
</uni-forms-item>
<uni-forms-item label="警衔" name="policeRank" >
<uni-easyinput :input-border="false" placeholder="警衔" v-model="formData.policeRank" />
</uni-forms-item>
<uni-forms-item label="文化程度" name="levelEducation">
<uni-data-select
v-model="formData.levelEducation"
:localdata="levelEducation"
></uni-data-select>
</uni-forms-item>
<uni-forms-item label="政治面貌" name="politicCountenance" >
<uni-data-select
v-model="formData.politicCountenance"
:localdata="politicCountenance"
></uni-data-select>
</uni-forms-item>
<uni-forms-item label="开户行" name="bankCard" required>
<uni-data-select
v-model="formData.bankCard"
:localdata="bank.map(item => {
return {
text: item.dictLabel,
value: item.dictValue
}
})"
></uni-data-select>
</uni-forms-item>
<uni-forms-item label="所属支行" name="bankBranch" required>
<uni-easyinput :input-border="false" placeholder="所属支行" v-model="formData.bankBranch" />
</uni-forms-item>
<uni-forms-item label="银行账号" name="bankCardAccount" required>
<uni-easyinput :input-border="false" placeholder="银行账号" v-model="formData.bankCardAccount" />
</uni-forms-item>
</view>
<view class="container">
<view class="title">案发情况</view>
<uni-forms-item label="事发时间" name="happenTime" required>
<uni-datetime-picker type="datetime" v-model="formData.happenTime" :border="false" placeholder="请选择事发时间" />
</uni-forms-item>
<uni-forms-item label="事实与理由" name="factReason" required>
<uni-easyinput type="textarea" :input-border="false" placeholder="事实与理由" v-model="formData.factReason" />
</uni-forms-item>
<uni-forms-item label="案发环节" name="incidentLink" required>
<uni-data-select
v-model="formData.incidentLink"
:localdata="incidentLink.map(item => {
return {
text: item.dictLabel,
value: item.dictValue
}
})"
@change="(val) => {
formData.incidentLinkName = getDictLabel(incidentLink, val)
}"
></uni-data-select>
</uni-forms-item>
<uni-forms-item label="受伤程度" name="injurySeverity" required>
<uni-data-select
v-model="formData.injurySeverity"
:localdata="injurySeverity.map(item => {
return {
text: item.dictLabel,
value: item.dictValue
}
})"
@change="(val) => {
formData.injurySeverityName = getDictLabel(injurySeverity, val)
}"
></uni-data-select>
</uni-forms-item>
<uni-forms-item label="侵权形式" name="formsOfTort" required>
<uni-data-picker :localdata="formsOfTort" placeholder="侵权形式" :border="false" :map="{text:'dictLabel', value: 'dictValue'}" v-model="formData.formsOfTort"
@change="(val) => {
formData.formsOfTortName = detail.value[0].text
}"
/>
</uni-forms-item>
<uni-forms-item label="侵权人姓名" name="infringerName">
<uni-easyinput :input-border="false" placeholder="侵权人姓名" v-model="formData.infringerName" />
</uni-forms-item>
<uni-forms-item label="处理方式" name="infringerHandle" required>
<uni-data-checkbox v-model="formData.infringerHandle" :localdata="infringerHandle" />
</uni-forms-item>
</view>
<view class="container">
<view class="title">佐证材料</view>
<uni-forms-item label="附件" name="documentFile" label-position="top" required>
<upload v-model="formData.documentFile" />
</uni-forms-item>
</view>
<view class="container">
<view class="title">呈报审批</view>
<uni-forms-item label="主办单位" name="handleDepartId" required>
<uni-data-picker :localdata="secondDeparts" placeholder="请选择主办单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="formData.handleDepartId" />
</uni-forms-item>
<uni-forms-item label="审批人" name="approverEmpNo" required>
<view>{{ formData.approver }}</view>
</uni-forms-item>
</view>
</uni-forms>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="back">取消</button>
<button type="primary" @tap="submit" class="col-12">确定</button>
</view>
</template>
<script>
import { getDictLabel } from '@/common/util'
import moment from 'moment'
import store from '@/store'
import { getDictOptions } from '@/common/dict'
import {
departTree, secondList
} from '@/api/depart'
import {
addComfort,
listRightPersonByDepartId
} from '@/api/comfort'
let _this;
export default {
data() {
return {
formData: {
applyDate: moment().format('YYYY-MM-DD'),
documentFile: [],
},
departs: [],
secondDeparts: [],
rules: {
applyDate: {
rules: [{
required: true,
errorMessage: '请选择申请时间',
}]
},
isSelf: {
rules: [{
required: true,
errorMessage: '请选择是否本人申请',
}]
},
agentName: {
rules: [{
required: true,
errorMessage: '请输入代理人姓名',
}]
},
relation: {
rules: [{
required: true,
errorMessage: '请选择代理人关系',
}]
},
applicantEmpName: {
rules: [{
required: true,
errorMessage: '请输入申请人姓名',
}]
},
sex: {
rules: [{
required: true,
errorMessage: '请选择性别',
}]
},
empNo: {
rules: [{
required: true,
errorMessage: '请输入警号',
}]
},
departId: {
rules: [{
required: true,
errorMessage: '请选择单位',
}]
},
idCode: {
rules: [{
required: true,
errorMessage: '请输入身份证',
}]
},
mobile: {
rules: [{
required: true,
errorMessage: '请输入联系方式',
}]
},
bankCard: {
rules: [{
required: true,
errorMessage: '请选择开户行',
}]
},
bankBranch: {
rules: [{
required: true,
errorMessage: '请输入所属支行',
}]
},
bankCardAccount: {
rules: [{
required: true,
errorMessage: '请输入银行账号',
}]
},
happenTime: {
rules: [{
required: true,
errorMessage: '请选择事发时间',
}]
},
factReason: {
rules: [{
required: true,
errorMessage: '请输入事实与理由',
}]
},
incidentLink: {
rules: [{
required: true,
errorMessage: '请选择案发环节',
}]
},
injurySeverity: {
rules: [{
required: true,
errorMessage: '请选择受伤程度',
}]
},
formsOfTort: {
rules: [{
required: true,
errorMessage: '请选择侵权形式',
}]
},
infringerHandle: {
rules: [{
required: true,
errorMessage: '请选择处理方式',
}]
},
handleDepartId: {
rules: [{
required: true,
errorMessage: '请选择主办单位',
}]
},
approverEmpNo: {
rules: [{
required: true,
errorMessage: '未配置审批人,当前主办单位的未配置维权专干(请联系系统管理员)',
}]
},
// documentFile: {
// rules: [{
// validateFunction: function(rule,value,data,callback){
// if (!value || value.length === 0) {
// callback('')
// }
// return true
// }
// }]
// },
},
isSelf: [
{
text: '是',
value: '1'
},
{
text: '否',
value: '0'
}
],
relation: [
{
text: '同事',
value: '1'
},
{
text: '亲属',
value: '2'
}
],
sex: [
{
text: '男',
value: '0'
},
{
text: '女',
value: '1'
}
],
levelEducation: [
{
text: '高中',
value: '高中'
},
{
text: '大专',
value: '大专'
},
{
text: '本科',
value: '本科'
},
{
text: '研究生及以上',
value: '研究生及以上'
}
],
politicCountenance: [
{
text: '群众',
value: '群众'
},
{
text: '团员',
value: '团员'
},
{
text: '预备党员',
value: '预备党员'
},
{
text: '党员',
value: '党员'
}
],
infringerHandle: [
{
text: '行政处罚',
value: '行政处罚'
},
{
text: '刑事处罚',
value: '刑事处罚'
},
]
}
},
watch : {
'formData.handleDepartId': function(val) {
listRightPersonByDepartId(val).then((data) => {
if (data.length) {
this.formData.approverEmpNo = data.map((item) => item.empNo);
this.formData.approver = data
.map((item) => item.empName)
.join("、");
} else {
delete this.formData.approverEmpNo;
delete this.formData.approver;
}
});
},
},
setup() {
const formsOfTort = getDictOptions('formsOfTort')
const injurySeverity = getDictOptions('injurySeverity')
const incidentLink = getDictOptions('incidentLink')
const bank = getDictOptions('bank')
return {
formsOfTort,
injurySeverity,
incidentLink,
bank,
getDictLabel
}
},
onLoad() {
_this = this;
departTree().then(data => {
this.departs = data[0].children
})
secondList().then(data => {
this.secondDeparts = data
})
},
methods: {
submit() {
this.$refs.form.validate().then(res => {
this.formData.happenTime = moment(this.formData.happenTime, 'YYYY-MM-DD HH:mm:ss').format('YYYY-MM-DD HH:mm')
addComfort(this.formData).then(data => {
_this.formData = {
applyDate: moment().format('YYYY-MM-DD'),
documentFile: [],
}
uni.showToast({
title: '操作成功',
icon: 'none',
duration: 3000
})
uni.navigateBack({
});
})
}).catch(err => {
uni.showToast({
title: '请检查表单必填项',
icon: 'none',
duration: 3000
})
})
},
back() {
uni.navigateBack({
});
},
handleIsSelfChange() {
console.log('handleIsSelfChange')
if (this.formData.isSelf === '1') {
this.formData.applicantEmpName = store.state.user.nickName
this.formData.empNo = store.state.user.empNo
this.formData.idCode = store.state.user.userName
this.formData.mobile = store.state.user.mobile
this.formData.departId = store.state.user.departId
if (store.state.user.userName.length === 18) {
this.formData.birthday = moment(store.state.user.userName.substr(6, 8), 'YYYYMMDD').format('YYYY-MM-DD')
this.formData.sex = getSex(store.state.user.userName)
}
}
}
}
}
function getSex(idCard) {
let res = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/;
if (idCard && res.test(idCard)) {
let genderCode = idCard.charAt(16);
if (parseInt(genderCode) % 2 == 0) {
return '1';
}
return '0';
}
return '';
}
</script>
<style lang="scss" scoped>
.wrapper {
background-color: #f5f5f5;
padding-bottom: 72px;
}
.container {
background-color: #fff;
margin-bottom: 24rpx;
}
.footer {
background-color: #fff;
border-top: 1px solid #eee;
}
.title {
margin-bottom: 16rpx;
}
</style>

209
pages/comfort/list.vue

@ -0,0 +1,209 @@
<template>
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="search-item-conent">
<uni-easyinput prefixIcon="search" type="text" v-model="query.applicantEmpName" placeholder="搜索" :inputBorder="false" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag" @tap="showPopup">
<view :class="filterFlag ? 'search-item-conent active': 'search-item-conent'">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="searchFlag = false">取消</view>
</view>
</view>
<view class="negative-container">
<view class="card negative-item" v-for="item in comforts" @tap="open(item)">
<view class="row" style="--label-width: 160rpx">
<view class="col col-12">
<view class="label">申请人</view>
<view class="content">{{ item.applicantEmpName }}</view>
</view>
<view class="col col-12">
<view class="label">申请时间</view>
<view class="content">{{ item.applyDate }}</view>
</view>
<view class="col col-12">
<view class="label">受伤程度</view>
<view class="content">{{ item.injurySeverityName }}</view>
</view>
<view class="col col-12">
<view class="label">申请金额</view>
<view class="content">{{ item.injurySeverity }}</view>
</view>
<view class="col col-24">
<view class="label">事实与理由</view>
<view class="content">{{ item.factReason }}</view>
</view>
</view>
<view class="negative-bottom">
<uni-tag :text="getDictLabel(comfortStatus, item.rpcStatus)" :type="getRpcStatusTagType(item.rpcStatus)" />
</view>
</view>
<empty v-if="comforts.length === 0" />
</view>
<view class="footer col-24">
<button type="primary" @tap="openAdd">我要抚慰</button>
</view>
<uni-popup ref="filterPopupRef" type="top">
<view class="popup-container">
<view class="popup-header">
<view>全部筛选</view>
<uni-icons type="closeempty" class="close-btn" @tap="closePopup"></uni-icons>
</view>
<view class="popup-body">
<view class="filter-container">
<view class="filter-label mt-10">状态</view>
<filter-radio :data="comfortStatus" v-model="query.rpcStatus" :prop="{text: 'dictLabel', value: 'dictValue'}" @change="search" />
</view>
</view>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="reset">重置</button>
<button type="primary" @tap="handleSearch" class="col-12">完成</button>
</view>
</view>
</uni-popup>
</template>
<script>
import store from '@/store'
import { getDictOptions } from '@/common/dict'
import { listComfort } from '@/api/comfort'
import {
departTree
} from '@/api/depart'
import { getDictLabel } from '@/common/util'
let oldList;
let _this;
export default {
data() {
return {
comforts: [],
query: {
size: 20,
current: 1
},
departs: [],
searchFlag: false,
filterFlag: false,
}
},
setup(props, context) {
const processingStatus = getDictOptions('processingStatus');
const businessTypes = getDictOptions('businessType')
const comfortStatus = getDictOptions('comfortStatus')
return {
processingStatus,
businessTypes,
comfortStatus,
getDictLabel
}
},
watch: {
searchFlag(val) {
if (val) {
oldList = this.comforts;
this.comforts = []
} else {
this.comforts = oldList
}
},
'query.thingDesc': function(val) {
if (_this.searchFlag) {
if (val) {
_this.getComforts()
} else {
this.comforts = []
}
}
}
},
onLoad() {
_this = this;
this.getComforts();
departTree().then(data => {
this.departs = data[0].children
})
},
methods: {
open(item) {
uni.navigateTo({
url: '/pages/comfort/action?id=' + item.rpcId
});
},
openAdd() {
uni.navigateTo({
url: '/pages/comfort/add'
});
},
getComforts() {
uni.showLoading({
title: '数据加载中'
});
listComfort(this.query).then(data => {
this.comforts = data.records
uni.hideLoading();
});
},
showPopup() {
this.$refs.filterPopupRef.open()
},
closePopup() {
this.$refs.filterPopupRef.close()
},
search() {
if (this.query.rpcStatus) {
this.filterFlag = true
} else {
this.filterFlag = false
}
this.getComforts()
},
handleSearch() {
this.search()
this.closePopup()
},
reset() {
this.filterFlag = false
this.query = {
size: 20,
current: 1
}
this.getComforts()
},
getRpcStatusTagType(rpcStatus) {
if (rpcStatus === 'returned') {
return 'error'
}
if (rpcStatus === 'approval') {
return 'primary'
}
return ''
}
}
}
</script>
<style lang="scss" scoped>
.negative-container {
max-height: calc(100vh - 98rpx);
overflow: auto;
box-sizing: border-box;
padding-bottom: 72px;
}
.negative-bottom {
display: flex;
justify-content: space-between;
.negative-tag {
color: #FF0000;
}
}
</style>

296
pages/common/camera.nvue

@ -0,0 +1,296 @@
<template>
<view :style="{ width: windowWidth, height: windowHeight }">
<live-pusher id="livePusher" ref="livePusher" class="livePusher" mode="FHD" beauty="0" whiteness="0"
min-bitrate="1000" audio-quality="16KHz" device-position="back" :auto-focus="true"
:muted="true" :enable-camera="true" :enable-mic="false" :zoom="false"
@statechange="statechange"
:style="{ width: windowWidth, height: windowHeight }"></live-pusher>
<view class="tools-t" v-if="files.length > 0">
<view>
<button class="rollover-btn tools-btn" @tap="handleNext">
<uni-icons type="checkmarkempty" size="32" color="#84FF6D" />
</button>
</view>
</view>
<view class="tools-b">
<view>
<view class="img-btn tools-btn" @tap="openPhoto">
<template v-if="files.length > 0">
<view class="img-number">{{ files.length }}</view>
<net-image :filepath="files[0].filePath" class="img-btn-img" />
</template>
</view>
</view>
<view>
<button class="photo-btn tools-btn" @tap="snapshot">
<view class="photo-btn-c"></view>
</button>
</view>
<view>
<button class="rollover-btn tools-btn" @tap="flip">
<uni-icons type="loop" size="24" color="#fff" />
</button>
</view>
</view>
</view>
</template>
<script>
import { addPhoto } from '@/api/photo'
import { pathToBase64 } from 'image-tools'
import {
uploadFileBase64
} from '@/api/file'
import store from '@/store'
let _this = null;
export default {
data() {
return {
livePusher: null,
aspect: '2:3',
windowWidth: '', //屏幕可用宽度
windowHeight: '', //屏幕可用高度
camerastate: false,
files: []
}
},
async onLoad() {
_this = this;
this.initCamera();
// 解决 store 取不到值的问题
store.commit('setRequestUrl', this.$store.state.requestUrl)
store.commit('setFileRequestUrl', this.$store.state.fileRequestUrl)
store.commit('setAppCredential', this.$store.state.appCredential)
store.commit('setUserCredential', this.$store.state.userCredential)
},
async onReady() {
// #ifdef APP-PLUS
this.livePusher = uni.createLivePusherContext('livePusher', this);
this.startPreview(); //开启预览并设置摄像头
this.poenCarme();
// #endif
},
methods: {
initCamera() {
uni.getSystemInfo({
success: function(res) {
_this.windowWidth = res.windowWidth;
_this.windowHeight = res.windowHeight;
let zcs = _this.aliquot(_this.windowWidth,_this.windowHeight);
_this.aspect = (_this.windowWidth/zcs)+':'+(_this.windowHeight/zcs);
console.log('画面比例:'+_this.aspect);
}
});
},
//整除数计算
aliquot(x, y) {
if (x % y == 0) return y;
return this.aliquot(y, x % y);
},
//轮询打开
poenCarme(){
//#ifdef APP-PLUS
if (plus.os.name == 'Android') {
setInterval(function() {
console.log(_this.camerastate);
if (!_this.camerastate) _this.startPreview();
}, 2500);
}
//#endif
},
//开始预览
startPreview() {
this.livePusher.startPreview({
success: a => {
console.log('startPreview', a.errMsg)
if (a.errMsg == 'startPreview:ok' || a.errMsg == 'operateLivePusher:ok') {
_this.camerastate = true; //标记相机启动成功
}
}
});
},
//抓拍
snapshot() {
//震动
uni.vibrateShort({
success: function() {
console.log('震动 success');
}
});
//拍照
this.livePusher.snapshot({
success: (e) => {
console.log(e.message.tempImagePath)
const fileName = e.message.tempImagePath.substring(e.message.tempImagePath.lastIndexOf('/') + 1)
pathToBase64(e.message.tempImagePath).then(base64 => {
console.log('base64', base64)
console.log('fileName', fileName)
// 上传文件
uploadFileBase64({
base64,
originalFilename: fileName
}).then(data => {
_this.files.push({
filePath: data.filePath,
fileName
})
addPhoto({
filePath: data.filePath,
fileName
}).then(() => {
})
})
});
_this.stopPreview();
setTimeout(() => {
_this.startPreview()
}, 1000)
},
fail(e) {
console.log('fail', e)
}
});
},
//停止预览
stopPreview() {
this.livePusher.stopPreview({
success: a => {
// _this.camerastate = false; //标记相机未启动
}
});
},
//反转
flip() {
this.livePusher.switchCamera();
},
//状态
statechange(e) {
//状态改变
console.log(e);
if (e.detail.code == 1007) {
_this.camerastate = true;
} else if (e.detail.code == -1301) {
_this.camerastate = false;
}
},
openPhoto() {
uni.navigateTo({
url: `/pages/photo/index`
});
},
handleNext() {
uni.showModal({
content: `关联问题,将照片关联到对应问题信息,并结束拍照;`,
cancelText: '继续拍照',
confirmText: '关联问题',
success: function (res) {
if (res.confirm) {
uni.navigateTo({
url: '/pages/task/problem/add?files=' + JSON.stringify(_this.files)
});
}
}
});
}
}
}
</script>
<style lang="scss">
.livePusher {
height: 100vh;
width: 100vw;
}
.tools-t {
position: fixed;
top: 32rpx;
right: 32rpx;
z-index: 9999;
display: flex;
flex-direction: row;
justify-content: flex-end;
.tools-btn {
border-radius: 50%;
padding: 0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.6);
height: 112rpx;
width: 112rpx;
border: none;
}
}
.tools-b {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
background-color: rgba(0, 0, 0, 1);
padding: 48rpx 80rpx;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.tools-btn {
border-radius: 50%;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
}
.photo-btn {
height: 140rpx;
width: 140rpx;
>.photo-btn-c {
height: 128rpx;
width: 128rpx;
border-radius: 50%;
border: 2px solid #000;
}
}
.rollover-btn {
background-color: transparent;
border: 1px solid #fff;
height: 96rpx;
width: 96rpx;
}
.img-btn {
background-color: transparent;
border: 1px solid #fff;
height: 96rpx;
width: 96rpx;
position: relative;
.img-btn-img {
height: 96rpx;
width: 96rpx;
border-radius: 50%;
display: block;
}
}
.img-number {
position: absolute;
top: -16rpx;
right: -16rpx;
z-index: 999;
color: #fff;
}
}
</style>

92
pages/duty/index.vue

@ -0,0 +1,92 @@
<template>
<view class="calendar-content">
<view>
<uni-calendar :selected="info.selected" :showMonth="false" @change="change"
@monthSwitch="monthSwitch" />
</view>
</view>
<view class="duty-container">
<view class="duty-title">市局</view>
<view class="row">
<view class="col col-12 duty-item">
<view class="duty-item-label">值班领导</view>
<view class="duty-item-content">张三</view>
</view>
</view>
<view class="duty-title">分局</view>
<view class="row">
<view class="col col-12 duty-item">
<view class="duty-item-label">天心分局</view>
<view class="duty-item-content">李四</view>
</view>
<view class="col col-12 duty-item">
<view class="duty-item-label">雨花分局</view>
<view class="duty-item-content">王五</view>
</view>
<view class="col col-12 duty-item">
<view class="duty-item-label">芙蓉分局</view>
<view class="duty-item-content">游麻</view>
</view>
<view class="col col-12 duty-item">
<view class="duty-item-label">长沙县局</view>
<view class="duty-item-content">张三</view>
</view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
info: {
selected: []
}
}
},
onReady() {
},
methods: {
change(e) {
console.log('change 返回:', e)
},
monthSwitch(e) {
console.log('monthSwitchs 返回:', e)
}
}
}
</script>
<style lang="scss" scoped>
.duty-container {
padding: 24rpx;
.col {
margin-bottom: 0;
}
}
.duty-title {
font-size: 17px;
margin-top: 24rpx;
margin-bottom: 12rpx;
}
.duty-item {
display: flex;
text-align: center;
.duty-item-label {
width: 50%;
height: 64rpx;
line-height: 64rpx;
background: #0F78F3;
color: #fff;
}
.duty-item-content {
width: 50%;
height: 64rpx;
line-height: 64rpx;
background: #E3F0FF;
}
}
</style>

442
pages/index/index.vue

@ -0,0 +1,442 @@
<template>
<template v-if="currentPage === 'home'">
<view>
<image src="/static/image/carousel.png" style="width: 100%" mode="widthFix"></image>
</view>
<view class="container">
<view class="title">我的任务</view>
<view class="flex flex-wrap gap-32">
<view class="app-item" @tap="openTask('todo')">
<view class="item-icon item-number mb-6">
<text>{{ task.todoCount }}</text>
</view>
<view class="item-name mb-6">待处理</view>
</view>
<view class="app-item" @tap="openTask('done')">
<view class="item-icon item-number mb-6">
<text>{{ task.doneCount }}</text>
</view>
<view class="item-name">已办结</view>
</view>
</view>
</view>
<!-- <view class="container">
<view class="title">问题处置</view>
<view class="flex flex-wrap gap-32">
<view class="app-item" @tap="openTask('todo')">
<view class="item-icon item-number mb-6">
<text>{{ task.todoCount }}</text>
</view>
<view class="item-name mb-6">待处理</view>
</view>
</view>
</view> -->
<view class="container">
<view class="title">督察应用</view>
<view class="flex flex-wrap app-container">
<view class="app-item" @tap="openTaskProblem">
<view class="item-icon mb-6">
<image src="/static/icon/ic_camera.png"></image>
</view>
<view class="item-name">问题随拍</view>
</view>
<view class="app-item" @tap="openNegative">
<view class="item-icon mb-6">
<image src="/static/icon/ic_warning.png"></image>
</view>
<view class="item-name">问题清单</view>
</view>
<view class="app-item" @tap="openSelfexamination">
<view class="item-icon mb-6">
<image src="/static/icon/ic_selfexamination.png"></image>
</view>
<view class="item-name">所队自查</view>
</view>
<view class="app-item" @tap="openDuty">
<view class="item-icon mb-6">
<image src="/static/icon/ic_card.png"></image>
</view>
<view class="item-name">值班报备</view>
</view>
<view class="app-item" @tap="openComfort">
<view class="item-icon mb-6">
<image src="/static/icon/ic_apply.png"></image>
</view>
<view class="item-name">抚慰申请</view>
</view>
<view class="app-item" @tap="openBooks">
<view class="item-icon mb-6">
<image src="/static/icon/ic_book.png"></image>
</view>
<view class="item-name">知识库</view>
</view>
</view>
</view>
</template>
<template v-else>
<view class="wrapper">
<view class="center-card flex gap-12">
<view class="avatar">
<net-image :filepath="store.state.user.avatarUrl" v-if="store.state.user.avatarUrl" />
<image src="/static/police.png" v-else></image>
</view>
<view class="flex flex-col justify-between">
<view>
<view class="name mb-6">{{ store.state.user.nickName }}</view>
<view class="second mb-6">{{ store.state.user.departName }}</view>
<view class="second">{{ }}</view>
</view>
<view class="info flex">
<view>警号</view>
<view class="mr-20">{{ store.state.user.empNo }}</view>
</view>
</view>
<view class="edit-btn">
<image src="/static/edit.png" style="height: 37px" mode="heightFix"></image>
</view>
</view>
<view class="panel">
<view class="panel-h" @tap="openManual">
<image src="/static/icon/ic_c_book.png" style="height: 27px; width: 27px"></image>
<view class="panel-c">
<text class="panel-text">使用手册</text>
<uni-icons type="right" size="20" color="#666" />
</view>
</view>
<view class="panel-h" @tap="openFeedback">
<image src="/static/icon/ic_c_message.png" style="height: 27px; width: 27px"></image>
<view class="panel-c">
<text class="panel-text">意见反馈</text>
<uni-icons type="right" size="20" color="#666" />
</view>
</view>
<view class="panel-h" @tap="openAbout">
<image src="/static/icon/ic_c_app.png" style="height: 27px; width: 27px"></image>
<view class="panel-c">
<text class="panel-text">关于APP</text>
<uni-icons type="right" size="20" color="#666" />
</view>
</view>
</view>
</view>
</template>
<view class="tab-bar">
<view v-for="item in tabBars" :class="currentPage === item.name ? 'tab-bar-item active': 'tab-bar-item'" @tap="currentPage = item.name">
<uni-icons :type="item.icon" size="50rpx"></uni-icons>
<view>{{ item.text }}</view>
</view>
<view class="tab-bar-item_center" @tap="openCamera">
<image src="/static/camer.png" class="tab-bar-item_center-icon" mode="widthFix"></image>
<view>随手拍</view>
</view>
</view>
</template>
<script lang="uts">
import permision from '@/common/permission'
import { getToken } from '@/common/auth'
import store from '@/store'
import { setToken } from '@/common/auth'
import { login } from '@/api/auth'
import { getTaskCount } from '@/api/task'
let _this;
export default {
data() {
return {
task: {
todoCount: 0,
doneCount: 0
},
user: {},
currentPage: 'home',
tabBars: [
{
text: '首页',
icon: 'home',
name: 'home'
},
{
text: '我的',
icon: 'person',
name: 'center'
}
]
}
},
setup() {
return {
store
}
},
async onShow() {
_this = this;
const url = 'http://172.20.10.3:8080/app/';
const env = 'prod';
// const url = 'http://192.168.3.22:8080/app/'
// #ifdef APP-PLUS
if (env === 'dev') {
if (!store.state.requestUrl) {
store.commit('setRequestUrl', url + 'forward')
}
if (!getToken() || !store.state.hasLogin) {
const userData = await login({ empNo: '012893' });
setToken(userData.token);
store.commit('setUser', userData.user)
}
} else {
const event = plus.android.importClass('com.maker.supervise.Event')
if (!store.state.requestUrl) {
const requestUrl = event.getRequestUrl()
store.commit('setRequestUrl', requestUrl)
}
if (!store.state.appCredential) {
const appCredential = encodeURIComponent(event.getAppCredential())
store.commit('setAppCredential', appCredential)
}
if (!store.state.userCredential) {
const userCredential = encodeURIComponent(event.getUserCredential())
store.commit('setUserCredential', userCredential)
}
if (!getToken() || !store.state.hasLogin) {
const userData = await login({ empNo: event.getEmpNo() });
setToken(userData.token);
store.commit('setUser', userData.user)
}
}
// #endif
const data = await getTaskCount()
if (data) {
_this.task = data;
}
},
methods: {
openDeveloping() {
uni.showModal({
title: '温馨提示',
content: '功能开发中...',
success: function (res) {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
},
openTask(taskStatus) {
uni.navigateTo({
url: '/pages/task/index?taskStatus=' + taskStatus
});
},
async openCamera() {
// #ifdef APP-PLUS
let status = await permision.requestAndroid('android.permission.CAMERA');
if (status === null || status === 1) {
uni.navigateTo({
url: '/pages/common/camera'
});
} else {
uni.showModal({
content: "没有开启相机权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
// #endif
// #ifdef H5
uni.navigateTo({
url: '/pages/common/camera'
});
// #endif
},
openNegative() {
uni.navigateTo({
url: '/pages/negative/index'
});
},
openDuty() {
uni.navigateTo({
url: '/pages/duty/index'
});
},
openComfort() {
uni.navigateTo({
url: '/pages/comfort/list'
});
},
openBooks() {
uni.navigateTo({
url: '/pages/books/index'
});
},
openSelfexamination() {
uni.navigateTo({
url: '/pages/task/selfexamination/list'
});
},
openTaskProblem() {
uni.navigateTo({
url: '/pages/task/problem/list'
});
},
openFeedback() {
uni.navigateTo({
url: `/pages/center/feedback`
});
},
openManual() {
uni.navigateTo({
url: `/pages/center/manual`
});
},
openAbout() {
uni.navigateTo({
url: `/pages/center/about`
});
},
}
}
</script>
<style lang="scss" scoped>
.container {
padding: 20rpx 30rpx;
.title {
font-size: 28rpx;
font-weight: 700;
margin-bottom: 24rpx;
}
}
.app-item {
width: 124rpx;
.item-icon {
width: 124rpx;
height: 124rpx;
line-height: 124rpx;
text-align: center;
background: #F3F3F3;
border-radius: 24rpx;
image {
width: 100%;
height: 100%;
}
}
.item-name {
font-size: 26rpx;
text-align: center;
}
.item-number {
font-size: 42rpx;
font-weight: 700;
}
}
.app-container {
gap: 24rpx 64rpx;
}
.tab-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: 96rpx;
display: flex;
box-shadow: inset 0 1px 0 0 #E4E4E4;
.tab-bar-item {
width: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
font-size: 12px;
color: #666;
.uni-icons {
color: #666 !important;
}
&.active {
color: var(--primary-color);
.uni-icons {
color: var(--primary-color) !important;
}
}
}
.tab-bar-item_center {
position: absolute;
left: 50%;
top: -48rpx;
transform: translateX(-50%);
font-size: 12px;
color: #666;
text-align: center;
.tab-bar-item_center-icon {
width: 96rpx;
display: block;
}
}
}
.center-card {
box-shadow: 0px 10rpx 26rpx 6rpx rgba(0, 0, 0, 0.08);
margin: 24rpx;
padding: 24rpx;
position: relative;
.name {
font-size: 28rpx;
line-height: 40rpx;
}
.second {
font-size: 24rpx;
line-height: 34rpx;
color: #666;
}
.info {
font-size: 24rpx;
}
.edit-btn {
position: absolute;
bottom: 0;
right: 0;
image {
display: block;
}
}
}
.panel {
padding: 0 24rpx;
.panel-h {
display: flex;
align-items: center;
gap: 0 24rpx;
.panel-c {
height: 90rpx;
line-height: 90rpx;
box-shadow: inset 0 -1px 0 0 #eee;
width: calc(100% - 80rpx);
display: flex;
justify-content: space-between;
}
}
}
.avatar {
width: 136rpx;
height: 205rpx;
image {
width: 100%;
height: 100%;
}
}
</style>

198
pages/negative/action.vue

@ -0,0 +1,198 @@
<template>
<view class="negative-wrapper">
<negative :data="data" :loading="loading" />
<view class="container" v-if="flowKey === 'first_distribute' || flowKey === 'second_distribute'">
<uni-forms ref="distributeFormRef" :modelValue="actionData" :rules="distributeRules" label-width="200rpx" style="margin: 12px">
<uni-forms-item label="主办层级" name="hostLevel" required>
<uni-data-checkbox v-model="actionData.hostLevel" :localdata="hostLevel" :map="{text: 'dictLabel', value: 'dictValue'}" />
</uni-forms-item>
<uni-forms-item label="办理单位" name="departId" required>
<uni-data-picker :localdata="departs" placeholder="请选择办理单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="actionData.departId" />
</uni-forms-item>
</uni-forms>
</view>
</view>
<view class="footer col-24 flex flex-wrap justify-end">
<n-button v-for="item in flowActions" :type="item.buttonType" class="button" :plain="item.plain" :label="item.buttonLabel" @tap="submit(item)" :disabled="loading" />
</view>
<modal ref="approveModelRef" @confirm="handleApproval" title="审批意见" placeholderText="请输入审批意见" confirmText="审批通过" />
<modal ref="returnModelRef" @confirm="handleApproval" title="退回意见" placeholderText="请输入退回意见" confirmType="warn" />
</template>
<script>
import { getDictOptions } from '@/common/dict'
import { getNegative, executeNegative } from '@/api/negative'
import {
departTree,
secondList
} from '@/api/depart'
let _this;
export default {
inheritAttrs: false,
data() {
return {
data: {},
flowActions: [],
loading: true,
actionData: {},
activeFlowAction: {},
departs: [],
distributeRules: {
hostLevel: {
rules: [{
required: true,
errorMessage: '请选择主办层级',
}]
},
departId: {
rules: [{
required: true,
errorMessage: '请选择办理单位',
}]
},
},
flowKey: '',
confirmationCompletionFlag: false
}
},
setup() {
const hostLevel = getDictOptions("hostLevel");
return {
hostLevel
}
},
onLoad() {
_this = this;
uni.setNavigationBarTitle({
title: `问题编号 ` + this.$page.options.id
})
this.getData()
},
methods: {
getData() {
getNegative(this.$page.options.id, this.$page.options.workId).then(data => {
this.data = data
this.flowActions = data.flowActions
this.flowKey = data.flowNode.flowKey
this.confirmationCompletionFlag = data.confirmationCompletionFlag
this.loading = false
if (this.flowKey === 'first_distribute' || this.flowKey === 'second_distribute') {
if (this.flowKey === 'first_distribute') {
secondList().then(data => {
this.departs = data
})
} else {
departTree().then(data => {
this.departs = data[0].children
})
}
}
})
},
async submit(item) {
console.log(item.actionKey)
const actions = ['save', 'apply_extension', 'apply_completion', 'apply_countersign', 'update_verify'];
if (actions.indexOf(item.actionKey) > -1) {
uni.showModal({
title: '温馨提示',
content: '该功能暂不支持在APP上操作,如有需要请在电脑端操作',
success: function (res) {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
return
}
if (item.openDialog) {
const approveActionKeys = ['second_approve', 'first_approve']
if (approveActionKeys.indexOf(item.actionKey) > -1) {
if (
this.confirmationCompletionFlag
) {
item = {
actionName: "认定办结",
actionKey: "confirmationCompletion",
doClose: true
};
}
this.$refs.approveModelRef.open()
this.activeFlowAction = item
return
}
const returnActionKeys = ['second_sign_return', 'three_sign_return', 'second_approve_return', 'first_approve_return']
if (returnActionKeys.indexOf(item.actionKey) > -1) {
this.$refs.returnModelRef.open()
this.activeFlowAction = item
}
return
}
if (item.actionKey === 'second_distribute') {
await this.$refs.distributeFormRef.validate()
}
this.loading = true
executeNegative(this.$page.options.id, {
workId: this.$page.options.workId,
actionKey: item.actionKey,
nextFlowKey: item.nextFlowKey,
actionName: item.actionName,
data: this.actionData
}).then(data => {
console.log(data)
if (item.actionKey === 'second_sign') {
uni.showToast({
title: '签收成功',
icon: 'success',
duration: 3000
})
}
if (item.doClose) {
uni.showToast({
title: '操作成功',
icon: 'success',
duration: 3000
})
uni.navigateBack({
});
} else {
this.getData()
}
})
},
handleApproval(comments) {
this.activeFlowAction.openDialog = false
this.actionData.comments = comments
this.submit(this.activeFlowAction)
}
}
}
</script>
<style lang="scss" scoped>
.footer {
background-color: #fff;
gap: 8rpx;
}
.negative-wrapper {
background-color: #f5f5f5;
min-height: 100vh;
box-sizing: border-box;
padding-bottom: 60px;
.container {
background-color: #fff;
margin-bottom: 12rpx;
}
}
</style>

226
pages/negative/index.vue

@ -0,0 +1,226 @@
<template>
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="search-item-conent">
<uni-easyinput prefixIcon="search" type="text" v-model="query.thingDesc" placeholder="搜索" :inputBorder="false" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag" @tap="showPopup">
<view :class="filterFlag ? 'search-item-conent active': 'search-item-conent'">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="searchFlag = false">取消</view>
</view>
</view>
<view class="negative-container">
<view class="card negative-item" v-for="item in negatives" @tap="open(item)">
<view class="negative-title">
<view>{{ item.problemSources }}</view>
<view></view>
</view>
<view class="row">
<view class="col col-12">
<view class="label">录入时间</view>
<view class="content">{{ item.crtTime }}</view>
</view>
<view class="col col-12">
<view class="label">发现时间</view>
<view class="content">{{ item.discoveryTime }}</view>
</view>
<view class="col col-12">
<view class="label">业务类别</view>
<view class="content">{{ item.businessTypeName }}</view>
</view>
<view class="col col-12">
<view class="label">涉及单位</view>
<view class="content">{{ item.involveDepartName }}</view>
</view>
<view class="col col-24">
<view class="label">问题内容</view>
<view class="content">{{ item.thingDesc }}</view>
</view>
<view class="col col-24" v-if="item.checkStatusName">
<view class="label">是否属实</view>
<view class="content">{{ item.checkStatusName }}</view>
</view>
<view class="col col-24" v-if="item.currentProcessingObject">
<view class="label">当前处理</view>
<view class="content">{{ item.currentProcessingObject }}</view>
</view>
</view>
<view class="negative-bottom">
<view v-if="item.processingStatus === 'signing'" style="color: var(--danger-color)">签收中</view>
<view v-if="item.processingStatus === 'processing'" style="color: #ff5722">办理中</view>
<view v-if="item.processingStatus === 'approval'" style="color: var(--primary-color)">审批中</view>
<view v-if="item.processingStatus === 'completed'" style="color: var(--success-color)">已办结</view>
</view>
</view>
<empty v-if="negatives.length === 0" />
</view>
<uni-popup ref="filterPopupRef" type="top">
<view class="popup-container">
<view class="popup-header">
<view>全部筛选</view>
<uni-icons type="closeempty" class="close-btn" @tap="closePopup"></uni-icons>
</view>
<view class="popup-body">
<view class="filter-container">
<!-- <view class="filter-label">问题来源</view>
<problem-picker v-model="query.problemTypeCode" @change="search" /> -->
<view class="filter-label mt-10">业务类型</view>
<filter-radio :data="businessTypes" v-model="query.businessTypeCode" :prop="{text: 'dictLabel', value: 'dictValue'}" @change="search" />
<view class="filter-label mt-10">办理状态</view>
<filter-radio :data="processingStatus" v-model="query.processingStatus" :prop="{text: 'dictLabel', value: 'dictValue'}" @change="search" />
<view class="filter-label mt-10">涉及单位</view>
<view class="mb-10">
<uni-data-picker :localdata="departs" placeholder="请选择涉及单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="query.involveDepartId" @change="search" />
</view>
<view class="filter-label mt-10">办理单位</view>
<view class="mb-10">
<uni-data-picker :localdata="departs" placeholder="请选择办理单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="query.handleDepartId" @change="search" />
</view>
</view>
</view>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="reset">重置</button>
<button type="primary" @tap="handleSearch" class="col-12">完成</button>
</view>
</view>
</uni-popup>
</template>
<script>
import store from '@/store'
import { getDictOptions } from '@/common/dict'
import { listNegative } from '@/api/negative'
import {
departTree
} from '@/api/depart'
import { getDictLabel } from '@/common/util'
let oldList;
let _this;
export default {
data() {
return {
negatives: [],
query: {
size: 100,
current: 1
},
departs: [],
searchFlag: false,
filterFlag: false,
}
},
setup(props, context) {
const processingStatus = getDictOptions('processingStatus');
const businessTypes = getDictOptions('businessType')
return {
processingStatus,
businessTypes,
getDictLabel
}
},
watch: {
searchFlag(val) {
if (val) {
oldList = this.negatives;
this.negatives = []
} else {
this.negatives = oldList
}
},
'query.thingDesc': function(val) {
if (_this.searchFlag) {
if (val) {
_this.getNegatives()
} else {
this.negatives = []
}
}
}
},
onLoad() {
_this = this;
this.getNegatives();
departTree().then(data => {
this.departs = data[0].children
})
},
methods: {
open(item) {
uni.navigateTo({
url: '/pages/negative/info?id=' + item.id
});
},
getNegatives() {
uni.showLoading({
title: '数据加载中'
});
listNegative(this.query).then(data => {
this.negatives = data.records
uni.hideLoading();
});
},
showPopup() {
this.$refs.filterPopupRef.open()
},
closePopup() {
this.$refs.filterPopupRef.close()
},
search() {
if (this.query.problemTypeCode || this.query.businessTypeCode || this.query.involveDepartId || this.query.handleDepartId) {
this.filterFlag = true
} else {
this.filterFlag = false
}
this.getNegatives()
},
handleSearch() {
this.search()
this.closePopup()
},
reset() {
this.filterFlag = false
this.query = {
size: 100,
current: 1
}
this.getNegatives()
}
}
}
</script>
<style lang="scss" scoped>
.negative-container {
max-height: calc(100vh - 98rpx);
overflow: auto;
}
.negative-title {
display: flex;
justify-content: space-between;
margin-bottom: 12rpx;
font-weight: 500;
font-size: 16px;
}
.negative-bottom {
display: flex;
justify-content: space-between;
.negative-tag {
color: #FF0000;
}
}
</style>

41
pages/negative/info.vue

@ -0,0 +1,41 @@
<template>
<view class="negative-wrapper">
<negative :data="data" :loading="loading" />
</view>
</template>
<script>
import { getNegative } from '@/api/negative'
let _this;
export default {
data() {
return {
data: {},
loading: true
}
},
onLoad() {
_this = this;
uni.setNavigationBarTitle({
title: `问题编号 ` + this.$page.options.id
})
getNegative(this.$page.options.id).then(data => {
this.data = data
this.loading = false
})
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.negative-wrapper {
background-color: #f5f5f5;
min-height: 100vh;
box-sizing: border-box;
}
</style>

60
pages/photo/index.vue

@ -0,0 +1,60 @@
<template>
<view v-for="item in photoGroups" class="container">
<view class="photo-date">记录时间{{ item.date }}</view>
<view class="photo-container">
<view v-for="photo in item.photos" class="photo-item" @tap="handleCheckPhoto(photo)">
<net-image :filepath="photo.filePath" />
<radio :checked="activePhotos.findIndex(o => o.filePath === photo.filePath) > -1" @tap.stop="handleCheckPhoto(photo)" />
</view>
</view>
</view>
<empty v-if="photoGroups.length === 0" description="无照片" />
<view class="footer col-24">
<button type="primary" @click="openInspectionAdd">关联问题</button>
</view>
</template>
<script>
import store from '@/store'
import { listPhoto } from '@/api/photo'
export default {
data() {
return {
photoGroups: [],
activePhotos: []
}
},
onLoad() {
listPhoto().then(data => {
this.photoGroups = data;
})
},
methods: {
openInspectionAdd() {
uni.navigateTo({
url: '/pages/task/problem/add?files=' + JSON.stringify(this.activePhotos)
});
},
handleCheckPhoto(photo) {
const index = this.activePhotos.findIndex(o => o.filePath === photo.filePath);
if (index === -1) {
this.activePhotos.push({
filePath: photo.filePath,
fileName: photo.fileName
})
} else {
this.activePhotos.splice(index, 1)
}
}
}
}
</script>
<style lang="scss" scoped>
.photo-date {
margin-bottom: 24rpx;
font-size: 12px;
}
</style>

392
pages/task/index.vue

@ -0,0 +1,392 @@
<template>
<tabs :options="tabOptions" v-model="query.taskStatus" v-if="!searchFlag" />
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="search-item-conent">
<uni-easyinput prefixIcon="search" type="text" placeholder="搜索" :inputBorder="false" v-model="query.taskName" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag" @tap="showPopup">
<view :class="filterFlag ? 'search-item-conent active': 'search-item-conent'">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="cancleSearch">取消</view>
</view>
</view>
<view>
<view class="flex gap-16 task-item card" v-for="item in tasks" @tap="open(item)" >
<template v-if="item.taskType === 'testing_alcohol'">
<view>
<image src="../../static/icon/ic_wine.png" style="width: 44px; height: 44px"></image>
</view>
<view style="width: calc(100% - 60px)">
<view class="flex justify-between header">
<view class="title">{{ item.taskName }}</view>
<view class="date">{{ item.createTime }}</view>
</view>
<view>
<view class="row">
<view class="col col-24">
<view class="label">督察单位</view>
<view class="content">{{ item.supDepartName }}</view>
</view>
<view class="col col-24">
<view class="label">抽检人员</view>
<view class="content">{{ item.totalNumber }}</view>
</view>
<view class="col col-24">
<view class="label">检查时间</view>
<view class="content">{{ item.beginTime }} ~ {{ item.endTime }}</view>
</view>
</view>
<view style="color: #FF0000;" v-if="item.taskStatus === 'todo'">待处理</view>
<view v-else>已办结</view>
</view>
</view>
</template>
<template v-if="item.taskType === 'inspection'">
<view>
<image src="../../static/icon/ic_task.png" style="width: 44px; height: 44px"></image>
</view>
<view style="width: calc(100% - 60px)">
<view class="flex justify-between header">
<view class="title">{{ item.taskName }}</view>
<view class="date">{{ item.createTime }}</view>
</view>
<view>
<view class="row">
<view class="col col-24">
<view class="label">督察单位</view>
<view class="content">{{ item.supDepartName }}</view>
</view>
<view class="col col-12">
<view class="label">督察类型</view>
<view class="content">{{ item.supervisionType }}</view>
</view>
<view class="col col-24">
<view class="label">督察内容</view>
<view class="content">{{ item.taskContent }}</view>
</view>
<view class="col col-24">
<view class="label">检查时间</view>
<view class="content">{{ item.beginTime }} ~ {{ item.endTime }}</view>
</view>
</view>
<view style="color: #FF0000;" v-if="item.taskStatus === 'todo'">{{ !item.hasSign? '待签收' : '待处理'}}</view>
<view v-else>已办结</view>
</view>
</view>
</template>
<template v-if="item.taskType === 'selfexamination'">
<view>
<image src="../../static/icon/ic_selfexamination.png" style="width: 44px; height: 44px"></image>
</view>
<view style="width: calc(100% - 60px)">
<view class="flex justify-between header">
<view class="title">{{ item.taskName }}</view>
<view class="date">{{ item.createTime }}</view>
</view>
<view>
<view class="row">
<view class="col col-24">
<view class="label">自查单位</view>
<view class="content">{{ item.supDepartName }}</view>
</view>
<view class="col col-12">
<view class="label">自查类型</view>
<view class="content">{{ item.type }}</view>
</view>
<view class="col col-24">
<view class="label">自查要求</view>
<view class="content">{{ item.requirement }}</view>
</view>
<view class="col col-24">
<view class="label">任务时间</view>
<view class="content">{{ item.beginTime }} ~ {{ item.endTime }}</view>
</view>
</view>
<view style="color: #FF0000;" v-if="item.taskStatus === 'todo'">{{ !item.hasSign? '待签收' : '待处理'}}</view>
<view v-else>已办结</view>
</view>
</view>
</template>
<template v-if="item.taskType === 'negative'">
<view>
<image src="../../static/icon/ic_question.png" style="width: 44px; height: 44px"></image>
</view>
<view style="width: calc(100% - 60px)">
<view class="flex justify-between header">
<view class="title">{{ item.taskName }}</view>
<view class="date">{{ item.createTime }}</view>
</view>
<view>
<view class="row">
<view class="col col-24">
<view class="label">业务类别</view>
<view class="content">{{ item.businessTypeName }}</view>
</view>
<view class="col col-24">
<view class="label">涉及单位</view>
<view class="content">{{ item.supDepartName }}</view>
</view>
<view class="col col-24">
<view class="label">问题内容</view>
<view class="content">{{ item.taskContent }}</view>
</view>
<view class="col col-24">
<view class="label">涉嫌问题</view>
<view class="content">{{ getDictLabelByArray(suspectProblems, item.involveProblem) || '/' }}</view>
</view>
</view>
<view style="color: #555;" v-if="item.flowKey === 'completed'">已办结</view>
<view style="color: #FF0000;" v-else>{{ getFlowLable(item.flowKey) }}</view>
</view>
</view>
</template>
</view>
<empty v-if="tasks.length === 0" />
</view>
<uni-popup ref="filterPopupRef" type="top">
<view class="popup-container">
<view class="popup-header">
<view>全部筛选</view>
<uni-icons type="closeempty" class="close-btn" @tap="closePopup"></uni-icons>
</view>
<view class="popup-body">
<view class="filter-container">
<view class="filter-label">任务类型</view>
<filter-radio :data="taskTypes" v-model="query.taskType" @change="search" />
<!-- <view class="filter-label">任务状态</view>
<filter-radio :data="taskStatus" v-model="query.taskStatus" /> -->
</view>
</view>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="reset">重置</button>
<button type="primary" @tap="handleSearch" class="col-12">完成</button>
</view>
</view>
</uni-popup>
</template>
<script>
import { listTask, getTaskCount } from '@/api/task'
import { getDictLabelByArray } from '@/common/util'
import { getDictOptions } from '@/common/dict'
let _this;
let oldTasks = []
export default {
inheritAttrs: false,
data() {
return {
tabOptions: [
{
text: '我的待办(0)',
value: 'todo'
},
{
text: '我的已办(0)',
value: 'done'
}
],
query: {
size: 100,
current: 1,
taskStatus: this.$page.options.taskStatus
},
tasks: [],
searchFlag: false,
filterFlag: false,
loading: false,
taskTypes: [
{
text: '测酒任务',
value: 'testing_alcohol'
},
{
text: '督察任务',
value: 'inspection'
},
{
text: '所队自查',
value: 'selfexamination'
},
{
text: '问题',
value: 'negative'
},
],
taskStatus: [
{
text: '待签收',
value: 'tobe_signed'
},
{
text: '待处理',
value: 'tobe'
},
],
}
},
setup() {
const suspectProblems = getDictOptions('suspectProblem');
return {
suspectProblems,
getDictLabelByArray
}
},
watch: {
searchFlag(val) {
if (val) {
oldTasks = this.tasks;
this.tasks = []
} else {
this.tasks = oldTasks
}
},
'query.taskName': function(val) {
console.log(this)
if (_this.searchFlag && val) {
_this.getTasks()
}
},
'query.taskStatus': function(val) {
this.getTasks()
}
},
onLoad() {
_this = this;
},
onShow() {
this.getTasks()
},
methods: {
open(item) {
if (item.taskType === 'testing_alcohol') {
uni.navigateTo({
url: `/pages/task/testingAlcohol/people?taskId=${item.id}&taskStatus=${item.taskStatus}`
});
}
if (item.taskType === 'inspection') {
if (item.hasSign) {
uni.navigateTo({
url: `/pages/task/inspection/list?taskId=${item.id}`
});
} else {
uni.navigateTo({
url: `/pages/task/inspection/info?taskId=${item.id}`
});
}
}
if (item.taskType === 'selfexamination') {
uni.navigateTo({
url: `/pages/task/selfexamination/index?taskId=${item.id}`
});
}
if (item.taskType === 'negative') {
console.log(item.taskStatus)
if (item.taskStatus === 'todo') {
uni.navigateTo({
url: `/pages/negative/action?id=${item.negativeId}&workId=${item.id}`
});
} else {
uni.navigateTo({
url: `/pages/negative/info?id=${item.negativeId}`
});
}
}
},
getFlowLable(flowKey) {
const signFlows = ['second_sign']
if (signFlows.indexOf(flowKey) > -1) {
return '待签收'
}
const distributeFlows = ['first_distribute', 'second_distribute']
if (distributeFlows.indexOf(flowKey) > -1) {
return '待下发'
}
const verifyFlows = ['verify']
if (verifyFlows.indexOf(flowKey) > -1) {
return '核查办理'
}
const approveFlows = ['second_approve']
if (approveFlows.indexOf(flowKey) > -1) {
return '待审批'
}
return ''
},
async getTasks() {
this.loading = true
uni.showLoading({
title: '数据加载中'
});
listTask(this.query).then(data => {
this.tasks = data.records
this.loading = false
uni.hideLoading();
})
getTaskCount(this.query).then(data => {
this.tabOptions[0].text = `待办任务(${data.todoCount}`
this.tabOptions[1].text = `已办任务(${data.doneCount}`
})
},
cancleSearch() {
this.searchFlag = false
this.query.taskName = ''
},
showPopup() {
this.$refs.filterPopupRef.open()
},
closePopup() {
this.$refs.filterPopupRef.close()
},
search() {
if (this.query.taskType) {
this.filterFlag = true
} else {
this.filterFlag = false
}
this.getTasks()
},
handleSearch() {
this.search()
this.closePopup()
},
reset() {
this.filterFlag = false
let taskStatus = this.query.taskStatus;
this.query = {
size: 100,
current: 1,
taskStatus
}
this.getTasks()
}
}
}
</script>
<style lang="scss" scoped>
.task-item {
.header {
margin-bottom: 12rpx;
.title {
font-size: 28rpx;
line-height: 40rpx;
}
.date {
font-size: 24rpx;
color: #666;
}
}
}
</style>

42
pages/task/inspection/info.vue

@ -0,0 +1,42 @@
<template>
<view>
<view class="container" v-html="taskContentHtml"></view>
<view class="footer col-24">
<button type="primary" @click="handleSign">任务签收</button>
</view>
</view>
</template>
<script>
import {
getInspection,
signInspection
} from '@/api/inspection'
export default {
data() {
return {
taskContentHtml: ''
}
},
onLoad() {
getInspection(this.$page.options.taskId).then(data => {
this.taskContentHtml = data.taskContentHtml
})
},
methods: {
handleSign() {
signInspection(this.$page.options.taskId).then(() => {
uni.navigateTo({
url: '/pages/task/inspection/list?taskId=' + this.$page.options.taskId
});
})
}
}
}
</script>
<style lang="scss">
</style>

131
pages/task/inspection/list.vue

@ -0,0 +1,131 @@
<template>
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="flex gap-8 search-item-conent">
<uni-easyinput :style="{width: `60px`}" prefixIcon="search" type="text" v-model="query.taskName"
placeholder="搜索" :inputBorder="false" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag">
<view class="flex gap-8 search-item-conent">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="searchFlag = false">取消</view>
</view>
</view>
<view class="problem-container">
<view class="problem-item" v-for="item in problems" @tap="open(item.id)">
<view class="problem-item-img">
<net-image :filepath="item.files[0].filePath" />
</view>
<view class="problem-item-c">
<view class="row" style="--label-width: 160rpx">
<view class="col col-24" v-if="item.probmeType">
<view class="label">问题类型</view>
<view class="content">{{ item.probmeType }}</view>
</view>
<view class="col col-24">
<view class="label">被督察单位</view>
<view class="content">{{ item.departName }}</view>
</view>
<view class="col col-24" v-if="item.peoples.length > 0">
<view class="label">被督察人员</view>
<view class="content">{{ item.peoples?.map(p => p.name).join(' ') }}</view>
</view>
<view class="col col-24">
<view class="label">情况描述</view>
<view class="content">{{ item.thingDesc }}</view>
</view>
</view>
<view class="flex justify-between">
<view></view>
<view class="problem-item-time">{{ item.createTime }}</view>
</view>
</view>
</view>
<empty v-if="problems.length === 0" />
</view>
<view class="footer col-24">
<button type="primary" @click="handleAdd">添加记录</button>
</view>
</template>
<script setup>
import store from '@/store'
</script>
<script>
import {
listInspectionProblem
} from '@/api/inspection'
export default {
props: ['taskId'],
data() {
return {
searchFlag: false,
query: {},
taskContent: this.$page.options.taskContent,
problems: []
}
},
onLoad() {
},
onShow() {
listInspectionProblem(this.$page.options.taskId, this.query).then(data => {
this.problems = data.records
})
},
onBackPress(options) {
console.log(options)
return false
},
methods: {
handleAdd() {
uni.navigateTo({
url: '/pages/task/problem/add?taskId=' + this.$page.options.taskId
});
},
open(id) {
uni.navigateTo({
url: '/pages/task/problem/index?id=' + id
});
}
}
}
</script>
<style lang="scss" scoped>
.problem-container {
padding: 0 24rpx;
}
.problem-item {
display: flex;
gap: 0 20rpx;
padding: 24rpx 0;
box-shadow: inset 0 -1px 0 0 #DDDDDD;
.problem-item-img {
width: 128rpx;
height: 128rpx;
image {
width: 100%;
height: 100%;
}
}
.problem-item-c {
width: calc(100% - 148rpx);
}
.problem-item-time {
font-size: 12px;
color: #666;
}
}
</style>

138
pages/task/problem/add.vue

@ -0,0 +1,138 @@
<template>
<uni-forms ref="form" :modelValue="formData" :rules="rules" label-width="210rpx" style="margin: 12px">
<uni-forms-item label="附件" name="files" label-position="top" required>
<upload v-model="formData.files" />
</uni-forms-item>
<uni-forms-item label="是否存在问题" name="hasProblem" required>
<uni-data-checkbox v-model="formData.hasProblem" :localdata="hasProblem" />
</uni-forms-item>
<uni-forms-item label="关联督察任务" v-if="needSeelctTaskFlag">
<inspection-task-data-picker v-model="formData.taskId" />
</uni-forms-item>
<uni-forms-item label="被督察单位" name="departId" required>
<uni-data-picker :localdata="departs" placeholder="请选择被督察单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="formData.departId" />
</uni-forms-item>
<uni-forms-item label="问题类型" name="problemTypeCode" v-if="formData.hasProblem === true">
<problem-picker v-model="formData.problemTypeCode" @nodeclick="(node) => formData.problemType = node.name" />
</uni-forms-item>
<uni-forms-item label="被督察人员" name="peoples" v-if="formData.hasProblem === true">
<police-picker v-model="formData.peoples" :departId="formData.departId" />
</uni-forms-item>
<uni-forms-item label="情况描述" name="thingDesc" label-position="top" required v-if="formData.hasProblem === true">
<uni-easyinput type="textarea" :input-border="false" placeholder="请输入具体情况" v-model="formData.thingDesc" />
</uni-forms-item>
</uni-forms>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="back">取消</button>
<button type="primary" @tap="submit" class="col-12">确定</button>
</view>
</template>
<script>
import {
departTree
} from '@/api/depart'
import {
addProblem
} from '@/api/taskProblem'
let _this;
export default {
data() {
return {
formData: {
files: this.$page.options.files? JSON.parse(this.$page.options.files) : [],
taskId: this.$page.options.taskId || ''
},
hasProblem: [
{
text: '是',
value: true
},
{
text: '否',
value: false
},
],
departs: [],
rules: {
files: {
rules: [{
validateFunction: function(rule,value,data,callback){
if (!value || value.length === 0) {
callback('请上传附件')
}
return true
}
}]
},
hasProblem: {
rules: [{
required: true,
errorMessage: '请选择是否存在督察问题',
}]
},
departId: {
rules: [{
required: true,
errorMessage: '请选择被督察单位',
}]
},
thingDesc: {
rules: [{
required: true,
errorMessage: '请输入具体情况',
}]
},
taskId: {
rules: [{
required: true,
errorMessage: '请关联督察任务',
}]
}
},
needSeelctTaskFlag: true
}
},
onLoad() {
_this = this;
if (this.$page.options.taskId) {
this.needSeelctTaskFlag = false
}
departTree().then(data => {
this.departs = data[0].children
})
},
methods: {
submit() {
this.$refs.form.validate().then(res => {
addProblem(this.formData).then(data => {
_this.formData = {
files: [],
taskId: _this.$page.options.taskId || ''
}
uni.showToast({
title: '新增成功',
icon: 'none',
duration: 5000
})
uni.navigateBack({
});
})
}).catch(err => {
console.log('表单错误信息:', err, this.formData);
})
},
back() {
uni.navigateBack({
});
}
}
}
</script>
<style>
</style>

89
pages/task/problem/index.vue

@ -0,0 +1,89 @@
<template>
<view class="container">
<view class="row" style="--label-width: 180rpx">
<view class="col col-24">
<view class="label">是否存在问题</view>
<view class="content">{{ problem.hasProblem ? '是' : '否' }}</view>
</view>
<view class="col col-24">
<view class="label">问题来源</view>
<view class="content">{{ getDictLabel(taskType, problem.taskType) }}</view>
</view>
<view class="col col-24">
<view class="label">录入时间</view>
<view class="content">{{ problem.createTime }}</view>
</view>
<view class="col col-24">
<view class="label">被督察单位</view>
<view class="content">{{ problem.departName }}</view>
</view>
<view class="col col-24">
<view class="label">被督察人员</view>
<view class="content">{{ problem.peoples ? JSON.parse(problem.peoples).map(item => item.name).join('、') : '/' }}</view>
</view>
<view class="col col-24" v-if="problem.thingDesc">
<view class="label">情况描述</view>
<view class="content">{{ problem.thingDesc }}</view>
</view>
<view class="col col-24">
<view class="label">是否下发</view>
<view class="content" v-if="problem.distributionState === '0'" style="color: red">未下发</view>
<view class="content" v-if="problem.distributionState === '1'">已下发</view>
</view>
</view>
<view class="mt-10 mb-6" style="font-size: 14px; color: #666">附件</view>
<view class="photo-container" v-if="problem.files">
<view v-for="photo in JSON.parse(problem.files)" class="photo-item">
<net-image :filepath="photo.filePath" />
</view>
</view>
</view>
</template>
<script>
import { getProblem } from '@/api/taskProblem';
import { getDictOptions } from '@/common/dict'
import { getDictLabel } from '@/common/util'
let _this;
export default {
inheritAttrs: false,
data() {
return {
problem: {}
}
},
setup() {
const taskType = getDictOptions('taskType');
return {
taskType,
getDictLabel
}
},
onLoad() {
_this = this;
getProblem(this.$page.options.id).then(data => {
this.problem = data
})
},
methods: {
}
}
</script>
<style scoped>
.info {
padding: 12px;
box-shadow: inset 0 -1px 0 0 #eee;
}
.police-info-avatar {
width: 136rpx;
height: 205rpx;
image {
width: 100%;
height: 100%;
}
}
</style>

201
pages/task/problem/list.vue

@ -0,0 +1,201 @@
<template>
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="search-item-conent">
<uni-easyinput prefixIcon="search" type="text" v-model="query.thingDesc" placeholder="搜索" :inputBorder="false" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag" @tap="showPopup">
<view :class="filterFlag ? 'search-item-conent active': 'search-item-conent'">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="searchFlag = false">取消</view>
</view>
</view>
<view class="negative-container">
<view class="card negative-item" v-for="item in problems" @tap="open(item)">
<view class="row">
<view class="col col-24">
<view class="label">录入时间</view>
<view class="content">{{ item.createTime }}</view>
</view>
<view class="col col-12">
<view class="label">涉及单位</view>
<view class="content">{{ item.departName }}</view>
</view>
<view class="col col-12">
<view class="label">涉及人员</view>
<view class="content">{{ item.peoples || '/' }}</view>
</view>
<view class="col col-24">
<view class="label">问题类型</view>
<view class="content">{{ item.problemType || '/' }}</view>
</view>
<view class="col col-24">
<view class="label">问题内容</view>
<view class="content">{{ item.thingDesc }}</view>
</view>
</view>
<view class="negative-bottom">
<view v-if="item.distributionState === '0'" style="color: var(--danger-color)">未下发</view>
<view v-if="item.distributionState === '1'" style="color: #ff5722">已下发</view>
</view>
</view>
<empty v-if="problems.length === 0" />
</view>
<uni-popup ref="filterPopupRef" type="top">
<view class="popup-container">
<view class="popup-header">
<view>全部筛选</view>
<uni-icons type="closeempty" class="close-btn" @tap="closePopup"></uni-icons>
</view>
<view class="popup-body">
<view class="filter-container">
<view class="filter-label mt-10">涉及单位</view>
<view class="mb-10">
<uni-data-picker :localdata="departs" placeholder="请选择涉及单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="query.departId" @change="search" />
</view>
</view>
</view>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="reset">重置</button>
<button type="primary" @tap="handleSearch" class="col-12">完成</button>
</view>
</view>
</uni-popup>
</template>
<script>
import store from '@/store'
import { getDictOptions } from '@/common/dict'
import { listProblem } from '@/api/taskProblem.js'
import {
departTree
} from '@/api/depart'
import { getDictLabel } from '@/common/util'
let oldList;
let _this;
export default {
data() {
return {
problems: [],
query: {
size: 100,
current: 1,
taskType: 'problem_shooting'
},
departs: [],
searchFlag: false,
filterFlag: false,
}
},
setup(props, context) {
const taskType = getDictOptions('taskType');
return {
taskType,
getDictLabel
}
},
watch: {
searchFlag(val) {
if (val) {
oldList = this.problems;
this.problems = []
} else {
this.problems = oldList
}
},
'query.thingDesc': function(val) {
if (_this.searchFlag) {
if (val) {
_this.getProblems()
} else {
this.problems = []
}
}
}
},
onLoad() {
_this = this;
this.getProblems();
departTree().then(data => {
this.departs = data[0].children
})
},
methods: {
open(item) {
uni.navigateTo({
url: '/pages/task/problem/index?id=' + item.id
});
},
getProblems() {
uni.showLoading({
title: '数据加载中'
});
listProblem(this.query).then(data => {
this.problems = data.records
uni.hideLoading();
});
},
showPopup() {
this.$refs.filterPopupRef.open()
},
closePopup() {
this.$refs.filterPopupRef.close()
},
search() {
if (this.query.taskType || this.query.departId) {
this.filterFlag = true
} else {
this.filterFlag = false
}
this.getProblems()
},
handleSearch() {
this.search()
this.closePopup()
},
reset() {
this.filterFlag = false
this.query = {
size: 100,
current: 1,
taskType: 'problem_shooting'
}
this.getProblems()
}
}
}
</script>
<style lang="scss" scoped>
.negative-container {
max-height: calc(100vh - 98rpx);
overflow: auto;
}
.negative-title {
display: flex;
justify-content: space-between;
margin-bottom: 12rpx;
font-weight: 500;
font-size: 16px;
}
.negative-bottom {
display: flex;
justify-content: space-between;
.negative-tag {
color: #FF0000;
}
}
</style>

106
pages/task/selfexamination/add.vue

@ -0,0 +1,106 @@
<template>
<uni-forms ref="form" :modelValue="formData" :rules="rules" label-width="210rpx" style="margin: 12px">
<uni-forms-item label="附件" name="files" label-position="top" required>
<upload v-model="formData.files" />
</uni-forms-item>
<uni-forms-item label="是否存在问题" name="hasProblem" required>
<uni-data-checkbox v-model="formData.hasProblem" :localdata="hasProblem" />
</uni-forms-item>
<uni-forms-item label="涉及人员" name="peoples" v-if="formData.hasProblem === true">
<police-picker v-model="formData.peoples" :departId="formData.departId" />
</uni-forms-item>
<uni-forms-item label="检查情况" name="thingDesc" label-position="top" required v-if="formData.hasProblem === true">
<uni-easyinput type="textarea" :input-border="false" placeholder="请输入检查情况" v-model="formData.thingDesc" />
</uni-forms-item>
</uni-forms>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="back">取消</button>
<button type="primary" @tap="submit" class="col-12">确定</button>
</view>
</template>
<script>
import {
departTree
} from '@/api/depart'
import {
addProblem
} from '@/api/taskProblem.js'
let _this;
export default {
data() {
return {
formData: {
files: this.$page.options.files? JSON.parse(this.$page.options.files) : [],
taskId: this.$page.options.taskId || '',
contentId: this.$page.options.contentId,
departId: this.$page.options.departId
},
hasProblem: [
{
text: '是',
value: true
},
{
text: '否',
value: false
},
],
rules: {
files: {
rules: [{
validateFunction: function(rule,value,data,callback){
if (!value || value.length === 0) {
callback('请上传附件')
}
return true
}
}]
},
hasProblem: {
rules: [{
required: true,
errorMessage: '请选择是否存在问题',
}]
},
thingDesc: {
rules: [{
required: true,
errorMessage: '请输入检测情况',
}]
},
},
needSeelctTaskFlag: true
}
},
onLoad() {
_this = this;
},
methods: {
submit() {
this.$refs.form.validate().then(res => {
addProblem(this.formData.taskId, this.formData).then(data => {
_this.formData = {
files: [],
taskId: _this.$page.options.taskId || ''
}
uni.navigateBack({
});
})
}).catch(err => {
console.log('表单错误信息:', err, this.formData);
})
},
back() {
uni.navigateBack({
});
}
}
}
</script>
<style>
</style>

85
pages/task/selfexamination/index.vue

@ -0,0 +1,85 @@
<template>
<view v-if="!selfexamination.hasSign">
<view class="container" v-html="selfexamination.requirementHtml"></view>
<view class="footer col-24">
<button type="primary" @click="handleSign">任务签收</button>
</view>
</view>
<view v-else>
<view v-for="(item, index) in selfexamination.contents" class="content-item" @tap="open(item.id)">
<view class="content-item-title">
<text>{{ index + 1 }} {{ item.title }}</text>
<uni-tag type="warning" text="待检查" :inverted="true" v-if="item.status === 'todo'" />
<uni-tag type="success" text="已检查" :inverted="true" v-else />
</view>
<view class="content-item-c">{{ item.content }}</view>
</view>
</view>
</template>
<script>
import {
getTaskSelfexamination,
signTaskSelfexamination
} from '@/api/selfexamination'
import { submitTask } from '@/api/task'
let _this;
export default {
data() {
return {
selfexamination: {}
}
},
onLoad() {
_this = this;
},
onShow() {
getTaskSelfexamination(this.$page.options.taskId).then(data => {
this.selfexamination = data
if (data.contents.filter(item => item.status === 'todo').length === 0) {
uni.showModal({
content: `本次自查内容以全部检查,\n请确认是否结束本次任务`,
success: function (res) {
if (res.confirm) {
submitTask(_this.$page.options.taskId).then(data => {
uni.navigateTo({
url: '/pages/task/index?taskStatus=todo'
});
})
}
}
});
}
})
},
methods: {
async handleSign() {
await signTaskSelfexamination(this.$page.options.taskId)
this.selfexamination.hasSign = true
},
open(conentId) {
uni.navigateTo({
url: `/pages/task/selfexamination/add?taskId=${this.$page.options.taskId}&contentId=${conentId}&departId=${this.selfexamination.supDepartId}`
});
}
}
}
</script>
<style lang="scss" scoped>
.content-item {
padding: 24rpx 0;
margin: 0 24rpx;
box-shadow: inset 0 -1px 0 0 #DDDDDD;
.content-item-title {
margin-bottom: 12rpx;
display: flex;
justify-content: space-between;
}
.content-item-c {
font-size: 12px;
color: #666;
}
}
</style>

82
pages/task/selfexamination/info.vue

@ -0,0 +1,82 @@
<template>
<view class="container">
<view class="row" style="--label-width: 180rpx">
<view class="col col-24">
<view class="label">任务名称</view>
<view class="content">{{ task.taskName }}</view>
</view>
<view class="col col-24">
<view class="label">自查类型</view>
<view class="content">{{ task.type }}</view>
</view>
<view class="col col-24">
<view class="label">自查单位</view>
<view class="content">{{ task.supDepartName }}</view>
</view>
<view class="col col-24">
<view class="label">自查时间</view>
<view class="content">{{ task.beginTime }}</view>
</view>
<view class="col col-24">
<view class="label">任务状态</view>
<view class="content">
<uni-tag text="自查中" type="primary" v-if="task.taskStatus === 'todo'"></uni-tag>
<uni-tag text="已完结" v-else></uni-tag>
</view>
</view>
</view>
<view class="h1">任务要求</view>
<view v-html="task.requirementHtml"></view>
<view class="h1">自查内容</view>
<view v-for="(item, index) in task.contents" class="content-item">
<view class="content-item-title">{{ index + 1 }} {{ item.title }}</view>
<view class="content-item-c">{{ item.content }}</view>
</view>
</view>
</template>
<script>
import { getTaskSelfexamination } from '@/api/selfexamination.js';
import { getDictOptions } from '@/common/dict'
import { getDictLabel } from '@/common/util'
let _this;
export default {
inheritAttrs: false,
data() {
return {
task: {}
}
},
setup() {
const taskType = getDictOptions('taskType');
return {
taskType,
getDictLabel
}
},
onLoad() {
_this = this;
getTaskSelfexamination(this.$page.options.id).then(data => {
this.task = data
})
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.h1 {
color: #ff5722;
margin-top: 24rpx;
margin-bottom: 12rpx;
font-weight: bold;
}
.content-item {
margin-bottom: 12rpx;
}
</style>

193
pages/task/selfexamination/list.vue

@ -0,0 +1,193 @@
<template>
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="search-item-conent">
<uni-easyinput prefixIcon="search" type="text" v-model="query.taskName" placeholder="搜索" :inputBorder="false" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag" @tap="showPopup">
<view :class="filterFlag ? 'search-item-conent active': 'search-item-conent'">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="searchFlag = false">取消</view>
</view>
</view>
<view class="negative-container">
<view class="card negative-item" v-for="item in list" @tap="open(item)">
<view class="negative-title">
<view>{{ item.taskName }}</view>
<view></view>
</view>
<view class="row">
<view class="col col-24">
<view class="label">自查类型</view>
<view class="content">{{ item.type }}</view>
</view>
<view class="col col-24">
<view class="label">自查单位</view>
<view class="content">{{ item.supDepartName }}</view>
</view>
<view class="col col-24">
<view class="label">自查时间</view>
<view class="content">{{ item.beginTime }} ~ {{ item.endTime }}</view>
</view>
</view>
<view class="negative-bottom">
<uni-tag text="自查中" type="primary" v-if="item.taskStatus === 'todo'"></uni-tag>
<uni-tag text="已完结" v-else></uni-tag>
</view>
</view>
<empty v-if="list.length === 0" />
</view>
<uni-popup ref="filterPopupRef" type="top">
<view class="popup-container">
<view class="popup-header">
<view>全部筛选</view>
<uni-icons type="closeempty" class="close-btn" @tap="closePopup"></uni-icons>
</view>
<view class="popup-body">
<view class="filter-container">
<view class="filter-label mt-10">自查单位</view>
<view class="mb-10">
<uni-data-picker :localdata="departs" placeholder="请选择自查单位" :border="false"
:map="{text:'shortName', value: 'id'}" v-model="query.supDepartId" @change="search" />
</view>
</view>
</view>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="reset">重置</button>
<button type="primary" @tap="handleSearch" class="col-12">完成</button>
</view>
</view>
</uni-popup>
</template>
<script>
import store from '@/store'
import { getDictOptions } from '@/common/dict'
import { listTaskSelfexamination } from '@/api/selfexamination.js'
import {
departTree
} from '@/api/depart'
import { getDictLabel } from '@/common/util'
let oldList;
let _this;
export default {
data() {
return {
list: [],
query: {
size: 100,
current: 1
},
departs: [],
searchFlag: false,
filterFlag: false
}
},
setup(props, context) {
return {
getDictLabel
}
},
watch: {
searchFlag(val) {
if (val) {
oldList = this.list;
this.list = []
} else {
this.list = oldList
}
},
'query.taskName': function(val) {
if (_this.searchFlag) {
if (val) {
_this.getList()
} else {
this.list = []
}
}
}
},
onLoad() {
_this = this;
this.getList();
departTree().then(data => {
this.departs = data[0].children
})
},
methods: {
open(item) {
uni.navigateTo({
url: `/pages/task/selfexamination/info?id=${item.id}`
});
},
getList() {
uni.showLoading({
title: '数据加载中'
});
listTaskSelfexamination(this.query).then(data => {
this.list = data.records
uni.hideLoading();
});
},
showPopup() {
this.$refs.filterPopupRef.open()
},
closePopup() {
this.$refs.filterPopupRef.close()
},
search() {
if (this.query.supDepartId) {
this.filterFlag = true
} else {
this.filterFlag = false
}
this.getList()
},
handleSearch() {
this.search()
this.closePopup()
},
reset() {
this.filterFlag = false
this.query = {
size: 100,
current: 1
}
this.getList()
}
}
}
</script>
<style lang="scss" scoped>
.negative-container {
max-height: calc(100vh - 98rpx);
overflow: auto;
}
.negative-title {
display: flex;
justify-content: space-between;
margin-bottom: 12rpx;
font-weight: 500;
font-size: 16px;
}
.negative-bottom {
display: flex;
justify-content: space-between;
.negative-tag {
color: #FF0000;
}
}
</style>

193
pages/task/testingAlcohol/add.vue

@ -0,0 +1,193 @@
<template>
<view class="flex gap-16 info">
<view class="police-info-avatar">
<net-image :filepath="people.avatarUrl" v-if="people.avatarUrl" />
<image src="/static/police.png" v-else></image>
</view>
<view style="width: calc(100% - 168rpx)">
<view>
<view class="row">
<view class="col col-12">
<view class="label">姓名</view>
<view class="content">{{ people.name }}</view>
</view>
<view class="col col-12">
<view class="label">警号</view>
<view class="content">{{ people.empNo }}</view>
</view>
<view class="col col-12">
<view class="label">所属单位</view>
<view class="content">{{ people.departName }}</view>
</view>
<view class="col col-12">
<view class="label">职位</view>
<view class="content">{{ people.position }}</view>
</view>
<view class="col col-24">
<view class="label">联系电话</view>
<view class="content">{{ people.mobile || '/' }}</view>
</view>
</view>
</view>
<view style="color: red">待检测</view>
</view>
</view>
<uni-forms ref="form" :modelValue="formData" :rules="rules" label-width="80px" style="margin: 12px">
<uni-forms-item label="检测时间" name="testingTime" required>
<uni-datetime-picker type="datetime" v-model="formData.testingTime" :border="false" placeholder="请选择检测时间" />
</uni-forms-item>
<uni-forms-item label="检测情况" name="testingResult" required>
<uni-data-checkbox v-model="formData.testingResult" :localdata="testingResult" />
</uni-forms-item>
<uni-forms-item label="测酒结果" name="drinkResult" required v-if="formData.testingResult === '已检测'">
<uni-data-checkbox v-model="formData.drinkResult" :localdata="drinkResult" />
</uni-forms-item>
<uni-forms-item label="酒精含量" name="alcoholContent" required v-if="formData.testingResult === '已检测' && formData.drinkResult === '饮酒'">
<uni-easyinput type="number" v-model="formData.alcoholContent" placeholder="请输入酒精含量" :inputBorder="false" />
</uni-forms-item>
<uni-forms-item label="测酒照片" name="testingFiles" label-position="top" required
v-if="formData.testingResult === '已检测'">
<upload v-model="formData.testingFiles" />
</uni-forms-item>
<uni-forms-item label="情况说明" name="unTestingDesc" required v-if="formData.testingResult === '未检测'">
<uni-easyinput type="textarea" v-model="formData.unTestingDesc" placeholder="请输入未检测现场情况说明"
:inputBorder="false" />
</uni-forms-item>
</uni-forms>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="back">取消</button>
<button type="primary" @tap="submit" class="col-12">确定</button>
</view>
</template>
<script>
import store from '@/store'
import { now } from '@/common/util'
import {
updateTestingAlcoholPeople
} from '@/api/testingAlcohol'
let _this;
export default {
data() {
return {
BASE_PATH: store.state.fileRequestUrl,
people: JSON.parse(this.$page.options.people),
formData: {
testingTime: now('YYYY-MM-DD HH:mm:ss'),
testingFiles: []
},
rules: {
testingTime: {
rules: [
{
required: true,
errorMessage: '请选择检测时间',
}
]
},
testingResult: {
rules: [
{
required: true,
errorMessage: '请选择检测情况',
}
]
},
drinkResult: {
rules: [
{
required: true,
errorMessage: '请选择测酒结果',
}
]
},
alcoholContent: {
rules: [
{
required: true,
errorMessage: '请输入酒精含量',
}
]
},
unTestingDesc: {
rules: [
{
required: true,
errorMessage: '请输入未检测现场情况说明',
}
]
},
testingFiles: {
rules: [{
validateFunction: function(rule,value,data,callback){
if (!value || value.length === 0) {
callback('请上传测酒现场照片')
}
return true
}
}]
},
},
testingResult: [{
text: '已检测',
value: '已检测'
},
{
text: '未检测',
value: '未检测'
}
],
drinkResult: [{
text: '未饮酒',
value: '未饮酒'
},
{
text: '饮酒',
value: '饮酒'
},
]
}
},
onLoad() {
_this = this;
},
methods: {
submit() {
this.$refs.form.validate().then(res=>{
this.formData.empNo = this.people.empNo
updateTestingAlcoholPeople(this.people.taskId, this.formData).then(data => {
uni.navigateBack({
});
})
}).catch(err =>{
console.log('表单错误信息:', err);
})
},
back() {
uni.navigateBack({
});
}
}
}
</script>
<style lang="scss">
.info {
padding: 12px;
box-shadow: inset 0 -1px 0 0 #eee;
}
.police-info-avatar {
width: 136rpx;
height: 205rpx;
image {
width: 100%;
height: 100%;
}
}
</style>

104
pages/task/testingAlcohol/info.vue

@ -0,0 +1,104 @@
<template>
<view class="flex gap-16 info">
<view class="police-info-avatar">
<net-image :filepath="people.avatarUrl" v-if="people.avatarUrl" />
<image src="/static/police.png" v-else></image>
</view>
<view style="width: calc(100% - 168rpx)">
<view>
<view class="row">
<view class="col col-12">
<view class="label">姓名</view>
<view class="content">{{ people.name }}</view>
</view>
<view class="col col-12">
<view class="label">警号</view>
<view class="content">{{ people.empNo }}</view>
</view>
<view class="col col-12">
<view class="label">所属单位</view>
<view class="content">{{ people.departName }}</view>
</view>
<view class="col col-12">
<view class="label">职位</view>
<view class="content">{{ people.position }}</view>
</view>
<view class="col col-24">
<view class="label">联系电话</view>
<view class="content">{{ people.mobile || '/' }}</view>
</view>
</view>
</view>
</view>
</view>
<view class="container">
<view class="row">
<view class="col col-24">
<view class="label">测酒时间</view>
<view class="content">{{ people.testingTime }}</view>
</view>
<view class="col col-12">
<view class="label">检测情况</view>
<view class="content">
<view v-if="people.testingResult === '未检测'" style="color: var(--danger-color)">{{ people.testingResult }}</view>
<view v-else>{{ people.testingResult }}</view>
</view>
</view>
<view class="col col-12">
<view class="label">饮酒结果</view>
<view class="content">
<uni-tag :text="people.drinkResult" type="success" v-if="people.drinkResult === '未饮酒'" />
<uni-tag :text="people.drinkResult" type="error" v-else />
</view>
</view>
<view class="col col-12" v-if="people.alcoholContent">
<view class="label">酒精含量</view>
<view class="content">{{ people.alcoholContent }}</view>
</view>
<view class="col col-24" v-if="people.unTestingDesc">
<view class="label">未检测原因</view>
<view class="content">{{ people.unTestingDesc }}</view>
</view>
</view>
</view>
</template>
<script>
import store from '@/store'
import { now } from '@/common/util'
import {
updateTestingAlcoholPeople
} from '@/api/testingAlcohol'
let _this;
export default {
data() {
return {
BASE_PATH: store.state.fileRequestUrl,
people: JSON.parse(this.$page.options.people)
}
},
onLoad() {
_this = this;
},
methods: {
}
}
</script>
<style lang="scss">
.info {
padding: 12px;
box-shadow: inset 0 -1px 0 0 #eee;
}
.police-info-avatar {
width: 136rpx;
height: 205rpx;
image {
width: 100%;
height: 100%;
}
}
</style>

293
pages/task/testingAlcohol/people.vue

@ -0,0 +1,293 @@
<template>
<tabs :options="tabOptions" v-model="query.status" v-if="!searchFlag && taskStatus === 'todo'" />
<view class="flex search">
<view class="search-item flex justify-center" :search="searchFlag">
<view class="search-item-conent">
<uni-easyinput prefixIcon="search" type="text" v-model="query.queryTxt" placeholder="搜索" :inputBorder="false" @focus="searchFlag = true" />
</view>
</view>
<view class="search-item flex justify-center" v-if="!searchFlag" @tap="showPopup">
<view :class="filterFlag ? 'search-item-conent active': 'search-item-conent'">
<uni-icons customPrefix="customicons" type="filter" size="20" color="#666" />
<view>筛选</view>
</view>
</view>
<view v-else>
<view class="cancel-btn" @tap="searchFlag = false">取消</view>
</view>
</view>
<view class="people-container">
<view class="flex gap-16 card" v-for="item in peoples" @tap="open(item)">
<view class="people-img">
<net-image :filepath="item.avatarUrl" v-if="item.avatarUrl" />
<image src="/static/police.png" v-else></image>
</view>
<view style="width: calc(100% - 84px)">
<view>
<view class="row">
<view class="col col-12">
<view class="label">姓名</view>
<view class="content">{{ item.name }}</view>
</view>
<view class="col col-12">
<view class="label">警号</view>
<view class="content">{{ item.empNo }}</view>
</view>
<view class="col col-12">
<view class="label">所属单位</view>
<view class="content">{{ item.departName }}</view>
</view>
<view class="col col-12">
<view class="label">职位</view>
<view class="content">{{ item.position }}</view>
</view>
<view class="col col-24">
<view class="label">联系电话</view>
<view class="content">{{ item.mobile || '/' }}</view>
</view>
<template v-if="item.status === 'done'">
<view class="col col-24">
<view class="label">测酒时间</view>
<view class="content">{{ item.testingTime }}</view>
</view>
<view class="col col-12">
<view class="label">检测情况</view>
<view class="content">
<view v-if="item.testingResult === '未检测'" style="color: var(--danger-color)">{{ item.testingResult }}</view>
<view v-else>{{ item.testingResult }}</view>
</view>
</view>
<view class="col col-12">
<view class="label">饮酒结果</view>
<view class="content">
<uni-tag :text="item.drinkResult" type="success" v-if="item.drinkResult === '未饮酒'" />
<uni-tag :text="item.drinkResult" type="error" v-else />
</view>
</view>
<view class="col col-12" v-if="item.alcoholContent">
<view class="label">酒精含量</view>
<view class="content">{{ item.alcoholContent }}</view>
</view>
<view class="col col-24" v-if="item.unTestingDesc">
<view class="label">未检测原因</view>
<view class="content">{{ item.unTestingDesc }}</view>
</view>
</template>
</view>
</view>
<view style="color: #FF0000;" v-if="item.status === 'todo'">待检测</view>
</view>
</view>
<empty v-if="peoples.length === 0" />
</view>
<uni-popup ref="filterPopupRef" type="top">
<view class="popup-container">
<view class="popup-header">
<view>全部筛选</view>
<uni-icons type="closeempty" class="close-btn" @tap="closePopup"></uni-icons>
</view>
<view class="popup-body">
<view class="filter-container">
<view class="filter-label">职位</view>
<filter-radio :data="positions" v-model="query.position" @change="search" />
<view v-if="query.status === 'done'">
<view class="filter-label">检测情况</view>
<filter-radio :data="testingResults" v-model="query.testingResult" @change="search" />
</view>
<view v-if="query.status === 'done'">
<view class="filter-label">饮酒结果</view>
<filter-radio :data="drinkResults" v-model="query.drinkResult" @change="search" />
</view>
</view>
</view>
<view class="footer col-24 flex gap-8">
<button class="col-12" @tap="reset">重置</button>
<button type="primary" @tap="handleSearch" class="col-12">完成</button>
</view>
</view>
</uni-popup>
</template>
<script>
import store from '@/store'
import { listTestingAlcoholPeople, countTestingAlcoholPeople } from '@/api/testingAlcohol'
import { submitTask } from '@/api/task'
let oldPeoples;
let _this;
export default {
inheritAttrs: false,
data() {
return {
BASE_PATH: store.state.fileRequestUrl,
tabOptions: [
{
text: '待测人员(0)',
value: 'todo'
},
{
text: '已测人员(0)',
value: 'done'
}
],
searchFlag: false,
peoples: [],
query: {
size: 100,
current: 1,
status: this.$page.options.taskStatus
},
taskStatus: this.$page.options.taskStatus,
filterFlag: false,
positions: [
{
text: '正职',
value: '正职'
},
{
text: '副职',
value: '副职'
},
],
testingResults: [
{
text: '已检测',
value: '已检测'
},
{
text: '未检测',
value: '未检测'
},
],
drinkResults: [
{
text: '饮酒',
value: '饮酒'
},
{
text: '未饮酒',
value: '未饮酒'
},
]
}
},
watch: {
searchFlag(val) {
if (val) {
oldPeoples = this.peoples;
this.peoples = []
} else {
this.peoples = oldPeoples
}
},
'query.queryTxt': function(val) {
if (_this.searchFlag) {
if (val) {
_this.getPeoples()
} else {
this.peoples = []
}
}
},
'query.status': function(val) {
if (val === 'todo') {
this.query.testingResult = ''
this.query.drinkResult = ''
}
this.getPeoples()
}
},
onLoad() {
_this = this;
},
onShow() {
console.log('onShow')
this.getPeoples()
},
methods: {
open(item) {
if (item.status === 'todo') {
uni.navigateTo({
url: '/pages/task/testingAlcohol/add?people=' + JSON.stringify(item)
});
}
if (item.status === 'done') {
uni.navigateTo({
url: '/pages/task/testingAlcohol/info?people=' + JSON.stringify(item)
});
}
},
getPeoples() {
listTestingAlcoholPeople(this.$page.options.taskId, this.query).then(data => {
this.peoples = data.records
});
countTestingAlcoholPeople(this.$page.options.taskId).then(data => {
this.tabOptions[0].text = `待测人员(${data.todoCount}`
this.tabOptions[1].text = `已测人员(${data.doneCount}`
if (data.todoCount === 0 && this.query.status === 'todo' && this.taskStatus === 'todo') {
uni.showModal({
content: `本次检测人数${data.doneCount}\n请确认是否提交本次检测结果并结束任务`,
confirmText: '提交',
success: function (res) {
if (res.confirm) {
console.log('用户点击确定');
submitTask(_this.$page.options.taskId).then(data => {
uni.navigateTo({
url: '/pages/task/index?taskStatus=todo'
});
})
}
}
});
}
})
},
showPopup() {
this.$refs.filterPopupRef.open()
},
closePopup() {
this.$refs.filterPopupRef.close()
},
search() {
this.filterFlag = !!this.query.position || !!this.query.testingResult || !!this.query.drinkResult
console.log(this.filterFlag)
this.getPeoples()
},
handleSearch() {
this.search()
this.closePopup()
},
reset() {
this.filterFlag = false
let taskStatus = this.query.taskStatus;
this.query = {
size: 10,
current: 1,
taskStatus
}
this.getPeoples()
}
}
}
</script>
<style lang="scss">
.people-container {
max-height: calc(100vh - 96px);
/* #ifdef H5 */
max-height: calc(100vh - 140px);
/* #endif */
overflow: auto;
}
.people-img {
width: 136rpx;
height: 205rpx;
image {
width: 100%;
height: 100%;
}
}
</style>

232
platforms/app-plus/feedback/feedback.vue

@ -0,0 +1,232 @@
<template>
<view class="page">
<view class="feedback-title">
<text>问题和意见</text>
<text class="feedback-quick" @tap="chooseMsg">快速键入</text>
</view>
<view class="feedback-body"><textarea placeholder="请详细描述你的问题和意见..." v-model="sendDate.content" class="feedback-textare"></textarea></view>
<view class="feedback-title"><text>图片(选填,提供问题截图,总大小10M以下)</text></view>
<view class="feedback-body feedback-uploader">
<view class="uni-uploader">
<view class="uni-uploader-head">
<view class="uni-uploader-title">点击预览图片</view>
<view class="uni-uploader-info">{{ imageList.length }}/5</view>
</view>
<view class="uni-uploader-body">
<view class="uni-uploader__files">
<block v-for="(image, index) in imageList" :key="index">
<view class="uni-uploader__file" style="position: relative;">
<image class="uni-uploader__img" :src="image" @tap="previewImage(index)"></image>
<view class="close-view" @click="close(index)">x</view>
</view>
</block>
<view class="uni-uploader__input-box" v-show="imageList.length < 5"><view class="uni-uploader__input" @tap="chooseImg"></view></view>
</view>
</view>
</view>
</view>
<view class="feedback-title"><text>QQ/邮箱</text></view>
<view class="feedback-body"><input class="feedback-input" v-model="sendDate.contact" placeholder="(选填,方便我们联系你 )" /></view>
<view class="feedback-title feedback-star-view">
<text>应用评分</text>
<view class="feedback-star-view"><uni-rate v-model="sendDate.score" color="#bbb"></uni-rate></view>
</view>
<button type="default" class="feedback-submit" @tap="send">提交</button>
<view class="feedback-title"><text>用户反馈的结果可在app打包后于DCloud开发者中心查看</text></view>
</view>
</template>
<script>
export default {
data() {
return {
msgContents: ['界面显示错乱', '启动缓慢,卡出翔了', 'UI无法直视,丑哭了', '偶发性崩溃'],
stars: [1, 2, 3, 4, 5],
imageList: [],
sendDate: {
score: 0,
content: '',
contact: ''
}
};
},
onLoad() {
this.deviceInfo = {
// appid: plus.runtime.appid,
appid: '__UNI__5D0B0CA',
imei: plus.device.imei, //
p: plus.os.name === 'Android' ? 'a' : 'i', //iiOSaAndroid
md: plus.device.model, //
app_version: plus.runtime.version,
plus_version: plus.runtime.innerVersion, //
os: plus.os.version,
net: '' + plus.networkinfo.getCurrentType()
};
this.sendDate = Object.assign(this.deviceInfo, this.sendDate);
},
methods: {
/**
* 关闭图片
* @param {Object} e
*/
close(e) {
this.imageList.splice(e, 1);
},
/**
* 快速输入
*/
chooseMsg() {
uni.showActionSheet({
itemList: this.msgContents,
success: res => {
this.sendDate.content = this.msgContents[res.tapIndex];
}
});
},
/**
* 选择图片
*/
chooseImg() {
//
uni.chooseImage({
sourceType: ['camera', 'album'],
sizeType: 'compressed',
count: 5 - this.imageList.length,
success: res => {
this.imageList = this.imageList.concat(res.tempFilePaths);
}
});
},
/**
* 评分
* @param {Object} e
*/
chooseStar(e) {
//
this.sendDate.score = e;
},
/**
* 预览图片
* @param {Object} index
*/
previewImage(index) {
uni.previewImage({
urls: this.imageList,
current: this.imageList[index]
});
},
/**
* 提交
*/
send() {
//
if (this.sendDate.content.length === 0) {
uni.showModal({
content: '请输入问题和意见',
showCancel: false
});
return;
}
uni.showLoading({
title: '上传中...'
});
let imgs = this.imageList.map((value, index) => {
return {
name: 'images' + index,
uri: value
};
});
// TODO 2M, 5
this.request(this.sendDate, imgs)
.then(res => {
if (typeof res.data === 'string') {
res.data = JSON.parse(res.data);
}
if (res.statusCode === 200 && res.data && res.data.ret === 0) {
uni.showModal({
content: '反馈成功',
showCancel: false
});
this.imageList = [];
this.sendDate = Object.assign(this.deviceInfo, {
score: 0,
content: '',
contact: ''
});
} else if (res.statusCode !== 200) {
uni.showModal({
content: '反馈失败,错误码为:' + res.statusCode,
showCancel: false
});
} else {
uni.showModal({
content: '反馈失败',
showCancel: false
});
}
})
.catch(err => {
console.log(err);
});
},
/**
* 发送请求到后台
*/
request(sendDate, imgs) {
return new Promise((resolve, reject) => {
let fromData = {
url: 'https://service.dcloud.net.cn/feedback',
success: res => {
resolve(res);
},
fail: res => {
reject(res);
},
complete() {
uni.hideLoading();
}
};
if (imgs.length > 0) {
fromData.files = imgs;
fromData.formData = sendDate;
uni.uploadFile(fromData);
} else {
fromData.data = sendDate;
fromData.method = 'POST';
uni.request(fromData);
}
});
}
}
};
</script>
<style>
page {
background-color: #efeff4;
}
.input-view {
font-size: 28rpx;
}
.close-view {
text-align: center;
line-height: 14px;
height: 16px;
width: 16px;
border-radius: 50%;
background: #ff5053;
color: #ffffff;
position: absolute;
top: -6px;
right: -4px;
font-size: 12px;
}
</style>

64
platforms/app-plus/orientation/orientation.vue

@ -0,0 +1,64 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-btn-v">
<button type="primary" @tap="getOrient">获取设备的方向信息</button>
<button type="primary" @tap="watchOrient">监听设备的方向变化</button>
<button type="primary" @tap="watchStop">停止监听</button>
</view>
<view class="uni-textarea">
<textarea :value="value" />
</view>
</view>
</view>
</template>
<script>
var id = null
export default {
data() {
return {
title: 'orientation',
value: ''
}
},
onUnload() {
this.watchStop();
},
methods: {
getOrient: function () {
var that = this;
plus.orientation.getCurrentOrientation(function (o) {
that.value = "alpha:" + o.alpha + "\nbeta:" + o.beta + "\ngamma:" + o.gamma;
}, function (e) {
console.log("获取失败:" + e.message);
});
},
watchOrient: function () {
var that = this;
if (id) {
return;
}
id = plus.orientation.watchOrientation(function (o) {
that.value = "监听设备方向变化信息\n" + "alpha:" + o.alpha + "\nbeta:" + o.beta + "\ngamma:" + o.gamma;
}, function (e) {
plus.orientation.clearWatch(id);
id = null;
console.log("监听失败:" + e.message);
});
},
watchStop: function () {
if (id) {
plus.orientation.clearWatch(id);
id = null;
} else {
console.log("没有监听设备方向变化");
}
}
}
}
</script>
<style>
</style>

69
platforms/app-plus/proximity/proximity.vue

@ -0,0 +1,69 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-hello-text">
手机顶部听筒处有传感器监听距离手机屏幕的障碍物覆盖该传感器会触发本事件变化
</view>
<view class="uni-btn-v uni-common-mt">
<button type="primary" @tap="getProximity">获取距离传感器信息</button>
<button type="primary" @tap="watchProximity">监听距离传感器变化</button>
<button type="primary" @tap="watchStop">停止监听</button>
</view>
<view class="uni-textarea uni-common-mt">
<textarea :value="value" />
</view>
</view>
</view>
</template>
<script>
var id = null
var bright = null
export default {
data() {
return {
title: 'proximity',
value: ''
}
},
methods: {
getProximity: function () {
var that = this;
plus.proximity.getCurrentProximity(function (d) {
that.value = "距离为:" + d;
}, function (e) {
that.value = "获取失败:" + e.message;
});
},
watchProximity: function () {
var that = this;
if (id) {
return;
}
bright = plus.screen.getBrightness();
id = plus.proximity.watchProximity(function (d) {
that.value = "距离变化:" + d;
plus.screen.setBrightness((d < 1) ? 0.01 : bright);
}, function (e) {
plus.proximity.clearWatch(id);
id = null;
that.value = "监听失败:" + e.message;
});
},
watchStop: function () {
var that = this;
if (id) {
that.value = "停止监听设备距离传感器信息";
plus.proximity.clearWatch(id);
id = null;
} else {
that.value = "没有监听设备距离传感器";
}
}
}
}
</script>
<style>
</style>

81
platforms/app-plus/push/push.vue

@ -0,0 +1,81 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap" v-if="provider[0]">
<view class="uni-btn-v uni-common-mt">
<button type="primary" @tap="listenTranMsg">监听透传数据</button>
</view>
<view class="uni-btn-v uni-common-mt">
<button type="primary" @tap="requireTranMsg">发送"透传数据"消息</button>
</view>
<view class="uni-title uni-common-mt">透传内容</view>
<view class="uni-textarea">
<textarea v-model="tranMsg" />
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'push',
provider: [],
pushServer: 'http://demo.dcloud.net.cn/push/?',
tranMsg:''
}
},
onLoad: function () {
uni.getProvider({
service: "push",
success: (e) => {
console.log("success", e);
this.provider = e.provider;
},
fail: (e) => {
console.log("获取推送通道失败", e);
}
});
},
onUnload:function(){
this.tranMsg = ''
},
methods: {
listenTranMsg() {
// IOSreceive线click
plus.push.addEventListener('click', (msg)=> {
this.tranMsg = JSON.stringify(msg)
});
plus.push.addEventListener('receive',(msg)=>{
this.tranMsg = JSON.stringify(msg)
})
uni.showToast({
title: '开始监听透传数据',
icon: 'success'
})
},
requireTranMsg() { //
var inf = plus.push.getClientInfo();
var url = this.pushServer + 'type=tran&appid=' + encodeURIComponent(plus.runtime.appid);
inf.id && (url += '&id=' + inf.id);
url += ('&cid=' + encodeURIComponent(inf.clientid));
if (plus.os.name == 'iOS') {
url += ('&token=' + encodeURIComponent(inf.token));
}
url += ('&title=' + encodeURIComponent('Hello uniapp'));
url += ('&content=' + encodeURIComponent('带透传数据推送通知!'));
if(plus.os.name === 'iOS'){
url += ('&payload=' + encodeURIComponent('{"title":"Hello uniapp Test","content":"test content"}'));
}else{
url += ('&payload=' + encodeURIComponent('\'{"title":"Hello uniapp Test","content":"test content"}\''));
}
url += ('&version=' + encodeURIComponent(plus.runtime.version));
plus.runtime.openURL(url);
}
}
}
</script>
<style>
</style>

106
platforms/app-plus/shake/shake.vue

@ -0,0 +1,106 @@
<template>
<view class="root" :style="{backgroundImage:'url('+img+')'}">
<view :class="[show ? 'up' : '','shake-up']">
<image mode="aspectFit" src="https://web-assets.dcloud.net.cn/unidoc/zh/shakeup.png"></image>
</view>
<view :class="[show ? 'down' : '','shake-down']">
<image mode="aspectFit" src="https://web-assets.dcloud.net.cn/unidoc/zh/shakedown.png"></image>
</view>
</view>
</template>
<script>
export default {
data() {
return {
img: 'https://web-assets.dcloud.net.cn/unidoc/zh/1.jpg',
show: false,
isOpened: false,
index: 1
}
},
computed:{
pageIndex() {
if (this.index === 1) {
return 'shake-1'
} else if (this.index === 2) {
return 'shake-2'
} else if (this.index === 3) {
return 'shake-3'
} else if (this.index === 4) {
return 'shake-4'
} else {
return 'shake-1'
}
}
},
onLoad: function () {
this.music = uni.createInnerAudioContext();
this.music.src = 'https://web-assets.dcloud.net.cn/unidoc/zh/shake.wav';
let t = null;
uni.onAccelerometerChange((res) => {
if (Math.abs(res.x) + Math.abs(res.y) + Math.abs(res.z) > 20 && !this.show && this.isOpened) {
this.music.play();
setTimeout(() => {
this.index++;
if (this.index > 4) {
this.index = 1
}
this.img = 'https://web-ext-storage.dcloud.net.cn/hello-uni-app/' + this.pageIndex + '.jpg';
}, 2000);
this.show = true;
if (t) {
clearTimeout(t);
}
t = setTimeout(() => {
t = null;
this.show = false;
}, 600)
}
})
},
onShow() {
this.isOpened = true;
},
onUnload() {
this.show = false;
this.isOpened = false;
uni.stopAccelerometer();
this.music.destroy();
}
}
</script>
<style>
.root {
height: 100%;
display: flex;
flex-direction: column;
background-position: center center;
background-repeat: no-repeat;
}
.shake-up,
.shake-down {
height: 50%;
overflow: hidden;
transition: all .5s ease-in-out;
-webkit-transition: all .5s ease-in-out;
background: #333;
}
.up {
transform: translateY(-50%);
-webkit-transform: translateY(-50%);
}
.down {
transform: translateY(50%);
-webkit-transform: translateY(50%);
}
image {
height: 100%;
width: 100%;
}
</style>

105
platforms/app-plus/speech/speech.vue

@ -0,0 +1,105 @@
<template>
<view>
<page-head :title="title"></page-head>
<view class="uni-padding-wrap uni-common-mt">
<view class="uni-textarea">
<textarea :value="value" placeholder="语音识别内容展示区域" disabled />
</view>
<view class="uni-common-mt uni-btn-v">
<button type="primary" @tap="startRecognize">开始语音识别</button>
<!-- <button type="primary" @tap="startRecognizeEnglish">开始语音识别英语</button> -->
</view>
</view>
</view>
</template>
<script>
import permision from "@/common/permission.js"
export default {
data() {
return {
title: 'speech',
value: ''
}
},
onUnload(){
this.value = ""
},
methods: {
async startRecognize () {
// #ifdef APP-PLUS
let status = await this.checkPermission();
if (status !== 1) {
return;
}
// #endif
// TODO ios toast
var options = {};
var that = this;
options.engine = 'baidu';
that.value = "";
plus.speech.startRecognize(options, function (s) {
console.log(s);
that.value += s;
}, function (e) {
console.log("语音识别失败:" + e.message);
});
},
async startRecognizeEnglish () {
// #ifdef APP-PLUS
let status = await this.checkPermission();
if (status !== 1) {
return;
}
// #endif
// TODO ios toast
var options = {};
var that = this;
options.engine = 'baidu';
options.lang = 'en-us';
that.value = "";
plus.speech.startRecognize(options, function (s) {
console.log(s);
that.value += s;
}, function (e) {
console.log("语音识别失败:" + e.message);
});
}
// #ifdef APP-PLUS
,
async checkPermission() {
let status = permision.isIOS ? await permision.requestIOS('record') :
await permision.requestAndroid('android.permission.RECORD_AUDIO');
if (status === null || status === 1) {
status = 1;
} else if (status === 2) {
uni.showModal({
content: "系统麦克风已关闭",
confirmText: "确定",
showCancel: false,
success: function(res) {
}
})
} else {
uni.showModal({
content: "需要麦克风权限",
confirmText: "设置",
success: function(res) {
if (res.confirm) {
permision.gotoAppSetting();
}
}
})
}
return status;
}
// #endif
}
}
</script>
<style>
</style>

BIN
static/camer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
static/center-selected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
static/center.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

12
static/customicons.css

@ -0,0 +1,12 @@
@font-face {
font-family: "customicons"; /* Project id 2878519 */
src: url('/static/icon/iconfont.ttf?t=1744633630602') format('truetype');
}
.customicons {
font-family: "customicons" !important;
}
.filter:before {
content: "\e64c";
}

BIN
static/edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/empty.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/font/Pacifico-Regular.ttf

Binary file not shown.

BIN
static/icon/doc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
static/icon/ic_apply.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
static/icon/ic_book.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/icon/ic_c_app.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
static/icon/ic_c_book.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/icon/ic_c_message.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/icon/ic_camera.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/icon/ic_card.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
static/icon/ic_question.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/icon/ic_selfexamination.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
static/icon/ic_task.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save