初始化产品库
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
unpackage/
|
||||
dist/
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.project
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
/package-lock.json
|
||||
/.hbuilderx/launch.json
|
||||
64
babel.config.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const plugins = []
|
||||
|
||||
if (process.env.UNI_OPT_TREESHAKINGNG) {
|
||||
plugins.push(require('@dcloudio/vue-cli-plugin-uni-optimize/packages/babel-plugin-uni-api/index.js'))
|
||||
}
|
||||
|
||||
if (
|
||||
(
|
||||
process.env.UNI_PLATFORM === 'app-plus' &&
|
||||
process.env.UNI_USING_V8
|
||||
) ||
|
||||
(
|
||||
process.env.UNI_PLATFORM === 'h5' &&
|
||||
process.env.UNI_H5_BROWSER === 'builtin'
|
||||
)
|
||||
) {
|
||||
const path = require('path')
|
||||
|
||||
const isWin = /^win/.test(process.platform)
|
||||
|
||||
const normalizePath = path => (isWin ? path.replace(/\\/g, '/') : path)
|
||||
|
||||
const input = normalizePath(process.env.UNI_INPUT_DIR)
|
||||
try {
|
||||
plugins.push([
|
||||
require('@dcloudio/vue-cli-plugin-hbuilderx/packages/babel-plugin-console'),
|
||||
{
|
||||
file(file) {
|
||||
file = normalizePath(file)
|
||||
if (file.indexOf(input) === 0) {
|
||||
return path.relative(input, file)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
])
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
process.UNI_LIBRARIES = process.UNI_LIBRARIES || ['@dcloudio/uni-ui']
|
||||
process.UNI_LIBRARIES.forEach(libraryName => {
|
||||
plugins.push([
|
||||
'import',
|
||||
{
|
||||
'libraryName': libraryName,
|
||||
'customName': (name) => {
|
||||
return `${libraryName}/lib/${name}/${name}`
|
||||
}
|
||||
}
|
||||
])
|
||||
})
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
'@vue/app',
|
||||
{
|
||||
modules: 'commonjs',
|
||||
useBuiltIns: process.env.UNI_PLATFORM === 'h5' ? 'usage' : 'entry'
|
||||
}
|
||||
]
|
||||
],
|
||||
plugins
|
||||
}
|
||||
84
package.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"name": "dv_cp_wechat",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-serve --minimize",
|
||||
"build": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build",
|
||||
"build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin vue-cli-service uni-build",
|
||||
"dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch",
|
||||
"info": "node node_modules/@dcloudio/vue-cli-plugin-uni/commands/info.js",
|
||||
"test:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin jest -i"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@dcloudio/uni-app-plus": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-h5": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-helper-json": "*",
|
||||
"@dcloudio/uni-mp-360": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-mp-alipay": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-mp-baidu": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-mp-kuaishou": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-mp-qq": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-mp-toutiao": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-mp-vue": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-mp-weixin": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-quickapp-native": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-quickapp-webview": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-stat": "^2.0.0-31820210406002",
|
||||
"@vue/shared": "^3.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"core-js": "^3.11.0",
|
||||
"dayjs": "^1.10.6",
|
||||
"echarts": "^4.9.0",
|
||||
"recorder-core": "^1.1.21080800",
|
||||
"regenerator-runtime": "^0.12.1",
|
||||
"vue": "^2.6.11",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuex": "^3.2.0",
|
||||
"vuex-persistedstate": "^4.0.0-beta.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "~7.12.0",
|
||||
"@dcloudio/types": "^2.2.1",
|
||||
"@dcloudio/uni-automator": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-cli-i18n": "^2.0.0-32920211029001",
|
||||
"@dcloudio/uni-cli-shared": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-i18n": "^2.0.0-32920211029001",
|
||||
"@dcloudio/uni-migration": "^2.0.0-31820210406002",
|
||||
"@dcloudio/uni-template-compiler": "^2.0.0-31820210406002",
|
||||
"@dcloudio/vue-cli-plugin-hbuilderx": "^2.0.0-31820210406002",
|
||||
"@dcloudio/vue-cli-plugin-uni": "^2.0.0-31820210406002",
|
||||
"@dcloudio/vue-cli-plugin-uni-optimize": "^2.0.0-31820210406002",
|
||||
"@dcloudio/webpack-uni-mp-loader": "^2.0.0-31820210406002",
|
||||
"@dcloudio/webpack-uni-pages-loader": "^2.0.0-31820210406002",
|
||||
"@vue/cli-plugin-babel": "^4.5.12",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-plugin-import": "^1.11.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"jest": "^25.4.0",
|
||||
"mini-types": "*",
|
||||
"miniprogram-api-typings": "^3.3.2",
|
||||
"node-sass": "npm:dart-sass@^1.25.0",
|
||||
"postcss-comment": "^2.0.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
},
|
||||
"browserslist": [
|
||||
"Android >= 4",
|
||||
"ios >= 8"
|
||||
],
|
||||
"uni-app": {
|
||||
"scripts": {
|
||||
"cp-weixin": {
|
||||
"title": "企业微信端",
|
||||
"env": {
|
||||
"UNI_PLATFORM": "mp-weixin"
|
||||
},
|
||||
"define": {
|
||||
"CP-WEIXIN": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
postcss.config.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const path = require('path')
|
||||
module.exports = {
|
||||
parser: require('postcss-comment'),
|
||||
plugins: [
|
||||
require('postcss-import')({
|
||||
resolve (id, basedir, importOptions) {
|
||||
if (id.startsWith('~@/')) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3))
|
||||
} else if (id.startsWith('@/')) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2))
|
||||
} else if (id.startsWith('/') && !id.startsWith('//')) {
|
||||
return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1))
|
||||
}
|
||||
return id
|
||||
}
|
||||
}),
|
||||
require('autoprefixer')({
|
||||
remove: process.env.UNI_PLATFORM !== 'h5'
|
||||
}),
|
||||
require('@dcloudio/vue-cli-plugin-uni/packages/postcss')
|
||||
]
|
||||
}
|
||||
1
public/WW_verify_BeLokK3oa5SpR9ER.txt
Normal file
@@ -0,0 +1 @@
|
||||
BeLokK3oa5SpR9ER
|
||||
1
public/WW_verify_mSayjTrg7ZGXGC2D.txt
Normal file
@@ -0,0 +1 @@
|
||||
mSayjTrg7ZGXGC2D
|
||||
1
public/WW_verify_ual6jgcTCMb4cdLb.txt
Normal file
@@ -0,0 +1 @@
|
||||
ual6jgcTCMb4cdLb
|
||||
28
public/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>
|
||||
<%= htmlWebpackPlugin.options.title %>
|
||||
</title>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
|
||||
})
|
||||
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>
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>Please enable JavaScript to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
164
src/App.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<script>
|
||||
import {mapMutations, mapState} from 'vuex'
|
||||
|
||||
export default {
|
||||
provide() {
|
||||
return {
|
||||
root: this,
|
||||
}
|
||||
},
|
||||
onLaunch: function () {
|
||||
this.initConfig()
|
||||
},
|
||||
onShow: function () {
|
||||
this.initWaterMarker()
|
||||
},
|
||||
onPageNotFound() {
|
||||
this.logout()
|
||||
},
|
||||
computed: {
|
||||
...mapState(['token', 'user']),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(['initWaterMarker', 'logout', 'getConfig']),
|
||||
initDev() {
|
||||
let baseURL = 'http://192.168.1.87:9000'
|
||||
this.getConfig({baseURL})
|
||||
|
||||
// this.$store.commit('login', 'bearer 88dd207a-dfe3-4f81-b9bd-e379de427d0b')
|
||||
},
|
||||
initConfig() {
|
||||
if (process.env.NODE_ENV == 'development') this.initDev()
|
||||
else {
|
||||
let baseURL = location.origin
|
||||
this.getConfig({baseURL})
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'uview/index.scss';
|
||||
@import './common/iconfont.css';
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', serif;
|
||||
}
|
||||
|
||||
uni-page-body {
|
||||
min-height: 100%;
|
||||
background: #f5f5f5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div[bottom] {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 128px;
|
||||
padding: 24px 32px;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #d4d4d4;
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
|
||||
& > * {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
& + * {
|
||||
margin-left: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div[flex] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.spb {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
div[shrink] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
uni-button {
|
||||
border-radius: 4px;
|
||||
|
||||
&.u-btn--primary {
|
||||
background-color: $uni-color-primary !important;
|
||||
border-color: $uni-color-primary !important;
|
||||
}
|
||||
|
||||
&:after {
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.fill {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.u-form-item {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
background: #ffffff;
|
||||
padding: 0 32px !important;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
line-height: normal !important;
|
||||
|
||||
.u-form-item__body {
|
||||
height: inherit;
|
||||
flex: 1;
|
||||
|
||||
.u-form-item--left {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.u-form-item--right__content__slot {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.u-form-item__message {
|
||||
margin: 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mapWarn {
|
||||
0% {
|
||||
transform: scale(.5);
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
30% {
|
||||
opacity: .5
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(1.4);
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
</style>
|
||||
61
src/common/axios.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import axios from 'axios'
|
||||
import store from '../store'
|
||||
import util from "./util";
|
||||
|
||||
let instance = axios.create({
|
||||
timeout: 600000,
|
||||
withCredentials: true,
|
||||
})
|
||||
instance.interceptors.request.use(config => {
|
||||
store.commit('initWaterMarker')
|
||||
if (!config.withoutToken && store.state.token) {
|
||||
config.headers["Authorization"] = store.state.token
|
||||
}
|
||||
return config
|
||||
}, err => {
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
instance.interceptors.response.use(res => {
|
||||
if (res.data) {
|
||||
if (res.data.code) {
|
||||
if (res.data.code == 0) {
|
||||
return res.data
|
||||
} else if (res.data.code == 401) {
|
||||
store.commit("logout");
|
||||
let reg = new RegExp('.*code=(.+$)', "g")
|
||||
if (reg.test(location.search)) {
|
||||
let code = location.search.replace(reg, '$1')
|
||||
store.commit('bindAccount', {
|
||||
code, then: res => {
|
||||
store.commit("login", [res?.token_type, res?.access_token].join(" ").trim())
|
||||
location.href = location.href.replace('code=' + code, '')
|
||||
}
|
||||
})
|
||||
} else util.confirm("用户信息验证失效,是否要重新登录?").then(() => {
|
||||
store.commit('redirectCode')
|
||||
// let app = store.state.apps?.find(e => location.href.indexOf(e.path) > -1)
|
||||
// const goto = path => {
|
||||
// location.href = location.origin + "/pages/loading?" + path
|
||||
// }
|
||||
// if (app) {
|
||||
// goto(location.search + `&app=${app.key}`)
|
||||
// } else {
|
||||
// goto(location.search + `#error`)
|
||||
// }
|
||||
}).catch(() => 0)
|
||||
} else {
|
||||
console.error(res.data.msg || "请求失败!")
|
||||
return Promise.reject(res.data.msg)
|
||||
}
|
||||
} else {
|
||||
return res.data
|
||||
}
|
||||
} else {
|
||||
console.error("服务器异常,请联系管理员!")
|
||||
}
|
||||
}, err => {
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
export default instance
|
||||
50
src/common/dict.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import request from "./axios";
|
||||
import store from "../store";
|
||||
|
||||
/**
|
||||
* 封装字典工具类
|
||||
*/
|
||||
const $dict = {
|
||||
vueStore: null,
|
||||
url: "/admin/dictionary/queryValsByCodeList",
|
||||
setUrl(v) {
|
||||
this.url = v
|
||||
},
|
||||
load(...code) {
|
||||
if (!this.vueStore) this.vueStore = store
|
||||
return request.post(this.url, null, {
|
||||
params: {
|
||||
codeList: code.join(',')
|
||||
}
|
||||
}).then((res) => {
|
||||
this.vueStore.commit("setDicts", res.data)
|
||||
})
|
||||
},
|
||||
getDict(key) {
|
||||
let dict = this.vueStore.getters.getDict(key)
|
||||
return dict ? dict.values : []
|
||||
},
|
||||
getValue(key, label) {
|
||||
let dict = this.vueStore.getters.getDict(key)
|
||||
if (dict) {
|
||||
let item = dict.values.find(v => v.dictName == label)
|
||||
return item ? item.dictValue : label
|
||||
} else return label
|
||||
},
|
||||
getLabel(key, value) {
|
||||
let dict = this.vueStore.getters.getDict(key)
|
||||
if (dict) {
|
||||
let item = dict.values.find(v => v.dictValue == value)
|
||||
return item ? item.dictName : value
|
||||
} else return value
|
||||
},
|
||||
getColor(key, value) {
|
||||
let dict = this.vueStore.getters.getDict(key)
|
||||
if (dict) {
|
||||
let item = dict.values.find(v => v.dictValue == value)
|
||||
return item ? item.dictColor : value
|
||||
} else return value
|
||||
},
|
||||
}
|
||||
|
||||
export default $dict
|
||||
712
src/common/iconfont.css
Normal file
245
src/common/permission.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/// 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 requestIOS(permissionID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
switch (permissionID) {
|
||||
case "push":
|
||||
resolve(push());
|
||||
break;
|
||||
case "location":
|
||||
resolve(location());
|
||||
break;
|
||||
case "record":
|
||||
resolve(record());
|
||||
break;
|
||||
case "camera":
|
||||
resolve(camera());
|
||||
break;
|
||||
case "album":
|
||||
resolve(album());
|
||||
break;
|
||||
case "contact":
|
||||
resolve(contact());
|
||||
break;
|
||||
case "calendar":
|
||||
resolve(calendar());
|
||||
break;
|
||||
case "memo":
|
||||
resolve(memo());
|
||||
break;
|
||||
default:
|
||||
resolve(0);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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')
|
||||
},
|
||||
requestIOS: requestIOS,
|
||||
requestAndroid: requestAndroid,
|
||||
gotoAppSetting: gotoAppPermissionSetting
|
||||
}
|
||||
|
||||
module.exports = permission
|
||||
59
src/common/util.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import dict from "./dict";
|
||||
import toast from '../uview/libs/function/toast'
|
||||
import addUnit from '../uview/libs/function/addUnit'
|
||||
import $parent from '../uview/libs/function/$parent'
|
||||
import guid from '../uview/libs/function/guid'
|
||||
import deepClone from '../uview/libs/function/deepClone'
|
||||
import debounce from '../uview/libs/function/debounce'
|
||||
import throttle from '../uview/libs/function/throttle'
|
||||
import trim from '../uview/libs/function/trim'
|
||||
import {sys} from '../uview/libs/function/sys'
|
||||
import test from '../uview/libs/function/test'
|
||||
import config from '../uview/libs/config/config'
|
||||
import zIndex from '../uview/libs/config/zIndex'
|
||||
import $moment from 'dayjs'
|
||||
|
||||
const confirm = (content, title, config) => {
|
||||
let ops = {content}
|
||||
if (typeof title == 'object') {
|
||||
ops = {...ops, ...title}
|
||||
} else ops = {...ops, title: title || "提示"}
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.showModal({
|
||||
...ops, ...config, success: res => {
|
||||
if (res?.confirm) {
|
||||
resolve()
|
||||
} else if (res?.cancel) {
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年龄
|
||||
* @param code
|
||||
*/
|
||||
const calcAge = (code) => {
|
||||
let birthday
|
||||
if (typeof code == 'string' && code.length == 18) {
|
||||
birthday = $moment(code.substring(6, 14), 'YYYYMMDD')
|
||||
} else if (typeof code == 'object') {
|
||||
birthday = code
|
||||
}
|
||||
return Math.ceil($moment().year() - $moment(birthday).year())
|
||||
}
|
||||
|
||||
|
||||
|
||||
const u = {toast, $parent, addUnit, guid, config, zIndex, deepClone, throttle, debounce, trim, test, sys}
|
||||
export default {
|
||||
dict,
|
||||
confirm,
|
||||
calcAge,
|
||||
u,
|
||||
dateFormat: (time, format) => {
|
||||
return $moment(time).format(format || 'YYYY-MM-DD').replace("Invalid Date", "")
|
||||
}
|
||||
}
|
||||
69
src/components/AiAdd.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<movable-area class="movableArea">
|
||||
<movable-view direction="all" x="300" y="500">
|
||||
<div class="AiAdd" @click.stop="add"></div>
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiAdd",
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
this.$emit("add")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.movableArea {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 999;
|
||||
|
||||
uni-movable-view {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.AiAdd {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
background: #1365DD;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
&:before , &:after{
|
||||
content: "";
|
||||
background: #FFFFFF;
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&:before{
|
||||
height: 48px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&:after{
|
||||
height: 4px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
87
src/components/AiBack.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<ai-fixed-btn v-if="!isTopPage||custom">
|
||||
<div class="AiBack" @click.stop="back">
|
||||
<img :src="imgHomeUrl + 'back.png'" alt="">
|
||||
<text>返回</text>
|
||||
</div>
|
||||
</ai-fixed-btn>
|
||||
</template>
|
||||
<script>
|
||||
import AiFixedBtn from "./AiFixedBtn";
|
||||
|
||||
export default {
|
||||
name: "AiBack",
|
||||
components: {AiFixedBtn},
|
||||
props: {
|
||||
delta: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
eventName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
data: {
|
||||
type: Object | Boolean,
|
||||
default: () => {
|
||||
}
|
||||
},
|
||||
custom: Boolean,
|
||||
visible: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isTopPage: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
if (this.visible)
|
||||
return this.$parent.$emit(this.eventName, this.data)
|
||||
|
||||
if (this.custom) {
|
||||
this.$emit("back")
|
||||
} else uni.navigateBack({
|
||||
delta: this.delta,
|
||||
success: () => {
|
||||
if (this.eventName != '') {
|
||||
uni.$emit(this.eventName, this.data)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.isTopPage = window.history.length <= 1
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiBack {
|
||||
width: 108px;
|
||||
height: 108px;
|
||||
background: #6BA1F9;
|
||||
box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.12);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #FFFFFF;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
115
src/components/AiCard.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<section class="AiCard">
|
||||
<div flex v-if="$slots.custom" class="start">
|
||||
<div class="fill">
|
||||
<slot name="custom"/>
|
||||
</div>
|
||||
<div v-if="$slots.menu" class="iconfont iconfont-iconMore" @tap.stop="handleMore"/>
|
||||
</div>
|
||||
<template v-else>
|
||||
<u-row>
|
||||
<div class="content">
|
||||
<slot/>
|
||||
</div>
|
||||
<div btn @tap="$emit('send')">发送</div>
|
||||
</u-row>
|
||||
<u-row justify="space-between">
|
||||
<slot v-if="$slots.title" name="title"/>
|
||||
<div v-else>{{ cardTitle }}</div>
|
||||
<div v-if="$slots.menu" class="iconfont iconfont-iconMore" @tap.stop="handleMore"/>
|
||||
</u-row>
|
||||
</template>
|
||||
<div v-if="menu" class="mask" @click="menu=false">
|
||||
<div class="moreMenu" :style="menuPos">
|
||||
<slot name="menu"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiCard",
|
||||
props: {
|
||||
cardTitle: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuPos: {},
|
||||
menu: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleMore({detail}) {
|
||||
this.menuPos = {
|
||||
left: detail.x - 10 + 'px',
|
||||
top: detail.y + 'px'
|
||||
}
|
||||
this.menu = !this.menu
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiCard {
|
||||
width: 100%;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.02);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #999;
|
||||
font-size: 26px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.content {
|
||||
background: #F9F9F9;
|
||||
border-radius: 4px;
|
||||
min-height: 120px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-bottom: 26px;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.u-row {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
div[btn] {
|
||||
color: #1365DD;
|
||||
padding: 0 0 0 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.iconfont-iconMore {
|
||||
font-size: 38px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.moreMenu {
|
||||
position: fixed;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
transform: translate(-100%, -100%);
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 11;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
76
src/components/AiCell.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<section class="AiCell" :class="{bottomBorder,alignCenter,topLabel}">
|
||||
<div class="label" :class="{title}">
|
||||
<slot v-if="$slots.label" name="label"/>
|
||||
<span v-else>{{ label }}</span>
|
||||
</div>
|
||||
<div class="content" :class="{topLabel}">
|
||||
<slot/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiCell",
|
||||
props: {
|
||||
label: {default: ""},
|
||||
bottomBorder: Boolean,
|
||||
topLabel: Boolean,
|
||||
title: Boolean,
|
||||
alignCenter: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiCell {
|
||||
display: flex;
|
||||
min-height: 72px;
|
||||
font-size: 30px;
|
||||
color: #333;
|
||||
padding: 14px 0;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
|
||||
&.bottomBorder {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
&.alignCenter {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.topLabel {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.label {
|
||||
min-width: 60px;
|
||||
flex-shrink: 0;
|
||||
width: auto;
|
||||
color: #999;
|
||||
|
||||
|
||||
&.title {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
font-size: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
min-height: 40px;
|
||||
max-width: 500px;
|
||||
text-align: right;
|
||||
|
||||
&.topLabel {
|
||||
text-align: start;
|
||||
margin-top: 16px;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
60
src/components/AiDate.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<section class="AiDate">
|
||||
<u-calendar v-model="show" @change="handleSelect" :mode="mode"/>
|
||||
<div flex @click="show=true">
|
||||
<div v-if="label" v-html="label"/>
|
||||
<div v-else v-html="placeholder"/>
|
||||
<i class="iconfont iconfont-iconArrow_Down"/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UCalendar from "../uview/components/u-calendar/u-calendar";
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export default {
|
||||
name: "AiDate",
|
||||
components: {UCalendar},
|
||||
computed: {
|
||||
label() {
|
||||
let arr = (this.selected || this.value)?.toString()?.split(",") || []
|
||||
arr = arr.map(e => dayjs(e).format("MM-DD").replace("Invalid Date", ''))
|
||||
return arr.join('至')
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
selected: ""
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {default: ""},
|
||||
placeholder: {default: "请选择"},
|
||||
mode: {default: "date"},//date 单个日期|range 日期范围
|
||||
},
|
||||
methods: {
|
||||
handleSelect(v) {
|
||||
if (this.mode == 'date') {
|
||||
this.selected = v.result
|
||||
this.$emit('change', v.result)
|
||||
} else if (this.mode == 'range') {
|
||||
this.selected = [v.startDate, v.endDate]
|
||||
this.$emit('change', v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiDate {
|
||||
color: #333333;
|
||||
|
||||
.iconfont-iconArrow_Down {
|
||||
margin-left: 4px;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
src/components/AiEmpty/AiEmpty.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="emptyWrap">
|
||||
<img class="emptyImg" src="./static/Empty.png">
|
||||
<div class="emptyText">{{description}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:"emptyData",
|
||||
props:{
|
||||
description:{
|
||||
default:'暂无相关信息',
|
||||
type:String
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.emptyWrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.emptyImg{
|
||||
width: 400rpx;
|
||||
height: 240rpx;
|
||||
margin-top: 112px;
|
||||
}
|
||||
.emptyText{
|
||||
font-size:29rpx;
|
||||
font-family:PingFangSC-Regular,PingFang SC;
|
||||
font-weight:400;
|
||||
color:rgba(183,183,183,1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
src/components/AiEmpty/static/1/Icon/Fields/bianhao@2x.png
Normal file
|
After Width: | Height: | Size: 806 B |
BIN
src/components/AiEmpty/static/1/Icon/Fields/time@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/components/AiEmpty/static/Empty.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
36
src/components/AiFixedBtn.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<section class="AiFixedBtn">
|
||||
<movable-area class="movableArea">
|
||||
<movable-view direction="all" x="300" y="500" @tap.stop>
|
||||
<slot/>
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiFixedBtn"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiFixedBtn {
|
||||
}
|
||||
|
||||
.movableArea {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 999;
|
||||
|
||||
uni-movable-view {
|
||||
pointer-events: auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
72
src/components/AiImage.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<section class="AiImage">
|
||||
<div v-if="$slots.default" @tap="prev">
|
||||
<slot/>
|
||||
</div>
|
||||
<u-image v-else :src="src" @tap="prev">
|
||||
<image v-if="link" class="errorImage" slot="error" :src="$cdn+'link.png'"/>
|
||||
<image v-else-if="miniapp" class="errorImage" slot="error" :src="$cdn+'miniwxmp.jpg'"/>
|
||||
<image v-else class="errorImage" slot="error" :src="$cdn+'file.png'"/>
|
||||
</u-image>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UImage from "../uview/components/u-image/u-image";
|
||||
import UModal from "../uview/components/u-modal/u-modal";
|
||||
import {mapActions} from "vuex";
|
||||
|
||||
export default {
|
||||
name: "AiImage",
|
||||
components: {UModal, UImage},
|
||||
data() {
|
||||
return {
|
||||
dialog: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
src: String,
|
||||
preview: Boolean,
|
||||
link: Boolean,
|
||||
miniapp: Boolean,
|
||||
file: {
|
||||
default: () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['previewFile', 'injectJWeixin']),
|
||||
prev() {
|
||||
if (this.preview) {
|
||||
if (!!this.src) {
|
||||
uni.previewImage({
|
||||
current: this.src,
|
||||
urls: [this.src]
|
||||
})
|
||||
} else {
|
||||
this.previewFile({size: 1, ...this.file})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiImage {
|
||||
::v-deep image {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
::v-deep .u-image__error {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.errorImage {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
28
src/components/AiLoading.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<section class="AiLoading">
|
||||
<image :src="image"/>
|
||||
<span>{{ tips }}</span>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiLoading",
|
||||
props: {
|
||||
tips: {default: "应用加载中"},
|
||||
image: {default: "https://cdn.cunwuyun.cn/wxAdmin/img/message.png"}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiLoading {
|
||||
font-size: 32px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
68
src/components/AiMap.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<section class="AiMap">
|
||||
<div ref="amap" class="map"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AMapLoader from "@amap/amap-jsapi-loader";
|
||||
|
||||
export default {
|
||||
name: "AiMap",
|
||||
props: {
|
||||
plugins: {default: () => ['AMap.DistrictSearch']},
|
||||
map: Object,
|
||||
lib: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
amap: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initMap() {
|
||||
let {plugins} = this
|
||||
AMapLoader.load({
|
||||
key: '54a02a43d9828a8f9cd4f26fe281e74e',
|
||||
version: '2.0',
|
||||
plugins
|
||||
}).then(AMap => {
|
||||
this.amap = new AMap.Map(this.$refs.amap, {
|
||||
resizeEnable: true,
|
||||
zoom: 14,
|
||||
})
|
||||
this.$emit('update:lib', AMap)
|
||||
this.$emit('update:map', this.amap)
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initMap()
|
||||
},
|
||||
destroyed() {
|
||||
this.amap?.destroy()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiMap {
|
||||
.map {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::v-deep .amap-logo, ::v-deep .amap-copyright {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::v-deep .amap-icon {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
85
src/components/AiResult.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<section class="AiResult">
|
||||
<slot v-if="$slots.default"/>
|
||||
<template v-else>
|
||||
<image :src="result.image"/>
|
||||
<span class="tips">{{ result.tips }}</span>
|
||||
<slot name="extra" class="extra" v-if="$slots.extra"/>
|
||||
<div v-if="result.btn" class="btn" @tap="handleTap">{{ result.btn }}</div>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiResult",
|
||||
props: {
|
||||
tips: {default: "提交成功!"},
|
||||
image: {default: "https://cdn.cunwuyun.cn/dvcp/h5/result/success.png"},
|
||||
btn: {default: ""},
|
||||
status: {default: "success"},
|
||||
btnTap: Function
|
||||
},
|
||||
computed: {
|
||||
result() {
|
||||
let obj = {
|
||||
image: this.image,
|
||||
tips: this.tips,
|
||||
btn: this.btn
|
||||
}
|
||||
if (this.status == "error") {
|
||||
obj.image = this.$cdn + "result/fail.png"
|
||||
obj.tips = this.tips || "提交失败!"
|
||||
}
|
||||
return obj
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTap() {
|
||||
this.btnTap && this.btnTap()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiResult {
|
||||
padding-top: 96px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
|
||||
& > image {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin: 16px auto 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.extra {
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
margin-top: 80px;
|
||||
width: calc(100% - 192px);
|
||||
height: 88px;
|
||||
background: #197DF0;
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.02);
|
||||
border-radius: 8px;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
87
src/components/AiSearchPopup.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<section class="AiSearchPopup">
|
||||
<u-popup v-model="show" length="100%" closeable :mode="mode">
|
||||
<slot v-if="$slots.default"/>
|
||||
<div class="searchPane" v-else>
|
||||
<div class="title">{{ title }}</div>
|
||||
<u-search v-model="search" :placeholder="placeholder" :show-action="false" @search="getList()" :focus="show"/>
|
||||
<div class="result">
|
||||
<div class="option" v-for="(op,i) in list" :key="i" @tap="handleSelect(op)">{{ op[ops.label] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</u-popup>
|
||||
<div @tap="show=true">
|
||||
<slot name="btn"/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiSearchPopup",
|
||||
props: {
|
||||
title: {default: "搜索"},
|
||||
placeholder: {default: "请搜索"},
|
||||
ops: {default: () => ({label: 'label', search: 'name'})},
|
||||
url: String,
|
||||
mode: {default: "right"}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
search: "",
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.url && this.$instance.post(this.url, null, {
|
||||
params: {[this.ops.search]: this.search}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.list = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSelect(op) {
|
||||
this.$emit('select', op)
|
||||
this.show = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiSearchPopup {
|
||||
|
||||
|
||||
::v-deep .searchPane {
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
}
|
||||
|
||||
.result {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 80px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
83
src/components/AiSelect.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<section class="AiSelect">
|
||||
<div class="display" v-if="$slots.default" @tap="handleShowOptions">
|
||||
<slot/>
|
||||
</div>
|
||||
<div v-else class="display" @tap="handleShowOptions">
|
||||
<div class="selectedLabel" v-if="selectedLabel">{{ selectedLabel }}</div>
|
||||
<i v-else>{{ placeholder }}</i>
|
||||
<u-icon name="arrow-right" color="#ddd"/>
|
||||
</div>
|
||||
<u-select v-model="show" :list="options" :mode="mode" @confirm="handleConfirm"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiSelect",
|
||||
props: {
|
||||
value: String,
|
||||
placeholder: {default: "请选择"},
|
||||
list: {default: () => []},
|
||||
mode: {default: "single-column"},
|
||||
dict: {default: ""},
|
||||
disabled: Boolean
|
||||
},
|
||||
computed: {
|
||||
selectedLabel() {
|
||||
let label = this.options.find(e => e.value == this.value)?.label
|
||||
return this.selected?.map(e => e.label)?.join(",") || label
|
||||
},
|
||||
options() {
|
||||
return this.dict ? this.$dict.getDict(this.dict).map(e => ({
|
||||
value: e.dictValue,
|
||||
label: e.dictName
|
||||
})) : this.list
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
selected: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleConfirm(v) {
|
||||
this.selected = v
|
||||
this.$emit("data", this.selected)
|
||||
this.$forceUpdate()
|
||||
},
|
||||
handleShowOptions() {
|
||||
if (!this.disabled) this.show = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiSelect {
|
||||
max-width: 100%;
|
||||
|
||||
::v-deep .u-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.display {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.selectedLabel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
color: $uni-text-color-grey;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
148
src/components/AiSelectEnterprise/AiSelectEnterprise.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div class="AiSelectEnterprise">
|
||||
<tree :checkList="checkList" :props="prop" @sendValue="(val)=>checkList = val" :multiple="multiple" :isCheck="true"
|
||||
:rootId="rootId"/>
|
||||
<div class="footer">
|
||||
<scroll-view scroll-x class="scroll" style="width: 100%;">
|
||||
</scroll-view>
|
||||
<div class="btn" @click="confirm">确定选择</div>
|
||||
<AiBack :visible="true" eventName="update:visible" :data="false" @click.native="confirm"></AiBack>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tree from "./tree";
|
||||
import AiBack from "../AiBack";
|
||||
|
||||
export default {
|
||||
name: "AiSelectEnterprise",
|
||||
components: {tree, AiBack},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rootId: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tree: [],
|
||||
checkList: this.value,
|
||||
prop: {
|
||||
label: 'name',
|
||||
multiple: this.multiple,
|
||||
},
|
||||
map: {},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
uni.pageScrollTo({
|
||||
duration: 0,
|
||||
scrollTop: 0
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
confirm() {
|
||||
let filter = []
|
||||
this.map = {}
|
||||
this.recursion(this.checkList)
|
||||
Object.keys(this.map).map(e => filter.push(this.map[e]))
|
||||
this.$emit("change", filter)
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
recursion(arr) {
|
||||
if (arr?.length) {
|
||||
arr.map(e => {
|
||||
if ((e.type == 0 || e.openId) && e.checked && !this.map[e.id]) {
|
||||
this.map[e.id] = e
|
||||
this.recursion(e.childrenUser)
|
||||
}
|
||||
if (e.childrenDept?.length) {
|
||||
this.recursion(e.childrenDept)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiSelectEnterprise {
|
||||
min-height: 100%;
|
||||
background-color: #F5F5F5;
|
||||
position: relative;
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
background: #F4F8FB;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 0 32px;
|
||||
|
||||
.scroll {
|
||||
height: 118px;
|
||||
|
||||
::v-deep .uni-scroll-view-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.tag {
|
||||
width: 236px;
|
||||
height: 72px;
|
||||
background: #EAEEF1;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 16px;
|
||||
|
||||
& > img {
|
||||
width: 48px;
|
||||
height: 45px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
& > label {
|
||||
width: 148px;
|
||||
height: 42px;
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 192px;
|
||||
height: 80px;
|
||||
background: #1365DD;
|
||||
border-radius: 4px;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32px;
|
||||
color: #FFFFFF;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
481
src/components/AiSelectEnterprise/tree.vue
Normal file
@@ -0,0 +1,481 @@
|
||||
<template>
|
||||
<div class="tree">
|
||||
<ai-top-fixed>
|
||||
<div class="top pad">
|
||||
<u-search v-if="searchIf" placeholder="搜索" @change="confirmSearch" @search="confirmSearch" :clearabled="true"
|
||||
v-model="keyword" :show-action="false" @clear="clear"></u-search>
|
||||
<u-tabs :list="list" :current="current" item-width="50%" height="96" bar-width="192"
|
||||
@change="tabChange"></u-tabs>
|
||||
</div>
|
||||
</ai-top-fixed>
|
||||
|
||||
<div class="tree-list">
|
||||
<scroll-view scroll-x class="scroll pad" style="width:100%" :scroll-left="scrollLeft">
|
||||
<div v-for="(item,index) in parent" class="inline-item" :key="index">
|
||||
<div class="inline-item" v-if="index==0" @click.stop="backTree(item,-1)">
|
||||
<text v-if="index==parent.length-1&&!isSear" class="none">可选范围</text>
|
||||
<text v-else class="active">可选范围</text>
|
||||
</div>
|
||||
<div v-if="index==0 && isSear" @click.stop="backTree(item,-2)"
|
||||
:class="[index==parent.length-1 && isSear] ? 'none inline-item':'active inline-item'">
|
||||
<span style="margin: 0 8px">/</span>
|
||||
搜索结果
|
||||
</div>
|
||||
<div class="inline-item" @click.stop="backTree(item,index)" v-if="index!=0">
|
||||
<span style="margin: 0 8px">/</span>
|
||||
<text v-if="index==parent.length-1" class="none inline-item">
|
||||
{{item[tag]}}
|
||||
</text>
|
||||
<text v-else class="active">
|
||||
{{item[tag]}}
|
||||
</text>
|
||||
</div>
|
||||
</div>
|
||||
</scroll-view>
|
||||
<div class="container-list">
|
||||
<div class="common" v-for="(item, index) in tree" @click.stop="toNext(item)" :key="index">
|
||||
<label class="content">
|
||||
<div class="checkbox" v-if="multiple" @click.stop="checkboxChange(item,index)">
|
||||
<img :src="$cdn + 'common/xzh.png'" v-if="item.checked" alt="">
|
||||
<img :src="$cdn + 'common/xzn.png'" v-else alt="">
|
||||
</div>
|
||||
<div class="checkbox" v-if="!multiple && (item.type==0 || item.openId)" @click.stop="checkbox(item,index)">
|
||||
<img :src="$cdn + 'common/xzh.png'" v-if="item.checked" alt="">
|
||||
<img :src="$cdn + 'common/xzn.png'" v-else alt="">
|
||||
</div>
|
||||
<div class="person" v-if="item.type==0">
|
||||
<u-avatar :src="item.avatar || ($cdn + 'common/xztx.png')" mode="square" :size="74"></u-avatar>
|
||||
</div>
|
||||
|
||||
<u-row justify="between" style="width: 100%;">
|
||||
<div class="word" v-if="tag=='name'">
|
||||
<img :src="$cdn + 'common/xzbq.png'" v-if="item.type==1" alt="">
|
||||
<span class="ellipsis">{{item[tag]}}</span>
|
||||
</div>
|
||||
<div class="word" v-else-if="tag=='tagname'">
|
||||
<template v-if="!item.openId">
|
||||
<img :src="$cdn + 'common/xzbqbottom.png'" alt="">
|
||||
<span class="ellipsis">{{item[tag]}}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<u-avatar :src="item.avatar || ($cdn + 'common/xztx.png')" mode="square" :size="74"
|
||||
style="margin: 0 17px;"></u-avatar>
|
||||
<span class="ellipsis">{{item["name"]}}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="right"
|
||||
v-if="item.type==1 && (item.childrenDept.length || item.childrenUser.length) && tag=='name'"></div>
|
||||
<div class="right" v-if="tag=='tagname' && !item.openId"></div>
|
||||
</u-row>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiTopFixed from "../AiTopFixed";
|
||||
|
||||
export default {
|
||||
name: "tree",
|
||||
components: {AiTopFixed},
|
||||
props: {
|
||||
checkList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
searchIf: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
rootId: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isSear: false,
|
||||
tree: [],
|
||||
parent: [1],
|
||||
searchResult: [],
|
||||
allData: [],
|
||||
newCheckList: this.checkList,
|
||||
scrollLeft: Infinity,
|
||||
keyword: "",
|
||||
current: 0,
|
||||
tag: "name",
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
tabChange(e) {
|
||||
this.tag = e == 0 ? "name" : "tagname"
|
||||
this.current = e
|
||||
this.parent = [1]
|
||||
// this.newCheckList = []
|
||||
this.getTree()
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.keyword = ""
|
||||
this.tree = this.allData
|
||||
this.parent = [1]
|
||||
this.isSear = false
|
||||
},
|
||||
|
||||
checkboxChange(item, index) {
|
||||
if (item.checked) {
|
||||
this.$set(this.tree[index], 'checked', false)
|
||||
this.delChild(item)
|
||||
for (let index = 0, n = this.newCheckList.length; index < n; index++) {
|
||||
let temp = this.newCheckList[index];
|
||||
if (temp.id == item.id) {
|
||||
this.newCheckList.splice(index, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
(item.type == 0 || item.openId) && this.newCheckList.push(item)
|
||||
this.$set(this.tree[index], 'checked', true)
|
||||
this.chooseChild(item)
|
||||
}
|
||||
this.$emit('sendValue', this.newCheckList)
|
||||
},
|
||||
|
||||
delUser(id) {
|
||||
for (let i = 0, len = this.newCheckList.length; i < len; i++) {
|
||||
if (this.newCheckList[i].id === id) {
|
||||
return this.newCheckList.splice(i, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
chooseChild(arr) {
|
||||
if (arr.childrenDept?.length) {
|
||||
for (let i = 0, len = arr.childrenDept.length; i < len; i++) {
|
||||
let item = arr.childrenDept[i]
|
||||
item.checked = true
|
||||
this.newCheckList.push(item)
|
||||
this.chooseChild(item)
|
||||
}
|
||||
}
|
||||
|
||||
if (arr.childrenUser?.length) {
|
||||
for (let i = 0, len = arr.childrenUser.length; i < len; i++) {
|
||||
let item = arr.childrenUser[i]
|
||||
item.checked = true
|
||||
this.newCheckList.push(item)
|
||||
this.chooseChild(item)
|
||||
}
|
||||
}
|
||||
|
||||
if (arr.users?.length) {
|
||||
for (let i = 0, len = arr.users.length; i < len; i++) {
|
||||
let item = arr.users[i]
|
||||
item.checked = true
|
||||
this.newCheckList.push(item)
|
||||
this.chooseChild(item)
|
||||
}
|
||||
}
|
||||
this.newCheckList = Array.from(new Set(this.newCheckList))
|
||||
},
|
||||
|
||||
delChild(arr) {
|
||||
if (arr.childrenDept?.length) {
|
||||
for (let i = 0, len = arr.childrenDept.length; i < len; i++) {
|
||||
let item = arr.childrenDept[i];
|
||||
item.checked = false
|
||||
for (let index = 0, n = this.newCheckList.length; index < n; index++) {
|
||||
let temp = this.newCheckList[index];
|
||||
if (temp.id == item.id) {
|
||||
this.newCheckList.splice(index, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
this.delChild(item)
|
||||
}
|
||||
}
|
||||
|
||||
if (arr.childrenUser?.length) {
|
||||
for (let i = 0, len = arr.childrenUser.length; i < len; i++) {
|
||||
let item = arr.childrenUser[i];
|
||||
item.checked = false
|
||||
for (let index = 0, n = this.newCheckList.length; index < n; index++) {
|
||||
let temp = this.newCheckList[index];
|
||||
if (temp.id == item.id) {
|
||||
this.newCheckList.splice(index, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
this.delChild(item)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//单选
|
||||
checkbox(item, index) {
|
||||
let status = !this.tree[index].checked
|
||||
this.$set(this.tree[index], 'checked', status)
|
||||
if (this.newCheckList.length <= 0) {
|
||||
this.newCheckList = [this.tree[index]]
|
||||
} else if (this.newCheckList.length == 1) {
|
||||
this.tree.forEach(item => {
|
||||
if (item.id != this.tree[index].id) {
|
||||
item.checked = false
|
||||
}
|
||||
})
|
||||
this.newCheckList = []
|
||||
if (this.tree[index].checked) {
|
||||
this.newCheckList.push(this.tree[index])
|
||||
}
|
||||
}
|
||||
this.$emit('sendValue', this.newCheckList)
|
||||
},
|
||||
|
||||
toNext(item) {
|
||||
if (this.tag == "name") {
|
||||
if (item.type == 1 && (item["childrenDept"].length || item["childrenUser"].length)) {
|
||||
this.tree = []
|
||||
if (item["childrenDept"].length) {
|
||||
this.tree = item["childrenDept"]
|
||||
}
|
||||
|
||||
if (item["childrenUser"].length) {
|
||||
this.tree = [...this.tree, ...item["childrenUser"]]
|
||||
}
|
||||
this.checkIf()
|
||||
if (this.parent[0].id !== item.id) {
|
||||
this.parent.push(item)
|
||||
}
|
||||
}
|
||||
} else if (this.tag == "tagname" && !item.openId) {
|
||||
this.tree = item.users
|
||||
if (this.parent[0].id !== item.id) {
|
||||
this.parent.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollLeft += 200
|
||||
})
|
||||
},
|
||||
|
||||
checkIf() {
|
||||
for (let i = 0, len = this.tree.length; i < len; i++) {
|
||||
for (let j = 0, lens = this.newCheckList.length; j < lens; j++) {
|
||||
if (this.newCheckList[j].id == this.tree[i].id) {
|
||||
this.$set(this.tree[i], 'checked', true)
|
||||
break
|
||||
} else {
|
||||
this.$set(this.tree[i], 'checked', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
confirmSearch(val) {
|
||||
this.searchResult = []
|
||||
this.search(this.tree, val)
|
||||
this.isSear = true
|
||||
this.parent.splice(1, Infinity)
|
||||
this.tree = this.searchResult
|
||||
if(!val) this.clear()
|
||||
},
|
||||
|
||||
search(data, keyword) {
|
||||
if (data.length) {
|
||||
for (let i = 0, len = data.length; i < len; i++) {
|
||||
if (data[i].name?.indexOf(keyword) != -1) {
|
||||
this.searchResult.push(data[i])
|
||||
}
|
||||
if (data[i]["childrenDept"]?.length || data[i]["childrenUser"]?.length) {
|
||||
this.search(data[i]["childrenDept"].concat(data[i]["childrenUser"]), keyword)
|
||||
}
|
||||
if (data[i]["users"]?.length) {
|
||||
this.search(data[i]["users"], keyword)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
backTree(item, index) {
|
||||
if (index == -1) {
|
||||
this.tree = this.allData
|
||||
this.parent.splice(1, Infinity)
|
||||
this.isSear = false
|
||||
this.keyword = ""
|
||||
} else if (index == -2) {
|
||||
this.tree = this.searchResult
|
||||
this.parent.splice(1, Infinity)
|
||||
} else {
|
||||
if (this.parent.length - index > 2) {
|
||||
this.parent.forEach((item, i) => {
|
||||
if (i > index) {
|
||||
this.parent.splice(i, Infinity)
|
||||
}
|
||||
})
|
||||
} else if (index != this.parent.length - 1) {
|
||||
this.parent.splice(this.parent.length - 1, 1)
|
||||
}
|
||||
this.tree = item["childrenDept"].concat(item["childrenUser"] || [])
|
||||
}
|
||||
if (this.multiple) return
|
||||
this.checkIf()
|
||||
},
|
||||
|
||||
getTree() {
|
||||
this.$http.post(this.current == 0 ? "/app/wxcp/wxuser/tree" : "/app/wxcp/wxtag/tree", null, {
|
||||
params: {
|
||||
rootId: this.rootId
|
||||
}
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
let result = this.tag == 'name' ? [res.data] : res.data
|
||||
this.tree = result
|
||||
this.allData = result
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
list() {
|
||||
return [
|
||||
{name: "组织架构"},
|
||||
{name: "标签"}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.getTree()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tree {
|
||||
min-height: 100%;
|
||||
background-color: #F5F5F5;
|
||||
|
||||
.top {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.tree-list {
|
||||
margin-top: 24px;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
.scroll {
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
|
||||
.inline-item {
|
||||
height: 112px;
|
||||
font-size: 30px;
|
||||
display: inline-block;
|
||||
line-height: 112px;
|
||||
|
||||
.active {
|
||||
color: #4297ED !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.none {
|
||||
color: #666666;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container-list {
|
||||
min-height: 1000px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
|
||||
.common {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
box-sizing: border-box;
|
||||
padding: 0 30px;
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100px;
|
||||
width: 100%;
|
||||
line-height: 100px;
|
||||
position: relative;
|
||||
font-size: 32px;
|
||||
|
||||
.right {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-right: 4px solid #CCCCCC;
|
||||
border-top: 4px solid #CCCCCC;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.word {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > img {
|
||||
width: 74px;
|
||||
height: 74px;
|
||||
margin: 0 34px;
|
||||
}
|
||||
|
||||
.ellipsis{
|
||||
width: 450px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.color {
|
||||
color: #00aaff;
|
||||
background-color: #00aaff;
|
||||
}
|
||||
}
|
||||
|
||||
.person {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #f57a00;
|
||||
font-size: 36px;
|
||||
text-align: center;
|
||||
margin: 0 34px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.pad {
|
||||
box-sizing: border-box;
|
||||
padding: 20px 32px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
src/components/AiTabbar.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<section class="AiTabbar">
|
||||
<div class="tabPane" v-for="(op,i) in tabbars" :key="i"
|
||||
@click="$emit('update:active',i)">
|
||||
<img :src="op.icon" alt=""/>
|
||||
<span :class="{active:i==active}">{{ op.text }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiTabbar",
|
||||
props: {
|
||||
active: {default: 0},
|
||||
list: {default: () => []},
|
||||
},
|
||||
computed: {
|
||||
tabbars() {
|
||||
return this.list.map((e, i) => ({
|
||||
...e,
|
||||
icon: i == this.active ? e.selectedIconPath : e.iconPath
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTabbar {
|
||||
height: 98px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background: #FFFFFF;
|
||||
border-top: 1px solid #ddd;
|
||||
display: flex;
|
||||
z-index: 9;
|
||||
|
||||
.tabPane {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 22px;
|
||||
cursor: pointer;
|
||||
|
||||
& > img {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
& > span {
|
||||
color: #C4CAD4;
|
||||
|
||||
&.active {
|
||||
color: #3267F0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
53
src/components/AiTable.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<section class="AiTable">
|
||||
<u-table color="#333">
|
||||
<u-tr>
|
||||
<u-th v-for="(col,i) in colConfigs" :key="i" :width="col.width">{{ col.label }}</u-th>
|
||||
</u-tr>
|
||||
<u-tr v-for="(row,j) in data" :key="j">
|
||||
<u-td v-for="(col,i) in colConfigs" :key="i" :width="col.width">
|
||||
<slot v-if="col.slot" :name="col.slot"/>
|
||||
<p v-else-if="col.dict">{{ $dict.getLabel(col.dict, row[col.prop]) }}</p>
|
||||
<p v-else>{{ row[col.prop] || "-" }}</p>
|
||||
</u-td>
|
||||
</u-tr>
|
||||
</u-table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UTable from "../uview/components/u-table/u-table";
|
||||
import UTd from "../uview/components/u-td/u-td";
|
||||
import UTh from "../uview/components/u-th/u-th";
|
||||
import UTr from "../uview/components/u-tr/u-tr";
|
||||
|
||||
export default {
|
||||
name: "AiTable",
|
||||
components: {UTr, UTh, UTd, UTable},
|
||||
props: {
|
||||
data: {default: () => []},
|
||||
colConfigs: {default: () => []},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTable {
|
||||
border-radius: 8px;
|
||||
min-height: 100px;
|
||||
overflow: hidden;
|
||||
|
||||
.u-table, .u-th {
|
||||
border-color: #D0D4DC !important;
|
||||
}
|
||||
|
||||
.u-th {
|
||||
background-color: #DFE6F4;
|
||||
color: #646D7F;
|
||||
}
|
||||
|
||||
.u-tr {
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
81
src/components/AiTabs.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<section class="AiTabs" :class="{wrap}">
|
||||
<div class="tabItem" v-for="(op,i) in ops" :key="i"
|
||||
:class="{active:value==op.value,plain}"
|
||||
:style="{width:itemWidth}"
|
||||
@tap="$emit('change',op.value)">
|
||||
{{ op.name }}
|
||||
</div>
|
||||
<div class="end">
|
||||
<slot name="end"/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiTabs",
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
value: {default: ""},
|
||||
ops: {default: () => []},
|
||||
wrap: Boolean,
|
||||
plain: Boolean,
|
||||
itemWidth: String
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTabs {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
max-height: 240px;
|
||||
overflow-y: auto;
|
||||
|
||||
&.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tabItem {
|
||||
flex-shrink: 0;
|
||||
min-width: 144px;
|
||||
max-width: 100%;
|
||||
min-height: 64px;
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
background: #FFFFFF;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #CCC;
|
||||
text-align: center;
|
||||
line-height: 64px;
|
||||
margin-bottom: 16px;
|
||||
margin-right: 16px;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.active {
|
||||
border-color: $uni-color-primary;
|
||||
color: $uni-color-primary;
|
||||
|
||||
&.plain {
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
background: $uni-color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.end {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
82
src/components/AiTextarea.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<section class="AiTextarea" :class="{border}">
|
||||
<u-input type="textarea" v-bind="$attrs" :value="value" :maxlength="maxlength"
|
||||
@input="handleInput" :disabled="disabled"/>
|
||||
<div class="bottomBar">
|
||||
<div class="leftPane">
|
||||
<slot name="bar"/>
|
||||
</div>
|
||||
<div v-if="!!maxlength">{{ value.length }}/{{ maxlength }}</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UInput from "../uview/components/u-input/u-input";
|
||||
|
||||
export default {
|
||||
name: "AiTextarea",
|
||||
components: {UInput},
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
value: {default: ""},
|
||||
maxlength: {default: 0},
|
||||
border: Boolean,
|
||||
disabled: Boolean
|
||||
},
|
||||
methods: {
|
||||
handleInput(v) {
|
||||
this.$emit('change', v)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTextarea {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
&.border {
|
||||
::v-deep textarea {
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e2e1e1;
|
||||
padding: 16px 16px 36px;
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 16px;
|
||||
}
|
||||
|
||||
::v-deep .u-input__right-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 16px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 36px;
|
||||
color: #999999;
|
||||
|
||||
.leftPane {
|
||||
display: flex;
|
||||
|
||||
& > * + * {
|
||||
margin-left: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
63
src/components/AiTopFixed.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<section class="AiTopFixed" :style="{background}">
|
||||
<!--占位区-->
|
||||
<div class="placeholder">
|
||||
<div v-if="$slots.tabs">
|
||||
<slot name="tabs"/>
|
||||
</div>
|
||||
<div class="content" v-if="$slots.default">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
<!--悬浮区-->
|
||||
<div class="fixed" :style="{background}">
|
||||
<div v-if="$slots.tabs">
|
||||
<slot name="tabs"/>
|
||||
</div>
|
||||
<div class="content" v-if="$slots.default">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiTopFixed",
|
||||
props: {
|
||||
background: {default: "#fff"}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTopFixed {
|
||||
width: 100%;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
width: 100%;
|
||||
top: 0;
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 32px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::v-deep .u-search {
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.02);
|
||||
margin-bottom: 32px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
229
src/components/AiUploader.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<div class="ai-uploader">
|
||||
<div class="fileList">
|
||||
<div class="item" v-for="(item, i) in fileList" :key="i">
|
||||
<template v-if="type == 'image'">
|
||||
<ai-image :src="item.url" :preview="preview"/>
|
||||
<div class="info">
|
||||
<i>{{ item.fileSizeStr }}</i>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ai-image :preview="preview" :file="item"/>
|
||||
<div class="info">
|
||||
<span>{{ item.name }} </span>
|
||||
<i>{{ item.fileSizeStr }}</i>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="!disabled">
|
||||
<div btn @tap="handleReUpload(i)">
|
||||
重新上传
|
||||
</div>
|
||||
<div btn @tap="remove(i)">
|
||||
删除
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="!disabled&&(fileList.length == 0 || (multiple && fileList.length < limit))" class="default"
|
||||
@click="upload">
|
||||
<i class="iconfont iconfont-iconAdd"/>
|
||||
<span>{{ placeholder }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import AiImage from './AiImage'
|
||||
|
||||
export default {
|
||||
name: 'AiUploader',
|
||||
components: {AiImage},
|
||||
props: {
|
||||
limit: {default: 1}, //数量
|
||||
placeholder: {default: '添加图片'}, // 文字提示
|
||||
type: {default: 'image'}, // 文件类型,image还是file
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
fileId: String,
|
||||
mediaId: String,
|
||||
def: {default: () => []},
|
||||
action: {default: '/app/wxcp/upload/uploadFile'},
|
||||
preview: Boolean,
|
||||
size: {default: 0},
|
||||
disabled: Boolean
|
||||
},
|
||||
computed: {
|
||||
...mapState(['baseURL', 'token']),
|
||||
errorImage() {
|
||||
return this.$cdn + 'file.png'
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
def: {
|
||||
handler(v) {
|
||||
if (!!v?.toString() && v?.url) {
|
||||
if (this.multiple) {
|
||||
this.fileList = v
|
||||
} else {
|
||||
this.fileList = [v]
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fileList: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove(index) {
|
||||
this.fileList.splice(index, 1)
|
||||
this.$emit('list', this.fileList)
|
||||
},
|
||||
upload(wait) {
|
||||
let params = {
|
||||
count: this.limit,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
let count = this.fileList?.length + (res.tempFiles?.length || res.tempFile ? 1 : 0)
|
||||
if (count > this.limit && this.limit !== 1) {
|
||||
return this.$u.toast(`不能超过${this.limit}个`)
|
||||
}
|
||||
if (res.tempFiles) {
|
||||
res.tempFiles?.map((item) => {
|
||||
this.uploadFile(item)
|
||||
})
|
||||
} else if (res?.tempFile) {
|
||||
this.uploadFile(res.tempFile)
|
||||
}
|
||||
},
|
||||
}
|
||||
typeof wait == 'function' && wait()
|
||||
if (this.type == 'image') {
|
||||
uni.chooseImage(params)
|
||||
} else if (this.type == 'video') {
|
||||
uni.chooseVideo(params)
|
||||
} else {
|
||||
uni.chooseFile(params)
|
||||
}
|
||||
},
|
||||
uploadFile(img) {
|
||||
if (this.size > 0 && img.size > this.size) {
|
||||
return this.$u.toast(`不能超过${Math.ceil(this.size / 1024 / 1024)}MB`)
|
||||
}
|
||||
uni.showLoading({title: '上传中'})
|
||||
let formData = new FormData()
|
||||
formData.append('file', img)
|
||||
this.$http
|
||||
.post(this.action, formData, {
|
||||
params: {type: this.type},
|
||||
})
|
||||
.then((res) => {
|
||||
uni.hideLoading()
|
||||
if (res?.data) {
|
||||
this.$emit('data', res.data)
|
||||
this.$u.toast('上传成功!')
|
||||
if (this.action == '/app/wxcp/upload/uploadFile') {
|
||||
this.$emit('update:mediaId', res.data?.media?.mediaId)
|
||||
this.$emit('update:fileId', res.data.file.id)
|
||||
this.fileList.push(res.data.file)
|
||||
} else if (this.action == '/admin/file/add2') {
|
||||
let info = res.data
|
||||
this.$emit('update:fileId', info?.id)
|
||||
this.fileList.push(res.data)
|
||||
}
|
||||
this.$emit("update:def", this.fileList)
|
||||
this.$emit("list", this.fileList)
|
||||
} else {
|
||||
this.$u.toast(res.msg)
|
||||
}
|
||||
})
|
||||
.catch(() => uni.hideLoading())
|
||||
},
|
||||
handleReUpload(i) {
|
||||
this.upload(() => this.remove(i))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-uploader {
|
||||
width: 100%;
|
||||
line-height: normal;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.fileList {
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
image {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
div[btn] {
|
||||
color: $uni-color-primary;
|
||||
}
|
||||
|
||||
div:nth-child(4) {
|
||||
color: #f72c27;
|
||||
}
|
||||
|
||||
& > * + * {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.default {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 8px;
|
||||
background: #f3f4f7;
|
||||
color: #89b;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.iconfont-iconAdd {
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
185
src/components/AiVideo.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<view class="imt-audio">
|
||||
<view class="audio-wrapper">
|
||||
<view class="audio-number">{{format(current)}}</view>
|
||||
<slider class="audio-slider" :activeColor="color" block-size="16" :value="current" :max="duration || 10" @changing="seek=true,current=$event.detail.value" @change="audio.seek($event.detail.value)"></slider>
|
||||
<view class="audio-number">{{format(duration)}}</view>
|
||||
</view>
|
||||
<view class="audio-control-wrapper" :style="{color}">
|
||||
<image
|
||||
class="audio-control audio-control-switch"
|
||||
@click="audio.paused?play():audio.pause()"
|
||||
:src="paused ? playImg : stopImg" />
|
||||
<p>{{ paused ? '点击播放' : '点击停止播放' }}</p>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import stopImg from '../pages/resourcesManage/img/stop-img.png'
|
||||
import playImg from '../pages/resourcesManage/img/play-icon.png'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
audio: uni.createInnerAudioContext(),
|
||||
current: 0, //当前进度(s)
|
||||
duration: 0, //总时长(s)
|
||||
paused: true, //是否处于暂停状态
|
||||
loading: false, //是否处于读取状态
|
||||
seek: false,
|
||||
stopImg,
|
||||
playImg
|
||||
}
|
||||
},
|
||||
props: {
|
||||
src: String, //音频链接
|
||||
autoplay: Boolean, //是否自动播放
|
||||
continue: Boolean, //播放完成后是否继续播放下一首,需定义@next事件
|
||||
control: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}, //是否需要上一曲/下一曲按钮
|
||||
color: {
|
||||
type: String,
|
||||
default: '#007BFF'
|
||||
} //主色调
|
||||
},
|
||||
methods: {
|
||||
//返回prev事件
|
||||
prev() {
|
||||
this.$emit('prev')
|
||||
},
|
||||
//返回next事件
|
||||
next() {
|
||||
this.$emit('next')
|
||||
},
|
||||
//格式化时长
|
||||
format(num) {
|
||||
return '0'.repeat(2 - String(Math.floor(num / 60)).length) + Math.floor(num / 60) + ':' + '0'.repeat(2 - String(Math.floor(num % 60)).length) + Math.floor(num % 60)
|
||||
},
|
||||
//点击播放按钮
|
||||
play() {
|
||||
this.audio.play()
|
||||
this.loading = true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.src) {
|
||||
this.audio.src = this.src
|
||||
this.autoplay && this.play()
|
||||
}
|
||||
this.audio.obeyMuteSwitch = false
|
||||
//音频进度更新事件
|
||||
this.audio.onTimeUpdate(() => {
|
||||
if (!this.seek) {
|
||||
this.current = this.audio.currentTime
|
||||
}
|
||||
if (!this.duration) {
|
||||
this.duration = this.audio.duration
|
||||
}
|
||||
})
|
||||
//音频播放事件
|
||||
this.audio.onPlay(() => {
|
||||
this.paused = false
|
||||
this.loading = false
|
||||
})
|
||||
//音频暂停事件
|
||||
this.audio.onPause(() => {
|
||||
this.paused = true
|
||||
})
|
||||
//音频结束事件
|
||||
this.audio.onEnded(() => {
|
||||
if (this.continue) {
|
||||
this.next()
|
||||
} else {
|
||||
this.paused = true
|
||||
this.current = 0
|
||||
}
|
||||
})
|
||||
//音频完成更改进度事件
|
||||
this.audio.onSeeked(() => {
|
||||
this.seek = false
|
||||
})
|
||||
},
|
||||
beforeDestroy(){
|
||||
this.audio.destroy()
|
||||
},
|
||||
watch: {
|
||||
src(src, old) {
|
||||
this.audio.src = src
|
||||
this.current = 0
|
||||
this.duration = 0
|
||||
if (old || this.autoplay) {
|
||||
this.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.imt-audio {
|
||||
background: #fff;
|
||||
border-radius: 20upx;
|
||||
}
|
||||
|
||||
.audio-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.audio-number {
|
||||
width: 120upx;
|
||||
font-size: 24upx;
|
||||
line-height: 1;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-slider {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.audio-control-wrapper {
|
||||
margin-top: 40upx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.audio-control-wrapper p {
|
||||
color: #999999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.audio-control-wrapper image {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
}
|
||||
|
||||
.audio-control {
|
||||
font-size: 32upx;
|
||||
line-height: 1;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.audio-control-next {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.audio-control-switch {
|
||||
font-size: 40upx;
|
||||
margin: 0 100upx;
|
||||
}
|
||||
|
||||
.audioLoading {
|
||||
animation: loading 2s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
src/components/VDrag.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<section class="VDrag">
|
||||
<vuedraggable v-bind="$attrs" @change="handleChange">
|
||||
<slot/>
|
||||
</vuedraggable>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import vuedraggable from 'vuedraggable'
|
||||
|
||||
export default {
|
||||
name: "VDrag",
|
||||
components: {vuedraggable},
|
||||
data: () => ({
|
||||
moveEvt: null
|
||||
}),
|
||||
methods: {
|
||||
handleChange(moved) {
|
||||
this.$emit('move', moved)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.VDrag {
|
||||
}
|
||||
</style>
|
||||
39
src/main.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import Vue from 'vue';
|
||||
import App from './App';
|
||||
import store from './store';
|
||||
import axios from './common/axios';
|
||||
import utils from './common/util';
|
||||
import dayjs from 'dayjs';
|
||||
import mixin from './uview/libs/mixin/mixin';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.prototype.$store = store;
|
||||
//初始化接口工具类
|
||||
axios.defaults.baseURL = store.state.baseURL;
|
||||
Vue.prototype.$http = axios;
|
||||
Vue.prototype.$cdn = 'https://cdn.cunwuyun.cn/dvcp/h5/';
|
||||
Vue.prototype.imgHomeUrl = 'https://cdn.cunwuyun.cn/dvcp/h5/home/';
|
||||
Vue.prototype.imgOtherUrl = 'https://cdn.cunwuyun.cn/dvcp/h5/other/';
|
||||
Vue.prototype.$formatName = (name) => {
|
||||
if (name == undefined) {
|
||||
return;
|
||||
}
|
||||
return name.substr(name.length - 2, name.length > 2 ? name.length - 1 : name.length);
|
||||
};
|
||||
Object.keys(utils).map((e) => (Vue.prototype['$' + e] = utils[e]));
|
||||
let relativeTime = require('dayjs/plugin/relativeTime');
|
||||
require('dayjs/locale/zh-cn');
|
||||
let dayjs_plugin_duration = require('dayjs/plugin/duration');
|
||||
dayjs.extend(dayjs_plugin_duration);
|
||||
dayjs.extend(relativeTime);
|
||||
Vue.prototype.$dayjs = dayjs;
|
||||
Vue.mixin(mixin);
|
||||
|
||||
App.mpType = 'app';
|
||||
process.env.NODE_ENV == 'development' && new VConsole();
|
||||
const app = new Vue({
|
||||
store,
|
||||
...App
|
||||
});
|
||||
|
||||
app.$mount();
|
||||
37
src/manifest.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "dv_cp_weixin",
|
||||
"description": "企业微信应用",
|
||||
"versionName": "1.0.0",
|
||||
"versionCode": "100",
|
||||
"transformPx": true,
|
||||
"mp-weixin": {
|
||||
"appid": "",
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
},
|
||||
"usingComponents": true,
|
||||
"permission": {
|
||||
"scope.userLocation": {
|
||||
"desc": "演示定位能力"
|
||||
}
|
||||
}
|
||||
},
|
||||
"h5": {
|
||||
"title": "数字乡村",
|
||||
"template": "template.h5.html",
|
||||
"router": {
|
||||
"mode": "history",
|
||||
"base": ""
|
||||
},
|
||||
"devServer": {
|
||||
"disableHostCheck": true,
|
||||
"port": "10323"
|
||||
},
|
||||
"optimization": {
|
||||
"preload": true,
|
||||
"treeShaking": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
411
src/pages.json
Normal file
@@ -0,0 +1,411 @@
|
||||
{
|
||||
"easycom": {
|
||||
"^u-(.*)": "@/uview/components/u-$1/u-$1.vue"
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/loading",
|
||||
"style": {
|
||||
"navigationBarTitleText": "欢迎使用村微..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/app",
|
||||
"style": {
|
||||
"navigationBarTitleText": "应用"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login"
|
||||
},
|
||||
{
|
||||
"path": "pages/notification/notification",
|
||||
"style": {
|
||||
"navigationBarTitleText": "通知公告"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/notification/components/read",
|
||||
"style": {
|
||||
"navigationBarTitleText": "接收情况"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/notification/components/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公告详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/notification/components/add",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增公告"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workTask/workTask",
|
||||
"style": {
|
||||
"navigationBarTitleText": "工作任务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workTask/components/create",
|
||||
"style": {
|
||||
"navigationBarTitleText": "创建任务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workTask/components/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "任务详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workTask/components/subDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "任务详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workTask/components/finishDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "完成详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workTask/components/finish",
|
||||
"style": {
|
||||
"navigationBarTitleText": "完成任务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/documentFlow/documentFlow",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公文流转"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/documentFlow/components/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公文详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/documentFlow/components/approval",
|
||||
"style": {
|
||||
"navigationBarTitleText": "批示"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/meetingNotice/meetingNotice",
|
||||
"style": {
|
||||
"navigationBarTitleText": "会议通知"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/meetingNotice/components/addMeeting",
|
||||
"style": {
|
||||
"navigationBarTitleText": "发起会议"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/meetingNotice/components/belongToMe",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我发起的"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/meetingNotice/components/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "会议详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/meetingNotice/components/meetingList"
|
||||
},
|
||||
{
|
||||
"path": "pages/askForm/askForm"
|
||||
},
|
||||
{
|
||||
"path": "pages/askForm/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "问卷表单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/askForm/formSetting",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/askForm/filedConfig",
|
||||
"style": {
|
||||
"navigationBarTitleText": "组件设置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/askForm/previewForm",
|
||||
"style": {
|
||||
"navigationBarTitleText": "表单预览"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/askForm/addForm",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新建调查表单"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/resident/resident"
|
||||
},
|
||||
{
|
||||
"path": "pages/resident/comp",
|
||||
"style": {
|
||||
"navigationBarTitleText": "居民管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/resident/groupResident"
|
||||
},
|
||||
{
|
||||
"path": "pages/whereabouts/whereabouts"
|
||||
},
|
||||
{
|
||||
"path": "pages/interview/interview",
|
||||
"style": {
|
||||
"navigationBarTitleText": "调查走访"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/interview/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "新增走访"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/quickReply/quickReply"
|
||||
},
|
||||
{
|
||||
"path": "pages/quickReply/typeManage"
|
||||
},
|
||||
{
|
||||
"path": "pages/quickReply/replyDetail"
|
||||
},
|
||||
{
|
||||
"path": "pages/supermarket/supermarket",
|
||||
"style": {
|
||||
"navigationBarTitleText": "信用好超市"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/supermarket/balance",
|
||||
"style": {
|
||||
"navigationBarTitleText": "结算"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/supermarket/search",
|
||||
"style": {
|
||||
"navigationBarTitleText": "搜索"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/supermarket/components/resultPage/resultPage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "结算提交"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/casuallyask/casuallyask",
|
||||
"style": {
|
||||
"navigationBarTitleText": "随心问"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/casuallyask/casuallyaskDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "随心问(详情页)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/casuallyask/closemsg",
|
||||
"style": {
|
||||
"navigationBarTitleText": "关闭留言"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/casuallyask/truemsg",
|
||||
"style": {
|
||||
"navigationBarTitleText": "提交留言"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/casuallyask/reply",
|
||||
"style": {
|
||||
"navigationBarTitleText": "发表回复"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workonline/workonline",
|
||||
"style": {
|
||||
"navigationBarTitleText": "网上办事"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workonline/detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "进度详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workonline/approvalopinion",
|
||||
"style": {
|
||||
"navigationBarTitleText": "进度详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/workonline/truemsg",
|
||||
"style": {
|
||||
"navigationBarTitleText": "进度详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/snapshot/snapshot",
|
||||
"style": {
|
||||
"navigationBarTitleText": "随手拍"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/snapshot/snapshotDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "随手拍详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/snapshot/handleResult",
|
||||
"style": {
|
||||
"navigationBarTitleText": "处理结果"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/snapshot/components/handlePage/handlePage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "处理结果"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/villageQRCode/villageQRCode",
|
||||
"style": {
|
||||
"navigationBarTitleText": "一村一码"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/guardianship/guardianship",
|
||||
"style": {
|
||||
"navigationBarTitleText": "智慧监护"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/guardianship/userDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/guardianship/historyList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "历史记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/guardianship/warningDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "预警"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/bigHorn/bigHorn",
|
||||
"style": {
|
||||
"navigationBarTitleText": "广播通知"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/bigHorn/playList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "播放记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/bigHorn/onlineList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "在线设备"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/bigHorn/onlinePlayList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "在播设备"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/bigHorn/addPlay",
|
||||
"style": {
|
||||
"navigationBarTitleText": "音频播放"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/bigHorn/selectMp3",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择内容"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/bigHorn/selectEquipment",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择设备"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/resourcesManage/resourcesManage",
|
||||
"style": {
|
||||
"navigationBarTitleText": "媒资管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/resourcesManage/addPlay",
|
||||
"style": {
|
||||
"navigationBarTitleText": "媒资管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/resourcesManage/talking",
|
||||
"style": {
|
||||
"navigationBarTitleText": "实时喊话"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/resourcesManage/recording",
|
||||
"style": {
|
||||
"navigationBarTitleText": "音频录制"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/videoSurveillance/videoSurveillance",
|
||||
"style": {
|
||||
"navigationBarTitleText": "视频监控"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/videoSurveillance/monitorDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "实时监控",
|
||||
"pageOrientation": "landscape"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"pageOrientation": "auto",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
174
src/pages/app.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<section class="app">
|
||||
<u-swiper :list="list" height="240" bg-color="#408CFF"></u-swiper>
|
||||
<label class="useful-app">常用应用</label>
|
||||
<u-grid
|
||||
:col="o.length"
|
||||
:border="false"
|
||||
v-for="(o, i) in gridGroup"
|
||||
:key="i"
|
||||
>
|
||||
<u-grid-item v-for="(i, j) in o" :key="j" @click="linkTo(i)">
|
||||
<u-icon :name="i.icon" :size="100"></u-icon>
|
||||
<view class="grid-text">{{ i.label }}</view>
|
||||
</u-grid-item>
|
||||
</u-grid>
|
||||
<label class="useful-app notice">通知公告</label>
|
||||
<section class="list">
|
||||
<div class="item" v-for="(item, index) in dataList" :key="index">
|
||||
<label class="title hidden">{{ item.title }}</label>
|
||||
<div class="content hidden">{{ item.content }}</div>
|
||||
<div class="date">{{ item.createTime }}</div>
|
||||
</div>
|
||||
<u-loadmore :status="status" />
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'app',
|
||||
data() {
|
||||
return {
|
||||
list: [this.imgHomeUrl + 'banner.png'],
|
||||
gridGroup: [
|
||||
[
|
||||
{
|
||||
icon: `${this.imgHomeUrl}icon1.png`,
|
||||
label: '随心问',
|
||||
link: '/pages/casuallyask/casuallyask'
|
||||
},
|
||||
{
|
||||
icon: `${this.imgHomeUrl}icon2.png`,
|
||||
label: '随手拍',
|
||||
link: '/pages/snapshot/snapshot'
|
||||
},
|
||||
{
|
||||
icon: `${this.imgHomeUrl}icon3.png`,
|
||||
label: '网上办事',
|
||||
link: '/pages/workonline/workonline'
|
||||
},
|
||||
{ icon: `${this.imgHomeUrl}icon4.png`, label: '调查走访' }
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: `${this.imgHomeUrl}icon5.png`,
|
||||
label: '信用好超市',
|
||||
link: '/pages/supermarket/supermarket'
|
||||
},
|
||||
{ icon: `${this.imgHomeUrl}icon6.png`, label: '中心工作' },
|
||||
{ icon: `${this.imgHomeUrl}icon7.png`, label: '会务通知' },
|
||||
{ icon: `${this.imgHomeUrl}icon8.png`, label: '工作去向' },
|
||||
{ icon: `${this.imgHomeUrl}icon8.png`, label: '大喇叭' }
|
||||
]
|
||||
],
|
||||
status: 'nomore',
|
||||
dataList: [],
|
||||
current: 0
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
if (this.loadingStatus == 'nomore') return
|
||||
this.$http
|
||||
.post(`/app/appworkcenternotice/list`, null, {
|
||||
params: {
|
||||
withoutToken: true,
|
||||
current: this.current + 1,
|
||||
size: 10
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
if (res && res.data) {
|
||||
this.dataList = res.data.records
|
||||
}
|
||||
})
|
||||
},
|
||||
linkTo(val) {
|
||||
if (val.link) {
|
||||
uni.navigateTo({ url: val.link })
|
||||
}
|
||||
}
|
||||
},
|
||||
onReachBottom() {
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app {
|
||||
min-height: 100%;
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
padding: 32px;
|
||||
position: relative;
|
||||
|
||||
.grid-text {
|
||||
font-size: 28px;
|
||||
color: #333333;
|
||||
line-height: 46px;
|
||||
}
|
||||
|
||||
.useful-app {
|
||||
margin-top: 80px;
|
||||
display: inherit;
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin-top: 56px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 38px;
|
||||
|
||||
.item {
|
||||
height: 192px;
|
||||
background: #f7f9ff;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
margin-bottom: 32px;
|
||||
|
||||
.title {
|
||||
font-size: 32px;
|
||||
font-weight: 800;
|
||||
color: #343d65;
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: 28px;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
margin: 20px 0 8px 0;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 28px;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
::v-deep .u-indicator-item-round {
|
||||
background-color: #d8dde6;
|
||||
}
|
||||
|
||||
::v-deep .u-indicator-item-round-active {
|
||||
background-color: #408cff !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
700
src/pages/askForm/addForm.vue
Normal file
@@ -0,0 +1,700 @@
|
||||
<template>
|
||||
<div class="add-form" v-if="pageShow">
|
||||
<div class="header-pic">
|
||||
<image v-if="form.headPicture" :src="form.headPicture" />
|
||||
<span @click="upload">更换图片</span>
|
||||
</div>
|
||||
<div class="form-info">
|
||||
<h2>文本选项</h2>
|
||||
<div class="form-info__wrapper">
|
||||
<textarea class="title" placeholder="请输入标题 (必填)" :maxlength="30" :auto-height="true" v-model="form.title"></textarea>
|
||||
<u-input class="content" :clearable="false" type="textarea" v-model="form.tableExplain" placeholder="请输入表单描述 (选填)" :height="80" :auto-height="true" :maxlength="255"></u-input>
|
||||
</div>
|
||||
</div>
|
||||
<draggable
|
||||
class="components-list"
|
||||
v-model="targetList"
|
||||
:animation="340"
|
||||
scroll
|
||||
element="div"
|
||||
:options="{
|
||||
animation: 340,
|
||||
handle: '.components-item__title'
|
||||
}"
|
||||
draggable=".components-item"
|
||||
:sort="true">
|
||||
<div class="components-item" v-for="(item, index) in targetList" :key="index" @click="toFiledSetting(item, index)">
|
||||
<div class="components-item__title">
|
||||
<div class="components-item__title--left">
|
||||
<em :style="{opacity: item.required ? 1 : 0}">*</em>
|
||||
<i>{{ index + 1 }}.</i>
|
||||
<h2>{{ item.label }}</h2>
|
||||
</div>
|
||||
<image :src="`${$cdn}askform/sc1.png`" @click.stop="removeComponent(index)" @touchstart.stop="removeComponent(index)" />
|
||||
</div>
|
||||
<div class="components-item__filed">
|
||||
<template v-if="(item.type === 'radio')">
|
||||
<u-radio-group v-model="item.value" wrap>
|
||||
<u-radio disabled :name="field.label" v-for="(field, i) in item.options" :key="i">
|
||||
<image :src="field.img[0].url" v-if="field.img.length"/>
|
||||
<span>{{ field.label }}</span>
|
||||
</u-radio>
|
||||
</u-radio-group>
|
||||
</template>
|
||||
<template v-if="(item.type === 'checkbox')">
|
||||
<u-checkbox-group v-model="item.value" wrap>
|
||||
<u-checkbox disabled :name="field.label" v-for="(field, i) in item.options" :key="i">
|
||||
<image :src="field.img[0].url" v-if="field.img.length"/>
|
||||
<span>{{ field.label }}</span>
|
||||
</u-checkbox>
|
||||
</u-checkbox-group>
|
||||
</template>
|
||||
<template v-if="(item.type === 'select')">
|
||||
<div class="components-item__select">
|
||||
<span>{{ item.placeholder }}</span>
|
||||
<u-icon name="arrow-down" color="#DEDFDF" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="(item.type === 'upload')">
|
||||
<div class="components-item__select components-item__textarea components-item__upload">
|
||||
<image :src="`${$cdn}askform/upload.png`" />
|
||||
<span>选择图片(2M以内)</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="(item.type === 'input')">
|
||||
<div class="components-item__select">
|
||||
<span>{{ item.placeholder }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="(item.type === 'textarea')">
|
||||
<div class="components-item__select components-item__textarea">
|
||||
<span>{{ item.placeholder }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
<div class="add-form__btn" @click="isShow = true">
|
||||
<image :src="`${$cdn}askform/add.png`" />
|
||||
<span>添加问题</span>
|
||||
</div>
|
||||
<div class="add-form__footer">
|
||||
<div>
|
||||
<span @click="toPreview">预览</span>
|
||||
<span @click="toSetting">设置</span>
|
||||
</div>
|
||||
<div @click="onConfirm">立即发布</div>
|
||||
</div>
|
||||
<u-popup v-model="isShow" :closeable="false" mode="bottom">
|
||||
<div class="add-popup">
|
||||
<div class="add-popup__title">
|
||||
<h2>添加问题</h2>
|
||||
<image :src="`${$cdn}askform/zk.png`" mode="aspectFit" @click="isShow = false" />
|
||||
</div>
|
||||
<div class="add-popup__list">
|
||||
<span @click="toFiledSetting('radio')">单选题</span>
|
||||
<span @click="toFiledSetting('checkbox')">多选题</span>
|
||||
<span @click="toFiledSetting('select')">单下拉框</span>
|
||||
<span @click="toFiledSetting('input')">单行填空</span>
|
||||
<span @click="toFiledSetting('textarea')">多行填空</span>
|
||||
<span @click="toFiledSetting('upload')">上传图片</span>
|
||||
</div>
|
||||
</div>
|
||||
</u-popup>
|
||||
<AiBack></AiBack>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiBack from "@/components/AiBack";
|
||||
import draggable from 'vuedraggable'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
pageShow: false,
|
||||
form: {
|
||||
tableExplain: '详细描述',
|
||||
title: '问卷调查',
|
||||
isShowheadPicture: true,
|
||||
isShowTableExplain: true,
|
||||
isShowBtn: true,
|
||||
headPicture: '',
|
||||
commitType: '1',
|
||||
periodValidityType: '0',
|
||||
actionNotice: '1',
|
||||
dynamicNotice: '1',
|
||||
periodValidityEndTime: '',
|
||||
shareStatus: '0',
|
||||
count: 0,
|
||||
wechatId: '0',
|
||||
type: 0,
|
||||
buttonExplain: '提交',
|
||||
tips: true
|
||||
},
|
||||
templateType: 0,
|
||||
targetList: [],
|
||||
isShow: false,
|
||||
type: 0,
|
||||
id: '',
|
||||
isQuote: false,
|
||||
touchStart: 0,
|
||||
formConfig: {}
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AiBack,
|
||||
draggable
|
||||
},
|
||||
|
||||
onLoad (query) {
|
||||
this.type = Number(query.type)
|
||||
|
||||
if (query.isQuote) {
|
||||
this.isQuote = true
|
||||
}
|
||||
|
||||
if (query.id) {
|
||||
this.id = query.id
|
||||
this.getInfo(query.id)
|
||||
} else {
|
||||
this.pageShow = true
|
||||
}
|
||||
this.init()
|
||||
|
||||
uni.$on('setting', res => {
|
||||
this.form = {
|
||||
...this.form,
|
||||
...res
|
||||
}
|
||||
this.formConfig = res
|
||||
})
|
||||
|
||||
uni.$on('filedConfig', res => {
|
||||
if (res.index === '-1') {
|
||||
this.targetList.push(res.config)
|
||||
} else {
|
||||
this.$set(this.targetList, [res.index], res.config)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
toSetting () {
|
||||
uni.navigateTo({
|
||||
url: `/pages/askForm/formSetting?id=${this.id}&formConfig=${JSON.stringify(this.formConfig)}`
|
||||
})
|
||||
},
|
||||
|
||||
removeComponent (index) {
|
||||
this.targetList.splice(index, 1)
|
||||
},
|
||||
|
||||
toPreview () {
|
||||
uni.navigateTo({
|
||||
url: `/pages/askForm/previewForm?targetList=${JSON.stringify(this.targetList)}&form=${JSON.stringify(this.form)}`
|
||||
})
|
||||
},
|
||||
|
||||
upload() {
|
||||
let params = {
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
let count = this.fileList?.length + (res.tempFiles?.length || res.tempFile ? 1 : 0)
|
||||
if (count > 1) {
|
||||
return this.$u.toast(`不能超过1个`)
|
||||
}
|
||||
if (res.tempFiles) {
|
||||
res.tempFiles.map((item) => {
|
||||
this.uploadFile(item)
|
||||
})
|
||||
} else if (res?.tempFile) {
|
||||
this.uploadFile(res.tempFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
uni.chooseImage(params)
|
||||
},
|
||||
|
||||
uploadFile (img) {
|
||||
uni.showLoading({title: '上传中'})
|
||||
let formData = new FormData()
|
||||
formData.append('file', img)
|
||||
this.$http.post('/admin/file/add2', formData).then((res) => {
|
||||
uni.hideLoading()
|
||||
if (res?.data) {
|
||||
this.$u.toast('上传成功!')
|
||||
this.form.headPicture = res.data.url
|
||||
}
|
||||
}).catch(res => {
|
||||
this.$u.toast(res)
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
onConfirm () {
|
||||
for (let item of this.targetList) {
|
||||
if (item.isShowPoints) {
|
||||
if (item.pointType === '0') {
|
||||
if (!item.answer || JSON.stringify(item.answer) === '[]') {
|
||||
return this.$u.toast(`请输入${item.label}正确答案`)
|
||||
}
|
||||
|
||||
if (!item.points) {
|
||||
return this.$u.toast(`请输入${item.label}的分值`)
|
||||
}
|
||||
}
|
||||
|
||||
if (item.pointType === '1') {
|
||||
for (let option of item.options) {
|
||||
if (!option.point) {
|
||||
return this.$u.toast(`请输入${item.label}${option.label}的分值`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (item.pointType === '2') {
|
||||
for (let option of item.options) {
|
||||
if (!option.point) {
|
||||
return this.$u.toast(`请输入${item.label}${option.label}的分值`)
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.points) {
|
||||
return this.$u.toast(`请输入${item.label}全部答对分值`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fields = this.targetList.map(item => {
|
||||
return {
|
||||
fieldType: item.type,
|
||||
fieldName: item.label,
|
||||
fieldInfo: JSON.stringify(item)
|
||||
}
|
||||
})
|
||||
|
||||
this.$http.post(`/app/appquestionnairetemplate/addOrUpdate`, {
|
||||
...this.form,
|
||||
fields,
|
||||
status: 1,
|
||||
id: this.isQuote ? '' : this.id,
|
||||
headPicture: this.form.headPicture,
|
||||
type: this.type,
|
||||
templateType: 0
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$u.toast('提交成功')
|
||||
|
||||
setTimeout(() => {
|
||||
uni.$emit('reload')
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}, 600)
|
||||
}
|
||||
}).catch(e => {
|
||||
this.$u.toast(e)
|
||||
})
|
||||
},
|
||||
|
||||
getInfo (id) {
|
||||
uni.showLoading()
|
||||
this.$http.post(`/app/appquestionnairetemplate/queryDetailById?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.form = {
|
||||
...res.data,
|
||||
headPicture: res.data.headPicture
|
||||
}
|
||||
|
||||
this.type = res.data.type
|
||||
|
||||
this.targetList = res.data.fields.map(item => {
|
||||
return JSON.parse(item.fieldInfo)
|
||||
})
|
||||
|
||||
this.pageShow = true
|
||||
} else {
|
||||
this.$u.toast(res.msg)
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
}).catch(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
toFiledSetting (type, index) {
|
||||
if (index != undefined) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/askForm/filedConfig?config=${JSON.stringify(type)}&index=${index}`
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
this.isShow = false
|
||||
uni.navigateTo({
|
||||
url: `/pages/askForm/filedConfig?type=${type}`
|
||||
})
|
||||
},
|
||||
|
||||
init () {
|
||||
if (this.type == 0) {
|
||||
this.form.headPicture = 'https://cdn.cunwuyun.cn/dvcp/h5/form/interview.png'
|
||||
}
|
||||
if (this.type == 1) {
|
||||
this.form.title = '考试测评'
|
||||
this.form.headPicture = 'https://cdn.cunwuyun.cn/dvcp/h5/form/exam.png'
|
||||
}
|
||||
if (this.type == 2) {
|
||||
this.form.title = '报名登记'
|
||||
this.form.headPicture = 'https://cdn.cunwuyun.cn/dvcp/h5/form/apply.png'
|
||||
}
|
||||
if (this.type == 3) {
|
||||
this.form.title = '满意调查'
|
||||
this.form.headPicture = 'https://cdn.cunwuyun.cn/dvcp/h5/form/satisfaction.png'
|
||||
}
|
||||
if (this.type == 4) {
|
||||
this.form.title = '投票评选'
|
||||
this.form.headPicture = 'https://cdn.cunwuyun.cn/dvcp/h5/form/vote.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.add-form {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 140px;
|
||||
box-sizing: border-box;
|
||||
background: #F3F6F9;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::v-deep .u-drawer-bottom {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.components-list {
|
||||
padding: 0 20px;
|
||||
|
||||
.components-item {
|
||||
margin-top: 24px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 4px 8px 4px rgba(233, 233, 233, 0.39);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #EEEFF0;
|
||||
background: #fff;
|
||||
|
||||
::v-deep .u-radio, ::v-deep .u-checkbox {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.u-checkbox__icon-wrap, .u-radio__icon-wrap {
|
||||
position: absolute;
|
||||
top: 4rpx;
|
||||
}
|
||||
|
||||
.u-radio__label, .u-checkbox__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 0;
|
||||
margin-left: 40px;
|
||||
text-align: justify;
|
||||
|
||||
span {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
span {
|
||||
flex: 1;
|
||||
color: #666;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.components-item__select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0 26px;
|
||||
border: 1px solid #DEDFDF;
|
||||
|
||||
&.components-item__textarea {
|
||||
align-items: flex-start;
|
||||
height: 160px;
|
||||
padding-top: 20px;
|
||||
|
||||
image {
|
||||
width: 46px;
|
||||
height: 34px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
&.components-item__upload {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.components-item__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32px;
|
||||
|
||||
em {
|
||||
margin-right: 4px;
|
||||
font-style: normal;
|
||||
color: rgb(226, 33, 32);;
|
||||
}
|
||||
|
||||
image {
|
||||
position: relative;
|
||||
flex-shrink: 1;
|
||||
right: -20px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
box-sizing: content-box;
|
||||
padding: 30px 20px 30px 20px;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
max-width: 550px;
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-popup {
|
||||
height: 440px;
|
||||
border-radius: 20px 20px 0 0;
|
||||
background: #fff;
|
||||
|
||||
.add-popup__title {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 96px;
|
||||
border-bottom: 1px solid #E4E5E6;
|
||||
|
||||
h2 {
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
image {
|
||||
position: absolute;
|
||||
right: 32px;
|
||||
top: 50%;
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.add-popup__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 34px;
|
||||
|
||||
span {
|
||||
width: calc((100% - 64px) / 3);
|
||||
height: 78px;
|
||||
line-height: 78px;
|
||||
margin-top: 32px;
|
||||
margin-right: 32px;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
font-size: 28px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #E4E5E6;
|
||||
|
||||
&:nth-of-type(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-form__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 112px;
|
||||
text-align: center;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
|
||||
&:last-child {
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
background: #3192F4;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
line-height: 112px;
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
|
||||
&:active {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-form__btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 214px;
|
||||
height: 66px;
|
||||
line-height: 66px;
|
||||
margin: 64px auto 0;
|
||||
background: #FFFFFF;
|
||||
border-radius: 34px;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 16px;
|
||||
color: #4392E6;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-info {
|
||||
padding: 0 20px;
|
||||
|
||||
& > h2 {
|
||||
height: 76px;
|
||||
line-height: 76px;
|
||||
color: #999999;
|
||||
font-weight: normal;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.form-info__wrapper {
|
||||
padding: 0 18px;
|
||||
background: #fff;
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
padding: 22px 0;
|
||||
font-size: 36px;
|
||||
border-bottom: 1px solid #F1F2F3;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px 0!important;
|
||||
font-size: 28px;
|
||||
|
||||
::v-deep textarea {
|
||||
color: #333;
|
||||
font-size: 28px!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-pic {
|
||||
position: relative;
|
||||
font-size: 0;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
width: 148px;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .u-radio, ::v-deep .u-checkbox {
|
||||
align-items: baseline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
src/pages/askForm/askForm.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<section class="askForm">
|
||||
<template v-if="showDetail&&!isManager">
|
||||
<form-detail/>
|
||||
</template>
|
||||
<template v-else-if="isManager">
|
||||
<form-list ref="FormList"/>
|
||||
</template>
|
||||
<ai-loading v-else :tips="errMsg"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiLoading from "../../components/AiLoading";
|
||||
import {mapState} from "vuex";
|
||||
import FormDetail from "./formDetail";
|
||||
import FormList from "./formList";
|
||||
|
||||
export default {
|
||||
name: "askForm",
|
||||
components: {FormList, FormDetail, AiLoading},
|
||||
computed: {
|
||||
...mapState(['openUser', 'user']),
|
||||
showDetail() {
|
||||
return !!this.$route.query?.id
|
||||
},
|
||||
isManager() {
|
||||
let {hash, query: {preview}} = this.$route
|
||||
if (preview) return false
|
||||
else if (hash == "#dev") return true
|
||||
else return hash != '#form' && !!this.user.id
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errMsg: "加载表单中..."
|
||||
}
|
||||
},
|
||||
onReachBottom() {
|
||||
this.$refs?.FormList?.reachBottom()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.askForm {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
182
src/pages/askForm/components/addList.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div class="form">
|
||||
<div class="form-list">
|
||||
<div
|
||||
class="form-list__item"
|
||||
@click="toAdd(`/pages/askForm/addForm?type=${index}`)"
|
||||
:style="{'background-image': `url(${$cdn}askform/${index + 1}.png)`}"
|
||||
v-for="(item, index) in itemList"
|
||||
:key="index">
|
||||
<h2>{{ item.name }}</h2>
|
||||
<div>立即创建</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="template" v-if="list.length">
|
||||
<h2>共享模板</h2>
|
||||
<div class="template-list">
|
||||
<div class="template-item" v-for="(item, index) in list" :key="index" hover-class="bg-hover" @click="quote(item.id)">
|
||||
<image :src="`${$cdn}askform/6.png`" />
|
||||
<h2>{{ item.title }}</h2>
|
||||
<u-icon name="arrow-right" color="#E1E2E3" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'addList',
|
||||
label: '新建项目',
|
||||
|
||||
data () {
|
||||
return {
|
||||
itemList: [{
|
||||
name: '调查问卷'
|
||||
}, {
|
||||
name: '考试测评'
|
||||
}, {
|
||||
name: '报名登记'
|
||||
}, {
|
||||
name: '满意调查'
|
||||
}, {
|
||||
name: '投票评选'
|
||||
}],
|
||||
list: []
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.getList()
|
||||
},
|
||||
|
||||
methods: {
|
||||
toAdd (url) {
|
||||
uni.navigateTo({
|
||||
url
|
||||
})
|
||||
},
|
||||
|
||||
quote (id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/askForm/addForm?isQuote=1&id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
getList () {
|
||||
this.$http.post(`/app/appquestionnairetemplate/list`, null, {
|
||||
params: {
|
||||
current: 1,
|
||||
templateType: 1,
|
||||
size: 10000
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.list = res.data.records
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.template {
|
||||
margin: 32px 32px 0;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
& > h2 {
|
||||
height: 88px;
|
||||
line-height: 88px;
|
||||
padding: 0 24px;
|
||||
color: #333333;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 104px;
|
||||
padding: 0 24px;
|
||||
border-bottom: 1px solid #D8DDE6;
|
||||
|
||||
&:active {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 36px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 30px;
|
||||
color: #E1E2E3;
|
||||
}
|
||||
|
||||
h2 {
|
||||
flex: 1;
|
||||
padding: 0 18px;
|
||||
color: #333333;
|
||||
font-size: 28px;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 32px 0;
|
||||
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-list__item {
|
||||
width: calc(50% - 13px);
|
||||
height: 216px;
|
||||
margin: 32px 24px 0 0;
|
||||
padding: 40px 20px 52px;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
background-size: 100% 100%;
|
||||
|
||||
&:active {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&:nth-of-type(2n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
width: 148px;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 28px;
|
||||
background: #6BA1F9;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 32px;
|
||||
padding-left: 10px;
|
||||
color: #333333;
|
||||
font-weight: 700;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
497
src/pages/askForm/components/list.vue
Normal file
@@ -0,0 +1,497 @@
|
||||
<template>
|
||||
<div class="form">
|
||||
<ai-top-fixed>
|
||||
<u-search placeholder="请输入标题" :show-action="false" search-icon-color="#ccc" v-model="search.title"
|
||||
@search="isMore = false, search.current = 1, getList()"/>
|
||||
</ai-top-fixed>
|
||||
<div class="form-list">
|
||||
<div class="form-item" v-for="(item, index) in list" :key="index"
|
||||
@click="info = item, id = item.id, isShow = true">
|
||||
<div class="form-item__top">
|
||||
<div class="form-item__left">
|
||||
<h2>{{ item.title }}</h2>
|
||||
<div class="form-item__left--info">
|
||||
<span>{{ item.createUserName }}</span>
|
||||
<span>{{ item.createUnitName }}</span>
|
||||
<span>{{ item.createTime.substr(0, item.createTime.length - 3) }}</span>
|
||||
<span>{{ $dict.getLabel('questionnaireType', item.type) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item__right">
|
||||
<h2>{{ item.dataCount }}</h2>
|
||||
<span>答卷数量</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item__bottom form-item__bottom--active">
|
||||
<i :style="{background: $dict.getColor('questionnaireStatus', item.status)}"></i>
|
||||
<span>{{ $dict.getLabel('questionnaireStatus', item.status) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<ai-empty v-if="!list.length && isMore"></ai-empty>
|
||||
</div>
|
||||
<u-popup v-model="isShow" :closeable="false" mode="bottom" :z-index="11">
|
||||
<div class="popup">
|
||||
<h2>{{ info.title }}</h2>
|
||||
<div class="operate-list">
|
||||
<div class="operate-item" @click="toEdit">
|
||||
<div>
|
||||
<image :src="`${$cdn}askform/bj.png`"/>
|
||||
</div>
|
||||
<h3>编辑</h3>
|
||||
</div>
|
||||
<div class="operate-item" @click="linkTo('/pages/askForm/askForm?preview=1&id=' + id)">
|
||||
<div>
|
||||
<image :src="`${$cdn}askform/yl.png`"/>
|
||||
</div>
|
||||
<h3>预览</h3>
|
||||
</div>
|
||||
<div class="operate-item" @click="publish" v-if="info.status !== '1'">
|
||||
<div>
|
||||
<image :src="`${$cdn}askform/fb.png`"/>
|
||||
</div>
|
||||
<h3>发布</h3>
|
||||
</div>
|
||||
<div class="operate-item" @click="toStop" v-if="info.status === '1'">
|
||||
<div>
|
||||
<image :src="`${$cdn}askform/stop.png`"/>
|
||||
</div>
|
||||
<h3>停止</h3>
|
||||
</div>
|
||||
<div class="operate-item" @click="showShare">
|
||||
<div>
|
||||
<image :src="`${$cdn}askform/fx.png`"/>
|
||||
</div>
|
||||
<h3>分享</h3>
|
||||
</div>
|
||||
<div class="operate-item" @click="share(id)">
|
||||
<div>
|
||||
<image :src="`${$cdn}askform/mb.png`"/>
|
||||
</div>
|
||||
<h3>共享为模板</h3>
|
||||
</div>
|
||||
<div class="operate-item" @click="remove(id)">
|
||||
<div>
|
||||
<image :src="`${$cdn}askform/sc.png`"/>
|
||||
</div>
|
||||
<h3>删除</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popup-btn" @click="isShow = false">关闭</div>
|
||||
</div>
|
||||
</u-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiTopFixed from '@/components/AiTopFixed'
|
||||
import AiEmpty from '@/components/AiEmpty/AiEmpty'
|
||||
import {mapActions} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'formList',
|
||||
label: '表单列表',
|
||||
|
||||
data() {
|
||||
return {
|
||||
search: {
|
||||
current: 1,
|
||||
templateType: 0,
|
||||
title: ''
|
||||
},
|
||||
id: '',
|
||||
info: {},
|
||||
isMore: false,
|
||||
list: [],
|
||||
isShow: false
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AiEmpty,
|
||||
AiTopFixed
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.injectJWeixin(['sendChatMessage', 'selectEnterpriseContact'])
|
||||
this.$dict.load(['questionnaireStatus', 'questionnaireType', 'questionnaireFieldType']).then(() => {
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(['injectJWeixin', 'wxInvoke']),
|
||||
|
||||
linkTo(url) {
|
||||
this.isShow = false
|
||||
|
||||
uni.navigateTo({
|
||||
url
|
||||
})
|
||||
},
|
||||
|
||||
toStop() {
|
||||
this.$http.post(`/app/appquestionnairetemplate/stopRelease?id=${this.info.id}`).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$u.toast('停止成功')
|
||||
this.search.current = 1
|
||||
this.isShow = false
|
||||
this.isMore = false
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
showShare() {
|
||||
if (this.info.status !== '1') {
|
||||
this.isShow = false
|
||||
return this.$u.toast(`该表单${this.info.status === '0' ? '未发布' : '已截止'},无法分享!`)
|
||||
}
|
||||
|
||||
uni.showActionSheet({
|
||||
itemList: ['分享', '微信分享', '获取链接'],
|
||||
success: data => {
|
||||
this.$http.post(`/app/appquestionnairetemplate/queryQrCode?id=${this.info.id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (data.tapIndex === 2) {
|
||||
this.copy(res.data.linkUrl)
|
||||
this.isShow = false
|
||||
}
|
||||
if (data.tapIndex === 0 || data.tapIndex === 1) {
|
||||
this.injectJWeixin(['shareAppMessage', 'shareWechatMessage']).then(() => {
|
||||
if (data.tapIndex === 0) {
|
||||
this.wxInvoke(['shareAppMessage', {
|
||||
title: this.info.title,
|
||||
desc: this.info.tableExplain,
|
||||
link: res.data.linkUrl,
|
||||
imgUrl: this.info.headPicture
|
||||
}, () => {
|
||||
this.isShow = false
|
||||
}])
|
||||
} else {
|
||||
this.wxInvoke(['shareWechatMessage', {
|
||||
title: this.info.title,
|
||||
desc: this.info.tableExplain,
|
||||
link: res.data.linkUrl,
|
||||
imgUrl: this.info.headPicture
|
||||
}, () => {
|
||||
this.isShow = false
|
||||
}])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
copy(link) {
|
||||
let oInput = document.createElement('input')
|
||||
oInput.value = link
|
||||
document.body.appendChild(oInput)
|
||||
oInput.select()
|
||||
document.execCommand('Copy')
|
||||
this.$u.toast('已复制')
|
||||
oInput.remove()
|
||||
},
|
||||
|
||||
publish() {
|
||||
if (this.info.status === '1') {
|
||||
return this.$u.toast('该表单已发布')
|
||||
}
|
||||
|
||||
this.linkTo(`/pages/askForm/formSetting?id=${this.info.id}&type=edit`)
|
||||
this.isShow = false
|
||||
},
|
||||
|
||||
toEdit() {
|
||||
if (this.info.dataCount !== 0) {
|
||||
return this.$u.toast('该表单已有数据,无法编辑!')
|
||||
}
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/askForm/addForm?id=${this.id}`
|
||||
})
|
||||
|
||||
this.isShow = false
|
||||
},
|
||||
|
||||
share(id) {
|
||||
this.$http.post(`/app/appquestionnairetemplate/share?id=${id}`).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.$confirm('调查表单共享成功,其他成员可在新建项目时直接使用!', '', {
|
||||
showCancel: false
|
||||
})
|
||||
this.isShow = false
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
remove(id) {
|
||||
if (this.info.dataCount !== 0) {
|
||||
return this.$u.toast('该表单已有数据,无法删除!')
|
||||
}
|
||||
|
||||
this.$confirm('确定删除该数据?').then(() => {
|
||||
this.$http.post(`/app/appquestionnairetemplate/delete?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$u.toast('删除成功')
|
||||
this.isShow = false
|
||||
this.search.current = 1
|
||||
this.isMore = false
|
||||
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
|
||||
reload() {
|
||||
this.isMore = false
|
||||
this.search.current = 1
|
||||
|
||||
this.getList()
|
||||
},
|
||||
|
||||
getList() {
|
||||
if (this.isMore) return
|
||||
|
||||
this.$http.post(`/app/appquestionnairetemplate/list`, null, {
|
||||
params: {
|
||||
...this.search,
|
||||
size: 10
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (this.search.current > 1) {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
} else {
|
||||
this.list = res.data.records
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
|
||||
if (res.data.records.length < 10) {
|
||||
this.isMore = true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
this.search.current = this.search.current + 1
|
||||
} else {
|
||||
uni.hideLoading()
|
||||
}
|
||||
}).catch(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form {
|
||||
::v-deep .u-search {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.popup {
|
||||
background: #F7F7F7;
|
||||
|
||||
& > h2 {
|
||||
height: 72px;
|
||||
line-height: 72px;
|
||||
padding: 0 20px;
|
||||
color: #999999;
|
||||
font-size: 22px;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #D7D8DA;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.popup-btn {
|
||||
height: 96px;
|
||||
line-height: 96px;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
font-size: 30px;
|
||||
background: #fff;
|
||||
|
||||
&:active {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.operate-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
text-align: center;
|
||||
padding-bottom: 26px;
|
||||
|
||||
.operate-item {
|
||||
width: 25%;
|
||||
font-size: 0;
|
||||
margin-top: 28px;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 20px;
|
||||
color: #666666;
|
||||
font-size: 26px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin: 24px 25px 0;
|
||||
padding: 32px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 16px;
|
||||
|
||||
.form-item__bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 28px;
|
||||
color: #999999;
|
||||
|
||||
i {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 6px;
|
||||
border-radius: 50%;
|
||||
background: #999999;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
&.form-item__bottom--active i {
|
||||
background: #3CB300;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item__top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.form-item__right {
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
line-height: 40px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1EA0FA;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #999999;
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-item__left {
|
||||
flex: 1;
|
||||
max-width: 80%;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: -15px;
|
||||
top: 50%;
|
||||
width: 2px;
|
||||
height: 96px;
|
||||
background: #F5F5F5;
|
||||
content: '';
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
h2 {
|
||||
line-height: 44px;
|
||||
margin-bottom: 16px;
|
||||
color: #333;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.form-item__left--info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
color: #999;
|
||||
font-size: 20px;
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
margin-right: 24px;
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: -12px;
|
||||
top: 50%;
|
||||
width: 2px;
|
||||
height: 20px;
|
||||
background: #D1D2D5;
|
||||
content: '';
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.type-0 {
|
||||
background: #2266FF !important;
|
||||
}
|
||||
|
||||
.type-1 {
|
||||
background: rgba(34, 170, 153, 1) !important;
|
||||
}
|
||||
|
||||
.type-2 {
|
||||
background: rgba(248, 180, 37, 1) !important;
|
||||
}
|
||||
|
||||
.type-3 {
|
||||
background: rgba(102, 119, 187, 1) !important;
|
||||
}
|
||||
|
||||
.type-4 {
|
||||
background: rgba(236, 68, 97, 1) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
163
src/pages/askForm/config.js
Normal file
@@ -0,0 +1,163 @@
|
||||
export const components = [
|
||||
{
|
||||
type: 'radio',
|
||||
label: '单选',
|
||||
fixedLabel: '单选',
|
||||
value: '',
|
||||
points: '',
|
||||
icon: 'iconradio',
|
||||
isShowPoints: false,
|
||||
required: true,
|
||||
hasAnswer: false,
|
||||
answer: '',
|
||||
pointType: '0',
|
||||
pointDict: [
|
||||
{
|
||||
dictName: '此题有唯一答案和分值',
|
||||
dictValue: '0'
|
||||
},
|
||||
{
|
||||
dictName: '每个选项都有对应分值',
|
||||
dictValue: '1'
|
||||
}
|
||||
],
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '',
|
||||
point: '',
|
||||
img: []
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '',
|
||||
point: '',
|
||||
img: []
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: '多选',
|
||||
fixedLabel: '多选',
|
||||
points: '',
|
||||
icon: 'iconcheck_box',
|
||||
isShowPoints: false,
|
||||
required: true,
|
||||
hasAnswer: false,
|
||||
answer: [],
|
||||
value: [],
|
||||
pointType: '0',
|
||||
pointDict: [
|
||||
{
|
||||
dictName: '此题有唯一答案和分值',
|
||||
dictValue: '0'
|
||||
},
|
||||
{
|
||||
dictName: '每个选项都有对应分值',
|
||||
dictValue: '1'
|
||||
},
|
||||
{
|
||||
dictName: '答对几项得几分,答错不得分',
|
||||
dictValue: '2'
|
||||
}
|
||||
],
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '',
|
||||
point: '',
|
||||
img: [],
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
point: '',
|
||||
value: '',
|
||||
img: [],
|
||||
checked: false
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
label: '单下拉框',
|
||||
fixedLabel: '单下拉框',
|
||||
value: '',
|
||||
points: '',
|
||||
icon: 'iconSelect',
|
||||
isShowPoints: false,
|
||||
required: true,
|
||||
hasAnswer: false,
|
||||
answer: '',
|
||||
pointType: '0',
|
||||
placeholder: '请选择',
|
||||
pointDict: [
|
||||
{
|
||||
dictName: '此题有唯一答案和分值',
|
||||
dictValue: '0'
|
||||
},
|
||||
{
|
||||
dictName: '每个选项都有对应分值',
|
||||
dictValue: '1'
|
||||
}
|
||||
],
|
||||
options: [
|
||||
{
|
||||
label: '选项1',
|
||||
value: '',
|
||||
point: '',
|
||||
img: []
|
||||
},
|
||||
{
|
||||
label: '选项2',
|
||||
value: '',
|
||||
point: '',
|
||||
img: []
|
||||
}
|
||||
],
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
label: '单行填空',
|
||||
fixedLabel: '单行填空',
|
||||
value: '',
|
||||
pointType: '0',
|
||||
icon: 'icontext_box',
|
||||
isShowPoints: false,
|
||||
points: '',
|
||||
required: true,
|
||||
hasAnswer: false,
|
||||
placeholder: '请输入...',
|
||||
answer: ''
|
||||
},
|
||||
{
|
||||
type: 'textarea',
|
||||
label: '多行填空',
|
||||
fixedLabel: '多行填空',
|
||||
pointType: '0',
|
||||
icon: 'icontext_area',
|
||||
points: '',
|
||||
isShowPoints: false,
|
||||
required: true,
|
||||
hasAnswer: false,
|
||||
answer: '',
|
||||
placeholder: '请输入...',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
type: 'upload',
|
||||
label: '上传图片',
|
||||
fixedLabel: '上传图片',
|
||||
value: '',
|
||||
icon: 'iconpic',
|
||||
isShowPoints: false,
|
||||
points: '',
|
||||
required: true,
|
||||
hasAnswer: false,
|
||||
answer: ''
|
||||
}
|
||||
];
|
||||
535
src/pages/askForm/filedConfig.vue
Normal file
@@ -0,0 +1,535 @@
|
||||
<template>
|
||||
<div class="form-config">
|
||||
<div class="config-group">
|
||||
<div class="config-item">
|
||||
<u-input class="form-maintitle" :maxlength="200" v-model="config.label" :placeholder="`请输入${config.fixedLabel}标题 ${config.required ? '(必填)' : ''}`" placeholder-style="color: #999999; font-weight: 600" />
|
||||
</div>
|
||||
<div class="config-item__select--wrapper" v-if="['radio', 'select', 'checkbox'].includes(config.type)">
|
||||
<div class="config-item__select" v-for="(item, index) in config.options" :key="index">
|
||||
<image class="config-icon" :src="`${$cdn}askform/del.png`" @click="removeOptions(index)" />
|
||||
<div class="config-item__upload" v-if="config.type !== 'select'" @click="upload(index)">
|
||||
<u-icon color="#8c9dc3" name="plus" v-if="!item.img.length"></u-icon>
|
||||
<image v-else :src="item.img[0].url" />
|
||||
</div>
|
||||
<div class="textarea">
|
||||
<textarea type="textarea" placeholder-style="color: #CDCDCF" :auto-height="true" v-model="item.label" :maxlength="100" placeholder="请输入选项" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item__select config-item__select--add" @click="addOptions">
|
||||
<image class="config-icon" :src="`${$cdn}askform/zj.png`" />
|
||||
<span>添加选项</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-group">
|
||||
<div class="config-item" v-if="!['radio', 'upload', 'checkbox', 'select'].includes(config.type)">
|
||||
<div class="config-item__left">
|
||||
<span>说明文字</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-input v-model="config.placeholder" placeholder="请输入说明文字" input-align="right" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item">
|
||||
<div class="config-item__left">
|
||||
<span>是否必填</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-switch v-model="config.required" active-value="1" inactive-value="0" :size="40" active-color="#1088F9"></u-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item" v-if="!['upload'].includes(config.type)">
|
||||
<div class="config-item__left">
|
||||
<span>答案与分值</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-switch v-model="config.isShowPoints" :size="40" active-color="#1088F9"></u-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item" v-if="['input', 'textarea'].includes(config.type) && config.isShowPoints">
|
||||
<div class="config-item__left">
|
||||
<span>正确答案</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-input v-model="config.answer" placeholder="请输入正确答案" input-align="right" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item" v-if="['radio', 'select'].includes(config.type) && config.isShowPoints && config.pointType === '0'">
|
||||
<div class="config-item__left">
|
||||
<span>正确答案</span>
|
||||
</div>
|
||||
<div class="config-item__right config-item__text" @click="isShowAnswer = true">
|
||||
<span>{{ config.answer ? config.answer : '请选择正确答案' }}</span>
|
||||
<u-icon name="arrow-down-fill" color="#c0c4cc" size="24"></u-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item config-item__checkbox" v-if="config.isShowPoints && ['radio', 'select', 'checkbox'].includes(config.type)">
|
||||
<div class="config-item__left">
|
||||
<span>计分方式</span>
|
||||
</div>
|
||||
<div class="config-item__right" @click="isShowType = true">
|
||||
<span>{{ pointTypeName ? pointTypeName : '请选择' }}</span>
|
||||
<u-icon name="arrow-right" color="#E1E2E3" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item config-item__answer config-item__checkbox" v-if="['checkbox'].includes(config.type) && config.isShowPoints && config.pointType === '0'">
|
||||
<div class="config-item__left">
|
||||
<span>正确答案</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-checkbox-group wrap @change="onCheckboxChange">
|
||||
<u-checkbox v-model="field.checked" :name="field.label" v-if="field.label" v-for="(field, i) in config.options" :key="i">
|
||||
<span>{{ field.label }}</span>
|
||||
</u-checkbox>
|
||||
</u-checkbox-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item" v-if="config.isShowPoints && config.pointType === '0'">
|
||||
<div class="config-item__left">
|
||||
<span>本题分值</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-input v-model="config.points" type="number" placeholder="请输入本题分值" input-align="right" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="config.isShowPoints && config.pointType === '1'">
|
||||
<div class="config-item" v-for="(item, index) in config.options" :key="index">
|
||||
<div class="config-item__left">
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-input v-model="item.point" placeholder="请输入分值" input-align="right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-item config-item__point" v-if="config.isShowPoints && config.pointType === '2'">
|
||||
<u-checkbox-group wrap @change="onCheckboxChange">
|
||||
<u-checkbox v-model="field.checked" :name="field.label" v-if="field.label" v-for="(field, i) in config.options" :key="i">
|
||||
<span>{{ field.label }}</span>
|
||||
<u-input v-model="field.point" type="number" placeholder="请输入分值" input-align="right" />
|
||||
</u-checkbox>
|
||||
</u-checkbox-group>
|
||||
</div>
|
||||
<div class="config-item" v-if="config.isShowPoints && config.pointType === '2'">
|
||||
<div class="config-item__left" style="padding-left: 20px">
|
||||
<span>全部答对</span>
|
||||
</div>
|
||||
<div class="config-item__right">
|
||||
<u-input v-model="config.points" type="number" placeholder="请输入全部答对分值" input-align="right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<u-select :list="config.options" :default-value="defaultAnswer" value-name="value" label-name="label" v-model="isShowAnswer" @confirm="answerChange"></u-select>
|
||||
<u-select :list="config.pointDict" :default-value="defaultType" value-name="dictValue" label-name="dictName" v-model="isShowType" @confirm="pointTypeChange"></u-select>
|
||||
<div class="add-form__footer">
|
||||
<div @click="back">
|
||||
<span>取消</span>
|
||||
</div>
|
||||
<div @click="confirm">确定</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { components } from './config'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
index: '',
|
||||
isShowType: false,
|
||||
isShowAnswer: false,
|
||||
config: {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onLoad (query) {
|
||||
if (query.type) {
|
||||
this.config = JSON.parse(JSON.stringify(components.filter(v => v.type === query.type)[0]))
|
||||
} else {
|
||||
this.config = JSON.parse(query.config)
|
||||
this.index = query.index
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
pointTypeName () {
|
||||
if (!this.config.pointDict) return ''
|
||||
|
||||
return this.config.pointDict.filter(v => v.dictValue === this.config.pointType)[0].dictName
|
||||
},
|
||||
|
||||
defaultType () {
|
||||
if (!this.config.pointType) return [0]
|
||||
|
||||
return [Number(this.config.pointType)]
|
||||
},
|
||||
|
||||
defaultAnswer () {
|
||||
if (!this.config.answer) return [0]
|
||||
|
||||
let index = 0
|
||||
if (this.config.answer) {
|
||||
this.config.options.forEach((v, i) => {
|
||||
if (v.label === this.config.answer) {
|
||||
index = i
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return [index]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
answerChange (e) {
|
||||
this.config.answer = e[0].label
|
||||
},
|
||||
|
||||
pointTypeChange (e) {
|
||||
console.log(e)
|
||||
this.config.pointType = e[0].value
|
||||
},
|
||||
|
||||
onCheckboxChange (e) {
|
||||
this.config.answer = e
|
||||
},
|
||||
|
||||
upload (index) {
|
||||
let params = {
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
let count = this.fileList?.length + (res.tempFiles?.length || res.tempFile ? 1 : 0)
|
||||
if (count > 1) {
|
||||
return this.$u.toast(`不能超过1个`)
|
||||
}
|
||||
if (res.tempFiles) {
|
||||
res.tempFiles.map((item) => {
|
||||
this.uploadFile(item, index)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
uni.chooseImage(params)
|
||||
},
|
||||
|
||||
uploadFile (img, index) {
|
||||
uni.showLoading({title: '上传中'})
|
||||
let formData = new FormData()
|
||||
formData.append('file', img)
|
||||
this.$http.post('/admin/file/add2', formData).then((res) => {
|
||||
uni.hideLoading()
|
||||
if (res?.data) {
|
||||
this.$u.toast('上传成功!')
|
||||
this.$set(this.config.options[index], 'img', [res.data])
|
||||
}
|
||||
}).catch(res => {
|
||||
this.$u.toast(res)
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
removeOptions (index) {
|
||||
const len = this.config.options.length
|
||||
const label = this.config.options[index].label
|
||||
if (len === 2) {
|
||||
return this.$u.toast('选项不能少于2个')
|
||||
}
|
||||
|
||||
if (this.config.type === 'checkbox') {
|
||||
const answerIndex = this.config.answer.indexOf(label)
|
||||
if (answerIndex > -1) {
|
||||
this.config.answer.splice(answerIndex, 1)
|
||||
}
|
||||
} else {
|
||||
if (label === this.config.answer) {
|
||||
this.config.answer = ''
|
||||
}
|
||||
}
|
||||
|
||||
this.config.options.splice(index, 1)
|
||||
},
|
||||
|
||||
back () {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
},
|
||||
|
||||
confirm () {
|
||||
uni.$emit('filedConfig', {
|
||||
config: this.config,
|
||||
index: this.index === '' ? '-1' : this.index
|
||||
})
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
},
|
||||
|
||||
addOptions () {
|
||||
const len = this.config.options.length
|
||||
let label = `选项${len + 1}`
|
||||
|
||||
const index= this.config.options.findIndex(v => label === v.label)
|
||||
if (index > -1) {
|
||||
label = `新选项${len + 1}`
|
||||
}
|
||||
|
||||
this.config.options.push({
|
||||
label: label,
|
||||
value: '',
|
||||
point: '',
|
||||
img: '',
|
||||
checked: false
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-config {
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 130px;
|
||||
|
||||
.form-maintitle {
|
||||
::v-deep .uni-input-input {
|
||||
font-size: 36px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.config-item__select--wrapper {
|
||||
.config-item__select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
::v-deep .u-input__input {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 104px;
|
||||
padding: 16px 0;
|
||||
font-size: 28px;
|
||||
border-bottom: 1px solid #dfe8f8;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.config-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-item__select--add {
|
||||
height: 120px;
|
||||
|
||||
.config-icon {
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #1D74F4;
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-group {
|
||||
margin-bottom: 32px;
|
||||
padding: 0 32px;
|
||||
background: #fff;
|
||||
|
||||
.config-item__upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 20px;
|
||||
border: 1px solid rgb(208, 212, 220);
|
||||
background-color: #fbfdff;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.config-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 100px;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #dfe8f8;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
::v-deep .u-radio__label, ::v-deep .u-checkbox__label {
|
||||
margin-right: 0;
|
||||
font-size: 28px;
|
||||
|
||||
span {
|
||||
max-width: 400rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
.config-item__left {
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.config-item__right {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
padding-left: 30px;
|
||||
|
||||
&.config-item__text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
span {
|
||||
max-width: 400px;
|
||||
margin-right: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-break: keep-all;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
}
|
||||
}
|
||||
|
||||
&.config-item__answer {
|
||||
display: block;
|
||||
padding: 20px 0;
|
||||
|
||||
.config-item__left {
|
||||
margin-bottom: 32rpx;
|
||||
span {
|
||||
word-break: break-all;
|
||||
color: #333;
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-item__right {
|
||||
width: 100%;
|
||||
padding-left: 0;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .u-checkbox {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.config-item__checkbox {
|
||||
height: auto;
|
||||
padding: 14px 0;
|
||||
|
||||
::v-deep .u-checkbox, ::v-deep .u-radio {
|
||||
// justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
.config-item__point {
|
||||
height: auto;
|
||||
padding: 0;
|
||||
|
||||
::v-deep .u-checkbox {
|
||||
justify-content: inherit;
|
||||
min-height: 100px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.u-checkbox__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
margin-right: 0;
|
||||
|
||||
.u-input {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.add-form__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 112px;
|
||||
text-align: center;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
|
||||
&:first-child:active {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
background: #3192F4;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
line-height: 112px;
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
|
||||
&:active {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
405
src/pages/askForm/formDetail.vue
Normal file
@@ -0,0 +1,405 @@
|
||||
<template>
|
||||
<section class="formDetail">
|
||||
<ai-result v-if="result.tips" v-bind="result">
|
||||
<template v-if="isExam" #extra>
|
||||
<div flex class="scorePane">
|
||||
<div>成绩</div>
|
||||
<div class="fill"><em v-html="score"/> 分</div>
|
||||
</div>
|
||||
</template>
|
||||
</ai-result>
|
||||
<template v-else-if="form.id">
|
||||
<image v-if="form.headPicture" class="headPicture" :src="form.headPicture"/>
|
||||
<b class="title">{{ form.title || "标题" }}</b>
|
||||
<div class="tableExplain">{{ form.tableExplain }}</div>
|
||||
<u-form class="content" label-position="top">
|
||||
<u-form-item class="item" v-for="(op,i) in fields" :key="i" :label="(i+1)+'.'+op.fieldName"
|
||||
:required="op.fieldInfo.required==1">
|
||||
<template v-if="op.fieldType=='input'">
|
||||
<input v-model="op.fieldValue" :placeholder="op.fieldInfo.placeholder" :disabled="isResult"/>
|
||||
</template>
|
||||
<template v-else-if="op.fieldType=='textarea'">
|
||||
<textarea v-model="op.fieldValue" :disabled="isResult" :placeholder="op.fieldInfo.placeholder"/>
|
||||
</template>
|
||||
<template v-else-if="op.fieldType=='upload'">
|
||||
<ai-uploader @list="v=>op.fieldValue=v.map(e=>e.url)" :def="op.fieldValue" :disabled="isResult"
|
||||
preview/>
|
||||
</template>
|
||||
<u-row v-else-if="op.fieldType=='radio'">
|
||||
<radio-group @change="({detail})=>op.fieldValue=detail.value">
|
||||
<div class="option" flex v-for="option in op.fieldInfo.options" :key="option.label">
|
||||
<radio :value="option.label" :disabled="isResult" :checked="op.fieldValue==option.label"/>
|
||||
<ai-image v-if="option.img" :src="option.img" preview/>
|
||||
<div class="label fill">{{ option.label }}</div>
|
||||
</div>
|
||||
</radio-group>
|
||||
</u-row>
|
||||
<u-row v-else-if="op.fieldType=='checkbox'">
|
||||
<checkbox-group @change="({detail})=>op.fieldValue=detail.value">
|
||||
<div class="option" flex v-for="option in op.fieldInfo.options" :key="option.label">
|
||||
<checkbox :value="option.label" :disabled="isResult"
|
||||
:checked="option.checked"/>
|
||||
<ai-image v-if="option.img" :src="option.img" preview/>
|
||||
<div class="label fill">{{ option.label }}</div>
|
||||
</div>
|
||||
</checkbox-group>
|
||||
</u-row>
|
||||
<template v-else-if="op.fieldType=='select'">
|
||||
<ai-select @data="v=>op.fieldValue=v.map(e=>e.value)" :list="op.fieldInfo.options" :disabled="isResult">
|
||||
<div class="option" flex>
|
||||
<div class="label fill" v-if="op.fieldValue">{{ op.fieldValue.toString() }}</div>
|
||||
<i class="fill" v-else>请选择</i>
|
||||
<u-icon name="arrow-right" color="#ddd"/>
|
||||
</div>
|
||||
</ai-select>
|
||||
</template>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<div class="bottom" v-if="!(isPreview||isResult)">
|
||||
<div class="bottomBtn" @tap="handleSubmit">提交</div>
|
||||
</div>
|
||||
</template>
|
||||
<ai-loading v-else tips="调查问卷加载中..."/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import UForm from "../../uview/components/u-form/u-form";
|
||||
import UFormItem from "../../uview/components/u-form-item/u-form-item";
|
||||
import {mapActions, mapState} from "vuex";
|
||||
import UInput from "../../uview/components/u-input/u-input";
|
||||
import AiTextarea from "../../components/AiTextarea";
|
||||
import AiUploader from "../../components/AiUploader";
|
||||
import AiSelect from "../../components/AiSelect";
|
||||
import URadio from "../../uview/components/u-radio/u-radio";
|
||||
import AiLoading from "../../components/AiLoading";
|
||||
import AiResult from "../../components/AiResult";
|
||||
import AiImage from "../../components/AiImage";
|
||||
import AiBack from "../../components/AiBack";
|
||||
|
||||
export default {
|
||||
name: "formDetail",
|
||||
components: {
|
||||
AiBack,
|
||||
AiImage,
|
||||
AiResult,
|
||||
AiLoading,
|
||||
URadio,
|
||||
AiSelect,
|
||||
AiUploader,
|
||||
AiTextarea,
|
||||
UInput,
|
||||
UFormItem,
|
||||
UForm
|
||||
},
|
||||
computed: {
|
||||
...mapState(['openUser', 'token']),
|
||||
isExam() {
|
||||
return this.form?.type == 1
|
||||
},
|
||||
isPreview() {
|
||||
return !!this.$route.query?.preview
|
||||
},
|
||||
isResult() {
|
||||
return !!this.$route.query?.result
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
fields: [],
|
||||
checkUser: false,
|
||||
result: {},
|
||||
score: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler(v) {
|
||||
this.fields = v?.fields?.map(e => {
|
||||
let fieldInfo = JSON.parse(e.fieldInfo)
|
||||
fieldInfo?.options?.map(op => {
|
||||
op.img = op?.img?.[0]?.url
|
||||
op.checked = !!e.fieldValue?.split(",")?.includes(op.label)
|
||||
})
|
||||
if (e.fieldType == 'select') {
|
||||
fieldInfo.options = fieldInfo.options.map(e => ({...e, value: e.label, label: e.label}))
|
||||
}
|
||||
return {...e, fieldInfo}
|
||||
}) || []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getUserInfo']),
|
||||
getForm() {
|
||||
let {id} = this.$route.query
|
||||
this.$http.post("/app/appquestionnairetemplate/queryDetailById", null, {
|
||||
params: {id}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.form = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
validateForm() {
|
||||
return !this.fields.some(e => {
|
||||
if (!!e?.fieldInfo?.required && !e.fieldValue?.toString()) {
|
||||
this.$u.toast(e.fieldName + "不能为空!")
|
||||
return true
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSubmit() {
|
||||
if (this.validateForm()) {
|
||||
this.handleScore()
|
||||
let {avatar: avatarUrl, openId, name: nickName, type: userType, unionId, corpName} = this.openUser
|
||||
this.$http.post("/app/appquestionnairetemplate/commit", {
|
||||
fields: this.fields.map(e => ({
|
||||
...e,
|
||||
fieldInfo: JSON.stringify(e.fieldInfo),
|
||||
fieldValue: e.fieldValue?.toString()
|
||||
})),
|
||||
avatarUrl, openId, nickName, userType, unionId, corpName,
|
||||
totalScore: this.score,
|
||||
questionnaireTemplateId: this.$route.query.id
|
||||
}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.result = {
|
||||
tips: "提交成功!感谢参与",
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
handleScore() {
|
||||
this.score = 0
|
||||
this.isExam && this.fields.map(field => {
|
||||
let item = field?.fieldInfo || {}
|
||||
let current = 0
|
||||
const calcScore = point => (current += (Number(point) || 0))
|
||||
if (item?.pointType == 0) {//此题有唯一答案和分值
|
||||
field.fieldValue?.toString() == item.answer?.toString() && calcScore(item?.points)
|
||||
} else if (item?.pointType == 1) {//每个选项都有对应分值
|
||||
item?.options?.map(op => {
|
||||
if (typeof field.fieldValue == "object") {
|
||||
if (field.fieldValue?.includes(op.label)) {
|
||||
calcScore(op.point)
|
||||
}
|
||||
} else {
|
||||
op.label == field.fieldValue && calcScore(op.point)
|
||||
}
|
||||
})
|
||||
} else if (item?.pointType == 2) {//答对几项得几分,答错不得分
|
||||
item?.options?.some(op => {
|
||||
if (typeof field.fieldValue == "object") {
|
||||
if (field.fieldValue?.includes(op.label)) {
|
||||
if (item.answer?.includes(op.label)) calcScore(op.point)
|
||||
else return current = 0
|
||||
}
|
||||
} else {
|
||||
op.label == field.fieldValue && calcScore(op.point)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.score += current
|
||||
//打印每题打分
|
||||
if (!!field.fieldValue) {
|
||||
const typeResult = (reply, answer) => {
|
||||
console.log("题目:%s,回答:%s,得分:%s,总分:%s \n 答案:%s", field.fieldName,
|
||||
reply, current, this.score, answer)
|
||||
}
|
||||
typeResult(field.fieldValue?.toString(), item.answer?.toString())
|
||||
}
|
||||
})
|
||||
},
|
||||
checkForm() {
|
||||
if (this.isPreview) {
|
||||
this.checkUser = true
|
||||
return Promise.resolve()
|
||||
}
|
||||
let {query: {id}, hash} = this.$route,
|
||||
{openId} = this.openUser
|
||||
if (hash != "#form") {
|
||||
this.result = {
|
||||
tips: "非法的调查问卷链接",
|
||||
status: "error"
|
||||
}
|
||||
} else if (openId) {
|
||||
return new Promise(resolve => {
|
||||
this.$http.post("/app/appquestionnairetemplate/commitCheck", null, {
|
||||
params: {id, openId}
|
||||
}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.checkUser = true
|
||||
if (this.isResult && res?.data) {
|
||||
this.form = res?.data
|
||||
} else resolve()
|
||||
} else this.result = {
|
||||
tips: "调查问卷加载失败",
|
||||
status: "error",
|
||||
btn: "重新加载",
|
||||
btnTap() {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
}).catch(err => {
|
||||
this.result = {
|
||||
tips: err || "调查问卷加载失败",
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.getUserInfo().then(() => {
|
||||
if (!!this.openUser?.openId) {
|
||||
this.checkForm()?.then(() => this.checkUser && this.getForm())
|
||||
} else {
|
||||
this.result = {
|
||||
tips: "您的信息获取失败",
|
||||
status: "error",
|
||||
btn: "重新加载",
|
||||
btnTap() {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.checkForm()?.then(() => this.checkUser && this.getForm())
|
||||
},
|
||||
mounted() {
|
||||
document.title = this.form.title || "调查问卷"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.formDetail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
|
||||
.headPicture {
|
||||
width: 100%;
|
||||
height: 320px;
|
||||
|
||||
.img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
font-size: 34px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
background: #fff;
|
||||
z-index: 99;
|
||||
|
||||
.bottomBtn {
|
||||
width: 100%;
|
||||
line-height: 96px;
|
||||
background: #287DE1;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
height: 96px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.tableExplain {
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
padding: 32px 24px;
|
||||
}
|
||||
|
||||
::v-deep .u-form-item {
|
||||
.u-form-item--left {
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.u-form-item--right__content__slot > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.display {
|
||||
justify-content: space-between;
|
||||
min-height: 58px;
|
||||
}
|
||||
|
||||
.uni-radio-input, .uni-checkbox-input {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex-shrink: 0;
|
||||
|
||||
* + & {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.option {
|
||||
width: 100%;
|
||||
margin-right: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 64px 32px 200px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
::v-deep .scorePane {
|
||||
width: calc(100% - 40px);
|
||||
padding: 0 32px;
|
||||
height: 124px;
|
||||
background: #E9F2FF;
|
||||
border-radius: 16px;
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
margin-top: 48px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.fill {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
em {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
color: #2C72FE;
|
||||
font-style: normal;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
164
src/pages/askForm/formList.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<section class="formList">
|
||||
<ai-top-fixed>
|
||||
<u-search placeholder="请输入标题" :show-action="false" search-icon-color="#ccc"
|
||||
v-model="search.title" @search="page.current=1,getList()"/>
|
||||
|
||||
</ai-top-fixed>
|
||||
<div class="mainPane">
|
||||
<div class="formBox column" flex v-for="op in list" :key="op.id">
|
||||
<div flex>
|
||||
<div class="fill column" flex>
|
||||
<b class="title">{{ op.title }}</b>
|
||||
<div class="info wrap" flex>
|
||||
<span v-html="op.createUserName"/>
|
||||
<span v-html="op.createUnitName"/>
|
||||
<span v-html="op.createTime"/>
|
||||
<span v-html="$dict.getLabel('questionnaireType',op.type)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split"/>
|
||||
<div flex class="column submitCount">
|
||||
<b>{{ op.dataCount }}</b>
|
||||
<div>答卷数量</div>
|
||||
</div>
|
||||
</div>
|
||||
<div flex class="bottom">
|
||||
<div class="dot" :style="{background:$dict.getColor('questionnaireStatus',op.status)}"/>
|
||||
<div>{{ $dict.getLabel("questionnaireStatus", op.status) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiTopFixed from "../../components/AiTopFixed";
|
||||
|
||||
export default {
|
||||
name: "formList",
|
||||
components: {AiTopFixed},
|
||||
data() {
|
||||
return {
|
||||
page: {current: 1, size: 10, total: 0},
|
||||
search: {title: ""},
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.$http.post("/app/appquestionnairetemplate/list", null, {
|
||||
params: {...this.page, ...this.search}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
if (this.page.current > 1) {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
} else this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
reachBottom() {
|
||||
if (this.page.total > this.list.length) {
|
||||
this.page.current++
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$dict.load("questionnaireStatus", 'questionnaireType')
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.formList {
|
||||
min-height: 100%;
|
||||
background: #F3F6F9;
|
||||
|
||||
::v-deep .mainPane {
|
||||
padding: 24px 24px 126px;
|
||||
|
||||
.formBox {
|
||||
width: 100%;
|
||||
min-height: 220px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 20px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
b {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 32px;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
border-left: 1px solid #D1D2D5;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:first-of-type {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin-top: 30px;
|
||||
font-size: 26px;
|
||||
|
||||
.dot {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
background: #3CB300;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.submitCount {
|
||||
width: 118px;
|
||||
|
||||
b {
|
||||
color: #1EA0FA;
|
||||
}
|
||||
|
||||
& > div {
|
||||
font-size: 22px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.split {
|
||||
width: 2px;
|
||||
background: #f5f5f5;
|
||||
margin: 0 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
314
src/pages/askForm/formSetting.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div class="form-setting">
|
||||
<h2>表单设置</h2>
|
||||
<div class="form-setting__list">
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__left">
|
||||
<span>截止时间</span>
|
||||
<image :src="`${$cdn}askform/bz.png`" @click="tips = '表单截止后,用户打开表单会提示此表单已结束' , isShowModal = true" />
|
||||
</div>
|
||||
<div class="setting-item__right">
|
||||
<u-radio-group v-model="periodValidityType" active-color="#1088F9">
|
||||
<u-radio name="0">永久有效</u-radio>
|
||||
<u-radio name="1">自定义时间</u-radio>
|
||||
</u-radio-group>
|
||||
<u-icon name="arrow-right" color="#E1E2E3" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item" v-if="periodValidityType === '1'" @click="isShowTime = true">
|
||||
<div class="setting-item__left">
|
||||
<span>截至时间</span>
|
||||
</div>
|
||||
<div class="setting-item__right">
|
||||
<span>{{ periodValidityEndTime }}</span>
|
||||
<u-icon name="arrow-right" color="#E1E2E3" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__left">
|
||||
<span>匹配客户方式</span>
|
||||
<image :src="`${$cdn}askform/bz.png`" @click="tips = '将参与活动的微信客户和企业微信客户匹配' , isShowModal = true" />
|
||||
</div>
|
||||
<div class="setting-item__right">
|
||||
<span>客户微信ID匹配</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__left">
|
||||
<span>提交次数</span>
|
||||
<image :src="`${$cdn}askform/bz.png`" @click="tips = '此功能发布后不可修改' , isShowModal = true" />
|
||||
</div>
|
||||
<div class="setting-item__right">
|
||||
<u-radio-group v-model="commitType" active-color="#1088F9">
|
||||
<u-radio name="0">不限次数</u-radio>
|
||||
<u-radio name="1">限提交一次</u-radio>
|
||||
</u-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__left">
|
||||
<span>行为通知</span>
|
||||
<image :src="`${$cdn}askform/bz.png`" @click="tips = '当客户点击或者发布表单时,发送表单的员工将会受到消息提醒' , isShowModal = true" />
|
||||
</div>
|
||||
<div class="setting-item__right">
|
||||
<u-switch v-model="actionNotice" active-value="1" inactive-value="0" :size="40" active-color="#1088F9"></u-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-item__left">
|
||||
<span>动态通知</span>
|
||||
<image :src="`${$cdn}askform/bz.png`" @click="tips = '当客户点击或者发布表单时,会将客户的打开行为记录在客户动态里' , isShowModal = true" />
|
||||
</div>
|
||||
<div class="setting-item__right">
|
||||
<u-switch v-model="dynamicNotice" active-value="1" inactive-value="0" :size="40" active-color="#1088F9"></u-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="add-form__footer">
|
||||
<div @click="back">
|
||||
<span>取消</span>
|
||||
</div>
|
||||
<div @click="confirm">{{ type === 'edit' ? '发布' : '确定' }}</div>
|
||||
</div>
|
||||
<u-modal v-model="isShowModal" :content="tips"></u-modal>
|
||||
<u-picker mode="time" v-model="isShowTime" :show-time-tag="true" @confirm="onTimeChange" :params="params"></u-picker>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
params: {
|
||||
year: true,
|
||||
month: true,
|
||||
day: true,
|
||||
hour: true,
|
||||
minute: true,
|
||||
second: true
|
||||
},
|
||||
tips: '',
|
||||
isShowModal: false,
|
||||
actionNotice: true,
|
||||
dynamicNotice: true,
|
||||
commitType: '1',
|
||||
wechatId: '0',
|
||||
periodValidityEndTime: '',
|
||||
isShowTime: false,
|
||||
periodValidityType: '0',
|
||||
type: '',
|
||||
id: ''
|
||||
}
|
||||
},
|
||||
|
||||
onLoad (query) {
|
||||
if (query.id) {
|
||||
this.id = query.id
|
||||
this.getInfo(query.id)
|
||||
this.type = query.type
|
||||
} else if (query.formConfig && query.formConfig !== '{}') {
|
||||
const res = JSON.parse(query.formConfig)
|
||||
this.periodValidityType = res.periodValidityType
|
||||
this.commitType = res.commitType
|
||||
this.actionNotice = res.actionNotice === '1'
|
||||
this.dynamicNotice = res.dynamicNotice === '1'
|
||||
|
||||
if (res.periodValidityType === '1') {
|
||||
this.periodValidityEndTime = res.periodValidityEndTime
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onTimeChange (e) {
|
||||
this.periodValidityEndTime = `${e.year}-${e.month}-${e.day} ${e.hour}:${e.minute}:${e.second}`
|
||||
},
|
||||
|
||||
back () {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
},
|
||||
|
||||
getInfo (id) {
|
||||
this.$http.post(`/app/appquestionnairetemplate/queryDetailById?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.periodValidityType = res.data.periodValidityType
|
||||
this.commitType = res.data.commitType
|
||||
this.actionNotice = res.data.actionNotice === '1'
|
||||
this.dynamicNotice = res.data.dynamicNotice === '1'
|
||||
|
||||
if (res.data.periodValidityType === '1') {
|
||||
this.periodValidityEndTime = res.data.periodValidityEndTime
|
||||
}
|
||||
}
|
||||
}).catch(msg => {
|
||||
this.$u.toast(msg)
|
||||
})
|
||||
},
|
||||
|
||||
publish () {
|
||||
this.$http.post(`/app/appquestionnairetemplate/release`, null, {
|
||||
params: {
|
||||
commitType: this.commitType,
|
||||
periodValidityType: this.periodValidityType,
|
||||
actionNotice: this.actionNotice ? '1' : '0',
|
||||
dynamicNotice: this.dynamicNotice ? '1' : '0',
|
||||
shareStatus: '0',
|
||||
wechatId: '0',
|
||||
id: this.id,
|
||||
periodValidityEndTime: this.periodValidityType === '1' ? this.periodValidityEndTime : ''
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
uni.$emit('reload')
|
||||
this.$u.toast('发布成功')
|
||||
|
||||
this.back()
|
||||
}
|
||||
}).catch(e => {
|
||||
this.$u.toast(e)
|
||||
})
|
||||
},
|
||||
|
||||
confirm () {
|
||||
if (this.type === 'edit') {
|
||||
this.publish()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
uni.$emit('setting', {
|
||||
periodValidityType: this.periodValidityType,
|
||||
commitType: this.commitType,
|
||||
actionNotice: this.actionNotice ? '1' : '0',
|
||||
dynamicNotice: this.dynamicNotice ? '1' : '0',
|
||||
periodValidityEndTime: this.periodValidityEndTime ? this.periodValidityEndTime : ''
|
||||
})
|
||||
|
||||
this.back()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form-setting {
|
||||
padding: 0 20px;
|
||||
|
||||
.add-form__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 112px;
|
||||
text-align: center;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
|
||||
&:first-child:active {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
background: #3192F4;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
line-height: 112px;
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
|
||||
&:active {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
& > h2 {
|
||||
height: 80px;
|
||||
padding-top: 24px;
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.form-setting__list {
|
||||
padding: 0 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 104px;
|
||||
border-bottom: 1px solid #D8DDE6;
|
||||
|
||||
.setting-item__right {
|
||||
color: #999;
|
||||
font-size: 28px;
|
||||
|
||||
span {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
::v-deep .u-radio__label {
|
||||
color: #999;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
::v-deep .u-radio {
|
||||
&:last-child {
|
||||
.u-radio__label {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.setting-item__left {
|
||||
color: #666666;
|
||||
font-size: 30px;
|
||||
|
||||
image {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
69
src/pages/askForm/index.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div class="form">
|
||||
<div class="form-content">
|
||||
<add-list ref="addList" v-if="currIndex === 1"></add-list>
|
||||
<list ref="list" v-if="currIndex === 0"></list>
|
||||
</div>
|
||||
<ai-tabbar :active.sync="currIndex" :list="tabBar"/>
|
||||
<div class="form-footer"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addList from './components/addList.vue'
|
||||
import list from './components/list.vue'
|
||||
import AiTabbar from '../../components/AiTabbar'
|
||||
|
||||
export default {
|
||||
name: 'formIndex',
|
||||
label: '问卷表单',
|
||||
|
||||
data () {
|
||||
return {
|
||||
currIndex: 0
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
addList,
|
||||
AiTabbar,
|
||||
list
|
||||
},
|
||||
|
||||
computed: {
|
||||
tabBar () {
|
||||
const link = icon => `${this.$cdn}askform/${icon}.png`
|
||||
return [
|
||||
{text: "表单列表", iconPath: "bdlb1", selectedIconPath: "bdlb2" },
|
||||
{text: "新建项目", iconPath: "xjxm1", selectedIconPath: "xjxm2" }
|
||||
].map(e => ({
|
||||
...e,
|
||||
iconPath: link(e.iconPath),
|
||||
selectedIconPath: link(e.selectedIconPath)
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
onLoad () {
|
||||
uni.$on('reload', () => {
|
||||
if (this.currIndex === 0) {
|
||||
this.$refs.list.reload()
|
||||
} else {
|
||||
this.$refs.addList.getList()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onReachBottom() {
|
||||
if (this.currIndex === 0) {
|
||||
this.$refs.list.getList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.form {
|
||||
padding-bottom: 98px;
|
||||
}
|
||||
</style>
|
||||
457
src/pages/askForm/previewForm.vue
Normal file
@@ -0,0 +1,457 @@
|
||||
<template>
|
||||
<div class="add-form">
|
||||
<div class="header-pic">
|
||||
<image v-if="form.headPicture" :src="form.headPicture" />
|
||||
</div>
|
||||
<div class="form-info">
|
||||
<div class="form-info__wrapper">
|
||||
<textarea class="title" :auto-height="true" disabled placeholder="请输入标题 (必填)" v-model="form.title"></textarea>
|
||||
<u-input class="content" disabled :clearable="false" type="textarea" v-model="form.tableExplain" placeholder="请输入表单描述 (选填)" :height="80" :auto-height="true" :maxlength="255"></u-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="components-list">
|
||||
<div class="components-item" v-for="(item, index) in targetList" :key="index">
|
||||
<div class="components-item__title">
|
||||
<div class="components-item__title--left">
|
||||
<em :style="{opacity: item.required ? 1 : 0}">*</em>
|
||||
<i>{{ index + 1 }}.</i>
|
||||
<h2>{{ item.label }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="components-item__filed">
|
||||
<template v-if="(item.type === 'radio')">
|
||||
<u-radio-group v-model="item.value" wrap>
|
||||
<u-radio :name="field.label" v-for="(field, i) in item.options" :key="i">
|
||||
<image :src="field.img[0].url" v-if="field.img.length"/>
|
||||
<span>{{ field.label }}</span>
|
||||
</u-radio>
|
||||
</u-radio-group>
|
||||
</template>
|
||||
<template v-if="(item.type === 'checkbox')">
|
||||
<u-checkbox-group wrap>
|
||||
<u-checkbox :name="field.label" v-model="field.checked1" v-for="(field, i) in item.options" :key="i">
|
||||
<image :src="field.img[0].url" v-if="field.img.length"/>
|
||||
<span>{{ field.label }}</span>
|
||||
</u-checkbox>
|
||||
</u-checkbox-group>
|
||||
</template>
|
||||
<template v-if="(item.type === 'select')">
|
||||
<div class="components-item__select">
|
||||
<span>{{ item.placeholder }}</span>
|
||||
<u-icon name="arrow-down" color="#DEDFDF" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="(item.type === 'upload')">
|
||||
<div class="components-item__select components-item__textarea components-item__upload">
|
||||
<image :src="`${$cdn}askform/upload.png`" />
|
||||
<span>选择图片(2M以内)</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="(item.type === 'input')">
|
||||
<div class="components-item__select">
|
||||
<span>{{ item.placeholder }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="(item.type === 'textarea')">
|
||||
<div class="components-item__select components-item__textarea">
|
||||
<span>{{ item.placeholder }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AiBack></AiBack>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiBack from "@/components/AiBack";
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
tableExplain: '详细描述',
|
||||
title: '问卷调查',
|
||||
isShowheadPicture: true,
|
||||
isShowTableExplain: true,
|
||||
isShowBtn: true,
|
||||
headPicture: '',
|
||||
commitType: '1',
|
||||
periodValidityType: '0',
|
||||
actionNotice: '1',
|
||||
dynamicNotice: '1',
|
||||
periodValidityEndTime: '',
|
||||
shareStatus: '0',
|
||||
count: 0,
|
||||
wechatId: '0',
|
||||
type: 0,
|
||||
buttonExplain: '提交',
|
||||
tips: true
|
||||
},
|
||||
templateType: 0,
|
||||
targetList: [],
|
||||
isShow: false,
|
||||
type: 0,
|
||||
id: '',
|
||||
touchStart: 0
|
||||
}
|
||||
},
|
||||
onLoad (query) {
|
||||
this.form = JSON.parse(query.form)
|
||||
this.targetList = JSON.parse(query.targetList)
|
||||
},
|
||||
|
||||
components: {
|
||||
AiBack
|
||||
},
|
||||
|
||||
methods: {
|
||||
getInfo (id) {
|
||||
uni.showLoading()
|
||||
this.$http.post(`/app/appquestionnairetemplate/queryDetailById?id=${id}`).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.form = {
|
||||
...res.data,
|
||||
headPicture: res.data.headPicture
|
||||
}
|
||||
|
||||
this.type = res.data.type
|
||||
|
||||
this.targetList = res.data.fields.map(item => {
|
||||
return JSON.parse(item.fieldInfo)
|
||||
})
|
||||
|
||||
this.pageShow = true
|
||||
} else {
|
||||
this.$u.toast(res.msg)
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
}).catch(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.add-form {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 60px;
|
||||
box-sizing: border-box;
|
||||
background: #F3F6F9;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
::v-deep .u-drawer-bottom {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.components-list {
|
||||
padding: 0 20px;
|
||||
|
||||
.components-item {
|
||||
margin-top: 24px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 4px 8px 4px rgba(233, 233, 233, 0.39);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #EEEFF0;
|
||||
background: #fff;
|
||||
|
||||
::v-deep .u-radio, ::v-deep .u-checkbox {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.u-checkbox__icon-wrap, .u-radio__icon-wrap {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.u-radio__label, .u-checkbox__label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 0;
|
||||
margin-left: 40px;
|
||||
line-height: 1.5;
|
||||
text-align: justify;
|
||||
|
||||
image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 0 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
span {
|
||||
flex: 1;
|
||||
color: #666;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.components-item__select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0 26px;
|
||||
border: 1px solid #DEDFDF;
|
||||
|
||||
&.components-item__textarea {
|
||||
align-items: flex-start;
|
||||
height: 160px;
|
||||
padding-top: 20px;
|
||||
|
||||
image {
|
||||
width: 46px;
|
||||
height: 34px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
&.components-item__upload {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.components-item__title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 32px;
|
||||
|
||||
em {
|
||||
margin-right: 4px;
|
||||
font-style: normal;
|
||||
color: rgb(226, 33, 32);;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
box-sizing: content-box;
|
||||
padding: 20px 0 20px 20px;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-popup {
|
||||
height: 440px;
|
||||
border-radius: 20px 20px 0 0;
|
||||
background: #fff;
|
||||
|
||||
.add-popup__title {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 96px;
|
||||
border-bottom: 1px solid #E4E5E6;
|
||||
|
||||
h2 {
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
image {
|
||||
position: absolute;
|
||||
right: 32px;
|
||||
top: 50%;
|
||||
width: 30px;
|
||||
height: 20px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.add-popup__list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 34px;
|
||||
|
||||
span {
|
||||
width: calc((100% - 64px) / 3);
|
||||
height: 78px;
|
||||
line-height: 78px;
|
||||
margin-top: 32px;
|
||||
margin-right: 32px;
|
||||
text-align: center;
|
||||
color: #333333;
|
||||
font-size: 28px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #E4E5E6;
|
||||
|
||||
&:nth-of-type(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-form__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 112px;
|
||||
text-align: center;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
|
||||
&:last-child {
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
background: #3192F4;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
line-height: 112px;
|
||||
color: #333333;
|
||||
font-size: 32px;
|
||||
|
||||
&:active {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-form__btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 214px;
|
||||
height: 66px;
|
||||
line-height: 66px;
|
||||
margin: 64px auto 0;
|
||||
background: #FFFFFF;
|
||||
border-radius: 34px;
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 16px;
|
||||
color: #4392E6;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-info {
|
||||
margin-top: 26px;
|
||||
padding: 0 20px;
|
||||
|
||||
& > h2 {
|
||||
height: 76px;
|
||||
line-height: 76px;
|
||||
color: #999999;
|
||||
font-weight: normal;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.form-info__wrapper {
|
||||
padding: 0 18px;
|
||||
background: #fff;
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
padding: 22px 0;
|
||||
font-size: 36px;
|
||||
border-bottom: 1px solid #F1F2F3;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px 0!important;
|
||||
font-size: 28px;
|
||||
|
||||
::v-deep textarea {
|
||||
color: #333;
|
||||
font-size: 28px!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-pic {
|
||||
position: relative;
|
||||
font-size: 0;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
width: 148px;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 26px;
|
||||
background: rgba(0, 0, 0, 0.16);
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 320px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
501
src/pages/bigHorn/addPlay.vue
Normal file
@@ -0,0 +1,501 @@
|
||||
<template>
|
||||
<div class="addPlay">
|
||||
<div class="content">
|
||||
<div class="item">
|
||||
<div class="label">播发内容</div>
|
||||
<div class="value" @click="linkTo('/pages/resourcesManage/resourcesManage?isChoose=1')">
|
||||
<span :class="formData.mediaName == '请选择' ? 'color-999' : ''">{{formData.mediaName}}</span>
|
||||
<img src="./img/right-icon.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="label">播放设备</div>
|
||||
<div class="value" @click="selectClick('showEquipment', equipmentList)">
|
||||
<span :class="formData.serialName == '请选择' ? 'color-999' : ''">{{formData.serialName}}</span>
|
||||
<img src="./img/right-icon.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="label">播发级别</div>
|
||||
<div class="value" @click="selectClick('showMessageLevel', messageLevelList)">
|
||||
<span :class="formData.messageLevelName == '请选择' ? 'color-999' : ''">{{formData.messageLevelName}}</span>
|
||||
<img src="./img/right-icon.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="radio-content">
|
||||
<div class="title">播放方式</div>
|
||||
<div class="item mar-r50" :class="formData.taskType == 0 ? 'active' : ''" @click="formData.taskType = 0">立即播放<img src="./img/bigHorn-xz.png" alt=""></div>
|
||||
<div class="item" :class="formData.taskType == 1 ? 'active' : ''" @click="formData.taskType = 1"><img src="./img/bigHorn-xz.png" alt="">定时播放</div>
|
||||
</div>
|
||||
<div class="content" v-if="formData.taskType != 0">
|
||||
<div class="item">
|
||||
<div class="label">定时策略</div>
|
||||
<div class="value" @click="selectClick('showCyclingType', cyclingTypeList)">
|
||||
<span :class="formData.cyclingTypeName == '请选择' ? 'color-999' : ''">{{formData.cyclingTypeName}}</span>
|
||||
<img src="./img/right-icon.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="radio-content" v-if="formData.taskType != 0 && formData.cyclingType == 2">
|
||||
<div class="title">播放天数</div>
|
||||
<div class="mini-item" :class="item.isCheck ? 'mini-active' : ''" v-for="(item, index) in dayList" :key="index" @click="checkClick(index)">{{item.label}}</div>
|
||||
</div>
|
||||
<div class="content" v-if="formData.taskType != 0 && formData.cyclingType == 3">
|
||||
<div class="item">
|
||||
<div class="label">播放天数</div>
|
||||
<div class="value">
|
||||
<u-input type="text" placeholder="请输入" height="18" input-align="right" v-model="formData.broadcastDay" maxlength="4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content" v-if="formData.taskType != 0">
|
||||
<div class="item">
|
||||
<div class="label">开始日期</div>
|
||||
<div class="value" @click="timeClick(true, 'showDate')">
|
||||
<span :class="formData.startDate ? 'color-999' : ''">{{formData.startDate || '请选择'}}</span>
|
||||
<img src="./img/right-icon.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="label">开始时间</div>
|
||||
<div class="value" @click="timeClick(false, 'showSatrt')">
|
||||
<span :class="formData.startTime ? 'color-999' : ''">{{formData.startTime || '请选择'}}</span>
|
||||
<img src="./img/right-icon.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="label">结束时间</div>
|
||||
<div class="value" @click="timeClick(false, 'showEnd')">
|
||||
<span :class="formData.endTime ? 'color-999' : ''">{{formData.endTime || '请选择'}}</span>
|
||||
<img src="./img/right-icon.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn" @click="addConfirm">确认</div>
|
||||
<u-select v-model="showSelect" :list="selectList" @confirm="confirm" label-name="dictName" value-name="dictValue"></u-select>
|
||||
<u-picker v-model="showDateTime" mode="time" :params="params" @confirm="confirm"></u-picker>
|
||||
<AiBack></AiBack>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AiBack from "@/components/AiBack";
|
||||
export default {
|
||||
name: "addPlay",
|
||||
data() {
|
||||
return {
|
||||
showSelect: false,
|
||||
selectList: [],
|
||||
showMedia: false,
|
||||
mediaList: [],
|
||||
showEquipment: false,
|
||||
equipmentList: [],
|
||||
showMessageLevel: false,
|
||||
messageLevelList: [],
|
||||
showCyclingType: false,
|
||||
cyclingTypeList: [],
|
||||
formData: {
|
||||
mediaId: '',
|
||||
mediaName: '请选择',
|
||||
serialNo: '',
|
||||
serialName: '请选择',
|
||||
messageLevel: '',
|
||||
messageLevelName: '请选择',
|
||||
taskType: '0',
|
||||
cyclingTypeName: '请选择',
|
||||
cyclingType: '',
|
||||
startDate: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
broadcastDay: '',
|
||||
cyclingDate: ''
|
||||
},
|
||||
dayList: [
|
||||
{
|
||||
isCheck: false,
|
||||
value: 1,
|
||||
label: '每周一'
|
||||
},
|
||||
{
|
||||
isCheck: false,
|
||||
value: 2,
|
||||
label: '每周二'
|
||||
},
|
||||
{
|
||||
isCheck: false,
|
||||
value: 3,
|
||||
label: '每周三'
|
||||
},
|
||||
{
|
||||
isCheck: false,
|
||||
value: 4,
|
||||
label: '每周四'
|
||||
},
|
||||
{
|
||||
isCheck: false,
|
||||
value: 5,
|
||||
label: '每周五'
|
||||
},
|
||||
{
|
||||
isCheck: false,
|
||||
value: 6,
|
||||
label: '每周六'
|
||||
},
|
||||
{
|
||||
isCheck: false,
|
||||
value: 7,
|
||||
label: '每周日'
|
||||
}
|
||||
],
|
||||
showDateTime: false,
|
||||
showDate: false,
|
||||
showSatrt: false,
|
||||
showEnd: false,
|
||||
params: {
|
||||
year: true,
|
||||
month: true,
|
||||
day: true,
|
||||
hour: false,
|
||||
minute: false,
|
||||
second: false
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AiBack
|
||||
},
|
||||
|
||||
onLoad () {
|
||||
uni.$on('choose', e => {
|
||||
console.log(e)
|
||||
this.formData.mediaId = e.mediaId
|
||||
this.formData.mediaName = e.mediaName
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
addConfirm() {
|
||||
var cyclingDateList = []
|
||||
this.dayList.map((item) => {
|
||||
if(item.isCheck) {
|
||||
cyclingDateList.push(item.value)
|
||||
}
|
||||
})
|
||||
|
||||
if(!this.formData.mediaId) {
|
||||
return this.$u.toast('请选择播发内容')
|
||||
}
|
||||
if(!this.formData.serialNo) {
|
||||
return this.$u.toast('请选择播放设备')
|
||||
}
|
||||
if(!this.formData.messageLevel) {
|
||||
return this.$u.toast('播发级别')
|
||||
}
|
||||
|
||||
//播放方式(定时播放)
|
||||
if(this.formData.taskType != 0 && this.formData.startDate == '请选择') {
|
||||
return this.$u.toast('请选择开始日期')
|
||||
}
|
||||
if(this.formData.taskType != 0 && this.formData.startTime == '请选择') {
|
||||
return this.$u.toast('请选择开始时间')
|
||||
}
|
||||
if(this.formData.taskType != 0 && this.formData.endTime == '请选择') {
|
||||
return this.$u.toast('请选择结束时间')
|
||||
}
|
||||
//播放方式(定时播放)定时策略(时长)
|
||||
if(this.formData.taskType != 0 && this.formData.cyclingType == 3 && !this.formData.broadcastDay) {
|
||||
return this.$u.toast('请输入播放天数')
|
||||
}
|
||||
//播放方式(定时播放)定时策略(自定义)
|
||||
if(this.formData.taskType != 0 && this.formData.cyclingType == 2 && !cyclingDateList.length) {
|
||||
return this.$u.toast('请选择播放天数')
|
||||
}
|
||||
|
||||
if(this.formData.taskType != 0 && this.formData.cyclingType == 2) {
|
||||
this.formData.cyclingDate = cyclingDateList.join(',')
|
||||
}
|
||||
this.formData.coverageType = '4'
|
||||
this.$http.post(`/app/appzyvideobroadcast/play`, {...this.formData,}).then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.$u.toast('提交成功')
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
linkTo (path) {
|
||||
uni.navigateTo({
|
||||
url: path
|
||||
})
|
||||
},
|
||||
|
||||
confirm(e) {
|
||||
if(this.showMedia) {
|
||||
this.formData.mediaId = e[0].value
|
||||
this.formData.mediaName = e[0].label
|
||||
}
|
||||
if(this.showEquipment) {
|
||||
this.formData.serialNo = e[0].value
|
||||
this.formData.serialName = e[0].label
|
||||
}
|
||||
if(this.showMessageLevel) {
|
||||
this.formData.messageLevel = e[0].value
|
||||
this.formData.messageLevelName = e[0].label
|
||||
}
|
||||
if(this.showCyclingType) {
|
||||
this.formData.cyclingType = e[0].value
|
||||
this.formData.cyclingTypeName = e[0].label
|
||||
}
|
||||
if(this.showDate) {
|
||||
this.formData.startDate = e.year + '-' + e.month + '-' + e.day
|
||||
}
|
||||
if(this.showSatrt) {
|
||||
var startTime = e.hour + ':' + e.minute + ':' + e.second
|
||||
var myDate = new Date();
|
||||
var time = myDate.getHours() + ':' + myDate.getMinutes() + ':' + myDate.getSeconds()
|
||||
if (this.timeToSec(startTime) - this.timeToSec(time) > 0) {
|
||||
this.formData.startTime = startTime
|
||||
}else {
|
||||
this.$u.toast('开始时间要大于当前时间')
|
||||
}
|
||||
}
|
||||
if(this.showEnd) {
|
||||
var endTime = e.hour + ':' + e.minute + ':' + e.second
|
||||
console.log(this.timeToSec(endTime), this.timeToSec(this.formData.startTime))
|
||||
if (this.timeToSec(endTime) - this.timeToSec(this.formData.startTime) > 0) {
|
||||
this.formData.endTime = endTime
|
||||
} else {
|
||||
this.$u.toast('结束时间要大于开始时间')
|
||||
}
|
||||
}
|
||||
this.init()
|
||||
},
|
||||
init() {
|
||||
this.showMedia = false
|
||||
this.showEquipment = false
|
||||
this.showMessageLevel = false
|
||||
this.showCyclingType = false
|
||||
this.showDate = false
|
||||
this.showSatrt = false
|
||||
this.showEnd = false
|
||||
},
|
||||
selectClick(showType, list) {
|
||||
this.showSelect = true
|
||||
this[showType] = true
|
||||
this.selectList = list
|
||||
},
|
||||
getMediaList() {
|
||||
this.$http.post(`/app/appdlbresource/list?current=1&size=10000`).then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.mediaList = []
|
||||
if(res.data && res.data.records.length) {
|
||||
res.data.records.map((item) => {
|
||||
let info = {
|
||||
dictName: item.name,
|
||||
dictValue: item.id
|
||||
}
|
||||
this.mediaList.push(info)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
getEquipmentList() {
|
||||
this.$http.post(`/app/appdlbquipment/getDlbDeviceList?current=1&size=10000&devStatus=5&keyword=`).then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.equipmentList = []
|
||||
if(res.data && res.data.records.length) {
|
||||
res.data.records.map((item) => {
|
||||
let info = {
|
||||
dictName: item.deviceName,
|
||||
dictValue: item.serialNo
|
||||
}
|
||||
this.equipmentList.push(info)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
checkClick(index) {
|
||||
this.dayList[index].isCheck = !this.dayList[index].isCheck
|
||||
},
|
||||
timeClick(showYear, showType) {
|
||||
this[showType] = true
|
||||
this.showDateTime = true
|
||||
if(showYear) {
|
||||
this.params = {
|
||||
year: true,
|
||||
month: true,
|
||||
day: true,
|
||||
hour: false,
|
||||
minute: false,
|
||||
second: false
|
||||
}
|
||||
}else {
|
||||
this.params = {
|
||||
year: false,
|
||||
month: false,
|
||||
day: false,
|
||||
hour: true,
|
||||
minute: true,
|
||||
second: true
|
||||
}
|
||||
}
|
||||
},
|
||||
timeToSec(time) {
|
||||
var s = "";
|
||||
var hour = time.split(":")[0];
|
||||
var min = time.split(":")[1];
|
||||
var second = time.split(":")[2];
|
||||
s = Number(hour * 3600) + Number(min * 60) + Number(second)
|
||||
return s;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$dict.load('dlbMessageUrgency', 'dlbBroadTaskType', 'dlbDyclingType').then(() => {
|
||||
this.getMediaList()
|
||||
this.getEquipmentList()
|
||||
this.messageLevelList = this.$dict.getDict('dlbMessageUrgency')
|
||||
this.cyclingTypeList = this.$dict.getDict('dlbDyclingType')
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.addPlay {
|
||||
padding-bottom: 128px;
|
||||
.content{
|
||||
padding-left: 32px;
|
||||
background-color: #fff;
|
||||
.item{
|
||||
width: 100%;
|
||||
padding: 34px 0;
|
||||
font-size: 32px;
|
||||
font-family: PingFangSC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
line-height: 44px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
color: #333;
|
||||
justify-content: space-between;
|
||||
.label{
|
||||
width: 198px;
|
||||
font-size: 32px;
|
||||
}
|
||||
.value{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 28px;
|
||||
flex: 1;
|
||||
padding-right: 32px;
|
||||
max-width: calc(100% - 198px);
|
||||
box-sizing: border-box;
|
||||
text-align: right;
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
img{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
.color-999{
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.radio-content{
|
||||
padding: 34px 32px 38px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
.title{
|
||||
font-size: 32px;
|
||||
font-family: PingFangSC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
line-height: 44px;
|
||||
margin-bottom: 54px;
|
||||
span{
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
.item{
|
||||
display: inline-block;
|
||||
width: 320px;
|
||||
height: 112px;
|
||||
line-height: 112px;
|
||||
text-align: center;
|
||||
background: #F5F5F5;
|
||||
border-radius: 4px;
|
||||
font-size: 30px;
|
||||
font-family: PingFangSC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
img{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.active{
|
||||
background: #E7F1FE;
|
||||
color: #1174FE;
|
||||
position: relative;
|
||||
img{
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
}
|
||||
}
|
||||
.mar-r50 {
|
||||
margin-right: 50px;
|
||||
}
|
||||
.mini-item{
|
||||
display: inline-block;
|
||||
width: 128px;
|
||||
height: 72px;
|
||||
line-height: 72px;
|
||||
text-align: center;
|
||||
background: #F9F9F9;
|
||||
border-radius: 16px;
|
||||
color: #333;
|
||||
font-size: 28px;
|
||||
margin-right: 58px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.mini-item:nth-of-type(5) {
|
||||
margin-right: 0;
|
||||
}
|
||||
.mini-active{
|
||||
background: #F2F8FE;
|
||||
border: 1px solid #89B2EE;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.btn{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 112px;
|
||||
line-height: 112px;
|
||||
text-align: center;
|
||||
background: #3975C6;
|
||||
font-size: 32px;
|
||||
font-family: PingFangSC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
167
src/pages/bigHorn/bigHorn.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div class="bigHorn">
|
||||
<div class="header">
|
||||
<img src="./img/bigHorn-bg.png" alt="">
|
||||
<div class="content">
|
||||
<div class="item" @click="linkTo('./onlineList')">
|
||||
<img src="./img/bigHorn-icon1@2x.png" alt="">
|
||||
<div>在线设备</div>
|
||||
<!-- <h2>1</h2> -->
|
||||
</div>
|
||||
<div class="item" @click="linkTo('./playList')">
|
||||
<img src="./img/bigHorn-icon2@2x.png" alt="">
|
||||
<div>播放记录</div>
|
||||
</div>
|
||||
<div class="item" @click="linkTo('./onlinePlayList')">
|
||||
<img src="./img/bigHorn-icon3@2x.png" alt="">
|
||||
<div>在播设备</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="banner">
|
||||
<div class="item" :class="item.bgClass" v-for="(item, index) in bannerList" :key="index" @click="linkTo(item.path)">
|
||||
<h2>{{item.title}}</h2>
|
||||
<div>{{item.text}}</div>
|
||||
<img :src="item.imgUrl" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "bigHorn",
|
||||
data() {
|
||||
return {
|
||||
bannerList: [
|
||||
{
|
||||
title: '素材播放',
|
||||
text: '支持音频立即播发和定时播发',
|
||||
imgUrl: require('./img/bigHorn-icon11@2x.png'),
|
||||
path: '/pages/bigHorn/addPlay',
|
||||
bgClass: 'bg-67A3F4'
|
||||
},
|
||||
{
|
||||
title: '实时喊话',
|
||||
text: '实时在线喊话,远程广播通知',
|
||||
imgUrl: require('./img/bigHorn-icon22@2x.png'),
|
||||
bgClass: 'bg-4ED5BB'
|
||||
},
|
||||
{
|
||||
title: '音频录制',
|
||||
text: '音频文件的录制',
|
||||
path: '/pages/resourcesManage/addPlay?type=1',
|
||||
imgUrl: require('./img/bigHorn-icon33@2x.png'),
|
||||
bgClass: 'bg-E5B565'
|
||||
},
|
||||
{
|
||||
title: '媒资管理',
|
||||
path: '/pages/resourcesManage/resourcesManage',
|
||||
text: '支持音频文件和录音内容添加',
|
||||
imgUrl: require('./img/bigHorn-icon44@2x.png'),
|
||||
bgClass: 'bg-F19661'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
linkTo(url) {
|
||||
uni.navigateTo({url})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.bigHorn {
|
||||
.header{
|
||||
position: relative;
|
||||
img{
|
||||
width: 100%;
|
||||
height: 306px;
|
||||
}
|
||||
.content{
|
||||
width: 686px;
|
||||
padding: 40px 0;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 4px 8px 4px rgba(0, 0, 0, 0.06);
|
||||
border-radius: 12px;
|
||||
position: absolute;
|
||||
top: 210px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
.item{
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
img{
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
div{
|
||||
font-size: 30px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 42px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
h2{
|
||||
font-size: 40px;
|
||||
font-family: DINAlternate-Bold, DINAlternate;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.banner{
|
||||
margin-top: 150px;
|
||||
.item{
|
||||
width: 686px;
|
||||
height: 190px;
|
||||
border-radius: 12px;
|
||||
margin: 0 auto 24px auto;
|
||||
padding: 40px 46px 0 80px;
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
position: relative;
|
||||
h2{
|
||||
font-size: 42px;
|
||||
font-family: PingFangSC-Medium, PingFang SC;
|
||||
font-weight: 500;
|
||||
line-height: 60px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
div{
|
||||
font-size: 26px;
|
||||
font-family: PingFangSC-Regular, PingFang SC;
|
||||
font-weight: 400;
|
||||
color: #FFF;
|
||||
line-height: 32px;
|
||||
}
|
||||
img{
|
||||
width: 160px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 30px;
|
||||
}
|
||||
}
|
||||
.bg-67A3F4{
|
||||
background: #67A3F4;
|
||||
}
|
||||
.bg-4ED5BB{
|
||||
background: #4ED5BB;
|
||||
}
|
||||
.bg-E5B565{
|
||||
background: #E5B565;
|
||||
}
|
||||
.bg-F19661{
|
||||
background: #F19661;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
src/pages/bigHorn/img/bigHorn-bg.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
src/pages/bigHorn/img/bigHorn-icon11@2x.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/pages/bigHorn/img/bigHorn-icon1@2x.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/pages/bigHorn/img/bigHorn-icon22@2x.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/pages/bigHorn/img/bigHorn-icon2@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/pages/bigHorn/img/bigHorn-icon33@2x.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
src/pages/bigHorn/img/bigHorn-icon3@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
src/pages/bigHorn/img/bigHorn-icon44@2x.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/pages/bigHorn/img/bigHorn-lb@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/pages/bigHorn/img/bigHorn-xz.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/pages/bigHorn/img/cir.png
Normal file
|
After Width: | Height: | Size: 795 B |
BIN
src/pages/bigHorn/img/lb@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
src/pages/bigHorn/img/right-icon.png
Normal file
|
After Width: | Height: | Size: 269 B |
BIN
src/pages/bigHorn/img/search-icon.png
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
src/pages/bigHorn/img/select-blue.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
93
src/pages/bigHorn/onlineList.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="onlineList">
|
||||
<div class="record">
|
||||
<div class="item" v-for="(item, index) in list" :key="index">
|
||||
<img src="./img/bigHorn-lb@2x.png" alt="">
|
||||
<div class="info">
|
||||
<p>{{item.deviceName}}</p>
|
||||
<span>{{item.areaName}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ai-empty v-if="!list.length"></ai-empty>
|
||||
<AiBack></AiBack>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AiEmpty from '@/components/AiEmpty/AiEmpty'
|
||||
import AiBack from "@/components/AiBack";
|
||||
export default {
|
||||
name: "onlineList",
|
||||
data() {
|
||||
return {
|
||||
page: {current: 1, size: 10, total: 0},
|
||||
list: []
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AiEmpty,
|
||||
AiBack
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.$http.post("/app/appdlbquipment/getDlbDeviceList", null, {
|
||||
params: {...this.page, devStatus: 5}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
if (this.page.current > 1) {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
} else this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
reachBottom() {
|
||||
if (this.page.total > this.list.length) {
|
||||
this.page.current++
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.onlineList {
|
||||
.record{
|
||||
padding-left: 32px;
|
||||
background-color: #fff;
|
||||
.item{
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
padding: 12px 40px 16px 0;
|
||||
img{
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 12px 16px 0 0;
|
||||
}
|
||||
.info{
|
||||
width: calc(100% - 100px);
|
||||
p{
|
||||
font-size: 34px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
span{
|
||||
font-size: 22px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
src/pages/bigHorn/onlinePlayList.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="onlinePlayList">
|
||||
<div class="record">
|
||||
<div class="item" v-for="(item, index) in list" :key="index">
|
||||
<img src="./img/bigHorn-lb@2x.png" alt="">
|
||||
<div class="info">
|
||||
<p>{{item.deviceName}}</p>
|
||||
<span>{{item.createTime}}</span><br />
|
||||
<span>{{item.name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ai-empty v-if="!list.length"></ai-empty>
|
||||
<AiBack></AiBack>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AiEmpty from '@/components/AiEmpty/AiEmpty'
|
||||
import AiBack from "@/components/AiBack";
|
||||
export default {
|
||||
name: "onlinePlayList",
|
||||
data() {
|
||||
return {
|
||||
page: {current: 1, size: 10, total: 0},
|
||||
list: []
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AiEmpty,
|
||||
AiBack
|
||||
},
|
||||
|
||||
methods: {
|
||||
getList() {
|
||||
this.$http.post("/app/appdlbquipment/getDlbDeviceList", null, {
|
||||
params: {...this.page, devStatus: 1}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
if (this.page.current > 1) {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
} else this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
reachBottom() {
|
||||
if (this.page.total > this.list.length) {
|
||||
this.page.current++
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.onlinePlayList {
|
||||
.record{
|
||||
padding-left: 32px;
|
||||
background-color: #fff;
|
||||
.item{
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
padding: 12px 40px 16px 0;
|
||||
img{
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 12px 16px 0 0;
|
||||
}
|
||||
.info{
|
||||
width: calc(100% - 100px);
|
||||
p{
|
||||
font-size: 34px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
span{
|
||||
font-size: 22px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
138
src/pages/bigHorn/playList.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="playList">
|
||||
<div class="title">
|
||||
<span>播放记录</span>
|
||||
<span>操作</span>
|
||||
</div>
|
||||
<div class="record">
|
||||
<div class="item" v-for="(item, index) in list" :key="index">
|
||||
<div class="info">
|
||||
<p>{{item.messageName}}</p>
|
||||
<span>{{item.createTime}}</span><br />
|
||||
<span>{{item.deviceName}}</span>
|
||||
</div>
|
||||
<div class="btn bg-3975C6" v-if="item.broadcastStatus == 0 || item.broadcastStatus == 1 || item.broadcastStatus == 2" @click="cancel(item.broadcastId)">撤销</div>
|
||||
<div class="btn bg-AFD0FC" v-if="item.broadcastStatus == 6">已取消</div>
|
||||
</div>
|
||||
</div>
|
||||
<AiBack></AiBack>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AiBack from "@/components/AiBack";
|
||||
export default {
|
||||
name: "playList",
|
||||
data() {
|
||||
return {
|
||||
page: {current: 1, size: 10, total: 0},
|
||||
list: []
|
||||
}
|
||||
},
|
||||
|
||||
components: {
|
||||
AiBack
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.$http.post("/app/appzyvideobroadcast/getBroadcastRecords", null, {
|
||||
params: {...this.page}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
if (this.page.current > 1) {
|
||||
this.list = [...this.list, ...res.data.records]
|
||||
} else this.list = res.data.records
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
reachBottom() {
|
||||
if (this.page.total > this.list.length) {
|
||||
this.page.current++
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
cancel(id) {
|
||||
this.$confirm('确定撤回该广播?').then(() => {
|
||||
this.$http.post(`/app/appzyvideobroadcast/getBroadcastRecall?broadcastId=${id}`).then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.$u.toast('撤回成功!')
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$dict.load('dlbBroadcastStatus').then(() => {
|
||||
this.getList()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.playList {
|
||||
.title{
|
||||
width: 100%;
|
||||
height: 88px;
|
||||
line-height: 88px;
|
||||
background: #FFF;
|
||||
padding: 0 80px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #333;
|
||||
font-size: 34px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.record{
|
||||
padding-left: 32px;
|
||||
background-color: #fff;
|
||||
.item{
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 12px 40px 16px 0;
|
||||
box-sizing: border-box;
|
||||
.info{
|
||||
width: 480px;
|
||||
margin-right: 40px;
|
||||
word-break: break-all;
|
||||
p{
|
||||
font-size: 34px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
line-height: 48px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
span{
|
||||
font-size: 22px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
.btn{
|
||||
width: 154px;
|
||||
height: 60px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
font-size: 30px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #FFF;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.bg-3975C6{
|
||||
background: #3975C6;
|
||||
}
|
||||
.bg-AFD0FC{
|
||||
background: #AFD0FC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
160
src/pages/bigHorn/selectEquipment.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="selectEquipment">
|
||||
<div class="search">
|
||||
<div class="search-bg">
|
||||
<img src="./img/search-icon.png" alt="">
|
||||
<u-input v-model="value" type="text" placeholder="搜索设备名称" class="search-input" height="18" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="record">
|
||||
<div class="item">
|
||||
<img src="./img/cir.png" alt="" class="check-img">
|
||||
<img src="./img/lb@2x.png" alt="" class="voice-img">
|
||||
<div class="info">
|
||||
<div class="text">
|
||||
<p>村头大喇叭</p>
|
||||
<span>刘家河居委会</span>
|
||||
</div>
|
||||
<div class="status">在线</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<img src="./img/cir.png" alt="" class="check-img">
|
||||
<img src="./img/lb@2x.png" alt="" class="voice-img">
|
||||
<div class="info">
|
||||
<div class="text">
|
||||
<p>村头大喇叭</p>
|
||||
<span>刘家河居委会</span>
|
||||
</div>
|
||||
<div class="status">在线</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<div>确定选择</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "selectEquipment",
|
||||
data() {
|
||||
return {
|
||||
value: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.selectEquipment {
|
||||
padding-bottom: 128px;
|
||||
.search{
|
||||
width: 100%;
|
||||
height: 104px;
|
||||
background: #FFF;
|
||||
margin-bottom: 4px;
|
||||
padding: 20px 32px;
|
||||
box-sizing: border-box;
|
||||
.search-bg{
|
||||
width: 686px;
|
||||
height: 64px;
|
||||
padding: 14px 0;
|
||||
box-sizing: border-box;
|
||||
background: #F5F5F5;
|
||||
border-radius: 32px;
|
||||
position: relative;
|
||||
img{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 32px;
|
||||
}
|
||||
.search-input{
|
||||
width: 590px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
font-size: 28px;
|
||||
margin-left: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.record{
|
||||
padding-left: 32px;
|
||||
background-color: #fff;
|
||||
.item{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ddd;
|
||||
.check-img{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 32px 32px 0 0;
|
||||
}
|
||||
.voice-img{
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 28px 16px 0 0;
|
||||
}
|
||||
.info{
|
||||
width: calc(100% - 148px);
|
||||
padding: 18px 0;
|
||||
line-height: 44px;
|
||||
font-size: 34px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.text{
|
||||
p{
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
span{
|
||||
font-size: 26px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #999;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
.status{
|
||||
font-size: 34px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #4E8EEE;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 128px;
|
||||
background: #FFF;
|
||||
border-top: 1px solid #ddd;
|
||||
padding: 24px 32px 24px 0;
|
||||
box-sizing: border-box;
|
||||
div{
|
||||
width: 192px;
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
background: #3975C6;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
83
src/pages/bigHorn/selectMp3.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="selectMp3">
|
||||
<div class="record">
|
||||
<div class="item">
|
||||
<img src="./img/cir.png" alt="">
|
||||
<div class="info">
|
||||
<p>村头大喇叭</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<div>确定选择</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "selectMp3",
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.selectMp3 {
|
||||
padding-bottom: 128px;
|
||||
.record{
|
||||
padding-left: 32px;
|
||||
background-color: #fff;
|
||||
.item{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
img{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 40px 16px 0 0;
|
||||
}
|
||||
.info{
|
||||
width: calc(100% - 60px);
|
||||
padding-bottom: 16px;
|
||||
padding: 34px 0;
|
||||
line-height: 44px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
font-size: 34px;
|
||||
font-family: PingFang-SC-Medium, PingFang-SC;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 128px;
|
||||
background: #FFF;
|
||||
border-top: 1px solid #ddd;
|
||||
padding: 24px 32px 24px 0;
|
||||
box-sizing: border-box;
|
||||
div{
|
||||
width: 192px;
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
text-align: center;
|
||||
background: #3975C6;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
268
src/pages/casuallyask/casuallyask.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div class="casuallyask">
|
||||
<div class="banner">
|
||||
<picker :range="dictList" @change="bindPickerChange" range-key="dictName">
|
||||
<div class="picker">
|
||||
{{ askType }}
|
||||
<u-icon
|
||||
name="arrow-down"
|
||||
:custom-style="{ margin: '0 5px' }"
|
||||
></u-icon>
|
||||
</div>
|
||||
</picker>
|
||||
<u-search
|
||||
placeholder="请输入标题"
|
||||
:show-action="false"
|
||||
v-model="keyword"
|
||||
@search="onSearch"
|
||||
/>
|
||||
</div>
|
||||
<u-tabs
|
||||
class="nav"
|
||||
:list="tabs"
|
||||
:is-scroll="false"
|
||||
:current="currentType"
|
||||
font-size="32"
|
||||
bar-width="192"
|
||||
height="96"
|
||||
@change="handleTabClick"
|
||||
></u-tabs>
|
||||
<div class="body" v-if="eventList.length !== 0">
|
||||
<div
|
||||
class="content"
|
||||
v-for="(item, index) in eventList"
|
||||
:key="index"
|
||||
@click="detail(item)"
|
||||
>
|
||||
<u-row>
|
||||
<div>
|
||||
<div class="top">
|
||||
|
||||
<text :class="item.type == '0' ? 'active' : 'noactive'">
|
||||
{{ $dict.getLabel('leaveMessageType', item.type) }}
|
||||
</text>
|
||||
<!-- 村 -->
|
||||
<text class="areaName" v-if="item.areaName">{{ item.areaName }}:</text>
|
||||
<!-- 问题 -->
|
||||
<text class="title">{{ item.title }}</text>
|
||||
</div>
|
||||
|
||||
<div class="cont">
|
||||
<span class="name_time">留言人:</span>
|
||||
<text class="text_c"> {{ item.leaveName }}</text>
|
||||
</div>
|
||||
<div class="cont">
|
||||
<text class="name_time">留言时间:</text>
|
||||
<text class="text_c"> {{ item.createTime }}</text>
|
||||
</div>
|
||||
</div>
|
||||
</u-row>
|
||||
</div>
|
||||
</div>
|
||||
<AiEmpty v-else/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiEmpty from '../../components/AiEmpty/AiEmpty'
|
||||
import URow from '../../uview/components/u-row/u-row.vue'
|
||||
|
||||
export default {
|
||||
name: 'casuallyAsk',
|
||||
// 组件
|
||||
components: {URow, AiEmpty},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
page: {current: 1, size: 10, total: 0},
|
||||
dictList: [], // 字典
|
||||
index: 0, // 留言类型 0投诉 1咨询 2建议
|
||||
keyword: '',
|
||||
askType: '提问类型',
|
||||
askIndex: '',
|
||||
currentType: 0, // 默认value的值(0待我回复)(1已回复)(2处理完成)
|
||||
eventList: [] // 数据列表
|
||||
// temp: { '0': '投诉', '1': '建议', '2': '咨询' }
|
||||
}
|
||||
},
|
||||
// 计算
|
||||
computed: {
|
||||
tabs() {
|
||||
return [
|
||||
{name: '待我回复', value: 0},
|
||||
{name: '我已回复', value: 1},
|
||||
{name: '处理完成', value: 2}
|
||||
]
|
||||
}
|
||||
},
|
||||
// 实例创建后
|
||||
onShow() {
|
||||
this.$dict.load('leaveMessageType').then(() => {
|
||||
this.dictList = this.$dict.getDict('leaveMessageType')
|
||||
this.getList()
|
||||
})
|
||||
},
|
||||
// 方法
|
||||
methods: {
|
||||
// 点击 value的值(0待我回复)(1已回复)(2处理完成)
|
||||
handleTabClick(i) {
|
||||
this.currentType = i
|
||||
this.getList()
|
||||
},
|
||||
getList() {
|
||||
this.$http.post(`/app/appleavemessage/list`, null, {
|
||||
params: {
|
||||
...this.page,
|
||||
title: this.keyword,
|
||||
status: this.currentType,
|
||||
type: this.askIndex
|
||||
}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.page.total = res.data.total
|
||||
if (this.page.current > 1)
|
||||
this.eventList = [...this.eventList, res.data.records]
|
||||
else this.eventList = res.data.records
|
||||
}
|
||||
})
|
||||
},
|
||||
detail(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/casuallyask/casuallyaskDetail?id=${item.id}&type=${item.type}`
|
||||
// url: `/pages/casuallyask/casuallyaskDetail?id=${item.id}`
|
||||
})
|
||||
},
|
||||
// 提问类型
|
||||
bindPickerChange(e) {
|
||||
let index = e.detail.value
|
||||
this.askType = this.dictList[index].dictName
|
||||
this.askIndex = index
|
||||
this.getList()
|
||||
},
|
||||
onSearch(e) {
|
||||
this.keyword = e
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.eventList.length < this.page.total) {
|
||||
this.page.current++
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.casuallyask {
|
||||
min-height: 100%;
|
||||
background: #f5f5f5;
|
||||
padding-top: 64px;
|
||||
|
||||
.banner {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
padding: 0 30px 0 30px;
|
||||
|
||||
.picker {
|
||||
font-size: 30px;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
margin-right: 8px;
|
||||
|
||||
.u-icon-wrap {
|
||||
margin-left: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
background-color: #ffffff;
|
||||
height: 96px;
|
||||
padding-top: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.body {
|
||||
padding: 114px 0 0 0;
|
||||
|
||||
.content {
|
||||
background-color: #ffffff;
|
||||
box-sizing: border-box;
|
||||
padding: 34px 32px;
|
||||
margin: 32px 20px;
|
||||
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.typeBox {
|
||||
|
||||
}
|
||||
|
||||
.noactive {
|
||||
text-align: center;
|
||||
color: #2266ff;
|
||||
width: 70px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
border-radius: 8px;
|
||||
background-color: #e8efff;
|
||||
}
|
||||
|
||||
.active {
|
||||
text-align: center;
|
||||
color: #ff4466;
|
||||
line-height: 44px;
|
||||
width: 70px;
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
background-color: #ffebef;
|
||||
}
|
||||
|
||||
.areaName {
|
||||
display: inline-block;
|
||||
color: #333;
|
||||
font-size: 32px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #333;
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.cont {
|
||||
margin: 10px 0;
|
||||
|
||||
.name_time {
|
||||
display: inline-block;
|
||||
width: 150px;
|
||||
height: 24px;
|
||||
color: #999;
|
||||
font-size: 30px;
|
||||
// font-weight: 800;
|
||||
}
|
||||
|
||||
.text_c {
|
||||
color: #343d65;
|
||||
font-size: 30px;
|
||||
// font-weight: 800;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
442
src/pages/casuallyask/casuallyaskDetail.vue
Normal file
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<div class="casuallyaskDetail">
|
||||
<div class="detail">
|
||||
<div class="headerTitle" flex>
|
||||
<div class="temp">
|
||||
[{{ $dict.getLabel('leaveMessageType', detail.type) }}]
|
||||
</div>
|
||||
<span class="areaName">{{ detail.areaName }}</span>
|
||||
<span>{{ detail.title }}</span>
|
||||
</div>
|
||||
<div class="leaveName_leavePhone">
|
||||
<!-- 顶部圆形头像 -->
|
||||
<span class="icon">{{ detail.headPortrait || $formatName(detail.leaveName) }}</span>
|
||||
<span class="leaveName">{{ detail.leaveName }}</span>
|
||||
<span class="leavePhone" v-if="detail.leavePhone">({{ detail.leavePhone }})</span>
|
||||
</div>
|
||||
<!-- 编号 -->
|
||||
<div class="info-item">
|
||||
<span class="label">
|
||||
<u-icon name="order"></u-icon>
|
||||
</span>
|
||||
<span class="value">{{ detail.msgCode }}</span>
|
||||
</div>
|
||||
<!-- 日期 -->
|
||||
<div class="info-item">
|
||||
<span class="label">
|
||||
<u-icon name="clock"></u-icon>
|
||||
</span>
|
||||
<span class="value">{{ detail.createTime }}</span>
|
||||
</div>
|
||||
<!-- 进度-->
|
||||
<div class="info-item">
|
||||
<span class="label"><u-icon name="tags"/></span>
|
||||
<text class="status">{{
|
||||
$dict.getLabel('leaveMessageStatus', detail.status)
|
||||
}}
|
||||
</text>
|
||||
</div>
|
||||
<div class="content_text_img">
|
||||
<!-- 提问内容 -->
|
||||
<div class="content_text">
|
||||
{{ detail.content }}
|
||||
</div>
|
||||
<!-- 提问内容的图片 -->
|
||||
<div class="imageList">
|
||||
<ai-image preview :src="items.url" alt="" v-for="(items, i) in imgList" :key="i"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reply_content" v-if="detail.status == 1 || detail.status == 2">
|
||||
<div class="reply_title">
|
||||
<img src="https://cdn.cunwuyun.cn/img/dialogue.svg" alt=""/>
|
||||
<p>沟通记录</p>
|
||||
</div>
|
||||
<div class="reply_list">
|
||||
<div class="item" v-for="(item, index) in appLeaveMessageReplyList" :key="index">
|
||||
<div class="item_top">
|
||||
<div class="item_left">
|
||||
<div class="icon">
|
||||
{{ detail.headPortrait || $formatName(item.createUserName) }}
|
||||
</div>
|
||||
<div class="name fill">
|
||||
<div class="createUserName_createUnitName">
|
||||
<span v-if="item.createUserId == user.id" class="reply_font">我的回复</span>
|
||||
<template v-else>
|
||||
<span class="createUserName">{{ item.createUserName }} </span>
|
||||
<span class="createUserName" v-if="item.createUserPhone">({{ item.createUserPhone }})</span>
|
||||
</template>
|
||||
</div>
|
||||
<div flex v-if="item.createUserId != user.id">
|
||||
<!-- 回复单位 -->
|
||||
<span class="createUnitName">{{ item.createUnitName }}</span>
|
||||
<span class="reply">回复</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item_right">{{ item.createTime }}</div>
|
||||
</div>
|
||||
<!-- 回复内容 -->
|
||||
<div class="myreply_con">
|
||||
<div class="myreply_text">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<!-- 回复图片 -->
|
||||
<div class="imageList">
|
||||
<ai-image v-for="img in item.images" :key="img.id" preview :src="img.url"/>
|
||||
</div>
|
||||
<!-- -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="close_reply" v-if="detail.status == 0 || detail.status == 1">
|
||||
<div class="btn" @click="close">关闭留言</div>
|
||||
<div class="btn reply" @click="reply">回复</div>
|
||||
</div>
|
||||
<u-modal
|
||||
v-model="show"
|
||||
:content="content"
|
||||
cancel-color="#2979ff"
|
||||
title=""
|
||||
:async-close="true"
|
||||
:show-cancel-button="true"
|
||||
@confirm="handleCloseMessage"
|
||||
@cancel="show = false"/>
|
||||
<back/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import back from '../../components/AiBack'
|
||||
import {mapState} from 'vuex'
|
||||
import AiImage from "../../components/AiImage";
|
||||
import UImage from "../../uview/components/u-image/u-image";
|
||||
|
||||
export default {
|
||||
name: 'casuallyaskDetail',
|
||||
components: {UImage, AiImage, back},
|
||||
props: {},
|
||||
computed: {
|
||||
...mapState(['user'])
|
||||
},
|
||||
onLoad(options) {
|
||||
this.getDetail(options.id)
|
||||
},
|
||||
onShow() {
|
||||
this.$dict.load('leaveMessageStatus', 'leaveMessageType')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
objdie: '', // id
|
||||
appLeaveMessageReplyList: [], //遍历得到的数据
|
||||
show: false, // 默认关闭模态框
|
||||
content: '关闭留言后,双方都无法再进行回复,是否确定关闭本次留言?',
|
||||
msgCode: null,
|
||||
imgList: [],
|
||||
detail: {}
|
||||
// images:[]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDetail(id = this.detail.id) {
|
||||
this.$http.post(`/app/appleavemessage/queryDetailById?id=${id}`,).then(res => {
|
||||
if (res?.data) {
|
||||
this.detail = res.data
|
||||
this.imgList = JSON.parse(this.detail.images)
|
||||
this.appLeaveMessageReplyList = res.data.appLeaveMessageReplyList.map(item => ({
|
||||
...item,
|
||||
images: JSON.parse(item.images).map(e => ({url: e.url || e?.file?.accessUrl, id: e.file?.id}))
|
||||
}))
|
||||
}
|
||||
})
|
||||
},
|
||||
// 关闭留言
|
||||
close() {
|
||||
this.show = true
|
||||
this.getDetail()
|
||||
},
|
||||
// 确定关闭留言
|
||||
handleCloseMessage() {
|
||||
this.$http.post(`/app/appleavemessage/release?id=${this.objdie}&status=2`).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.getDetail()
|
||||
uni.navigateTo({url: `./closemsg?flag=true`})
|
||||
}
|
||||
}).catch(() => {
|
||||
uni.navigateTo({url: `./closemsg?flag=false`})
|
||||
})
|
||||
this.show = false
|
||||
},
|
||||
// 去回复
|
||||
reply() {
|
||||
uni.navigateTo({url: `./reply?msgCode=${this.detail.msgCode}`})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.casuallyaskDetail {
|
||||
padding-bottom: 80px;
|
||||
|
||||
.detail {
|
||||
padding: 15px 32px 112px 32px;
|
||||
background-color: #fff;
|
||||
font-size: 30px;
|
||||
font-weight: 400;
|
||||
color: #343D65;
|
||||
|
||||
.headerTitle {
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
line-height: 64px;
|
||||
letter-spacing: 2px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.leaveName_leavePhone {
|
||||
height: 56px;
|
||||
margin: 20px 0 36px 0;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
border-radius: 50%;
|
||||
background-color: #2266ff;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.leaveName {
|
||||
// display: block;
|
||||
margin-left: 20px;
|
||||
color: #343d65;
|
||||
}
|
||||
|
||||
// .leavePhone {
|
||||
// }
|
||||
}
|
||||
|
||||
.info-item {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 28px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.value {
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
// color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.content_text_img {
|
||||
margin-top: 64px;
|
||||
max-height: 700px;
|
||||
color: #000;
|
||||
font-size: 32px;
|
||||
// font-weight: 800;
|
||||
overflow: hidden;
|
||||
|
||||
.content_text {
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.reply_content {
|
||||
margin-top: 25px;
|
||||
|
||||
.reply_title {
|
||||
margin-top: 25px;
|
||||
width: 225px;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
p {
|
||||
display: inline-block;
|
||||
color: #333;
|
||||
// font-weight: 800;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.reply_list {
|
||||
.item {
|
||||
.item_top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
|
||||
.item_left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
border-radius: 50%;
|
||||
background-color: #2266ff;
|
||||
color: #fff;
|
||||
font-size: 23px;
|
||||
text-align: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.createUnitName {
|
||||
color: #135ab8;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.createUserName_createUnitName {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.reply_font {
|
||||
width: 120px;
|
||||
color: #333333;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.createUserName {
|
||||
margin-bottom: 5px;
|
||||
color: #135ab8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item_right {
|
||||
font-size: 26px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.myreply_con {
|
||||
width: 570px;
|
||||
background-color: #f3f6f9;
|
||||
margin: 24px 0 0 80px;
|
||||
padding: 14px;
|
||||
|
||||
.myreply_text {
|
||||
overflow: auto;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.imageList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.AiImage {
|
||||
margin: 0 12px 12px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no_yes_reply {
|
||||
.con_title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
height: 64px;
|
||||
line-height: 64px;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
line-height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #2266ff;
|
||||
color: #fff;
|
||||
font-size: 23px;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
// font-weight: 800;
|
||||
}
|
||||
|
||||
.my_reply {
|
||||
width: 370px;
|
||||
}
|
||||
|
||||
.right {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.myreply_content {
|
||||
width: 606px;
|
||||
height: 460px;
|
||||
background-color: #f3f6f9;
|
||||
margin: 24px 0 0 62px;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no_more {
|
||||
padding: 44px 0;
|
||||
color: #999;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.close_reply {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
height: 112px;
|
||||
width: 100%;
|
||||
border-top: 1px solid #ddd;
|
||||
z-index: 999;
|
||||
background: #fff;
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&.reply {
|
||||
background-color: #1365dd;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
78
src/pages/casuallyask/closemsg.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="closemsg">
|
||||
<img :src="imgSrc" alt="" />
|
||||
<text>{{ text }}</text>
|
||||
<u-button
|
||||
type="primary"
|
||||
:custom-style="{ width: '100%', borderRadius: '4px', marginTop: '48px' }"
|
||||
@click="goBack"
|
||||
>{{ btnText }}</u-button
|
||||
>
|
||||
<back></back>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import back from '../../components/AiBack'
|
||||
|
||||
export default {
|
||||
name: 'CloseMsg',
|
||||
components: { back },
|
||||
data() {
|
||||
return {
|
||||
flag: true
|
||||
}
|
||||
},
|
||||
onLoad(val) {
|
||||
if (val.flag) {
|
||||
this.flag = val.flag
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack({
|
||||
// url: `/pages/casuallyask/casuallyask`
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
text() {
|
||||
return this.flag ? '关闭留言成功!' : '关闭留言失败'
|
||||
},
|
||||
btnText() {
|
||||
return this.flag ? '确定' : '查看详情'
|
||||
},
|
||||
imgSrc() {
|
||||
return this.flag
|
||||
? this.imgOtherUrl + 'kztcg.png'
|
||||
: this.imgOtherUrl + 'kztsb.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.closemsg {
|
||||
min-height: 100%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 96px;
|
||||
|
||||
img {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: #333333;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
197
src/pages/casuallyask/reply.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="reply">
|
||||
<div class="reply_content">
|
||||
<span class="icon">*</span>
|
||||
<text class="msgfont">回复内容</text>
|
||||
<textarea :maxlength="500" placeholder="请输入内容(500字以内)" v-model="msg" class="textarea"></textarea>
|
||||
<!-- <u-input
|
||||
type="textarea"
|
||||
height="50"
|
||||
:auto-height="false"
|
||||
placeholder="请输入内容(500字以内)"
|
||||
placeholder-style="color:#999;"
|
||||
maxlength="500"
|
||||
v-model="msg"
|
||||
class="textarea"
|
||||
/> -->
|
||||
</div>
|
||||
<div class="reply_img">
|
||||
<text class="img">图片资料</text>
|
||||
<text class="img_text">(最多9张)</text>
|
||||
<div class="upload">
|
||||
<div class="info">
|
||||
<ai-uploader multiple @data="data" @change="change" :limit="9" action="/admin/file/add2"></ai-uploader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ai-back/>
|
||||
<div class="submit">
|
||||
<button class="btn" @click="btn">提交</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiUploader from '../../components/AiUploader'
|
||||
import AiBack from "../../components/AiBack";
|
||||
|
||||
export default {
|
||||
// name: '',
|
||||
// 组件
|
||||
components: {AiBack, AiUploader},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
msg: '',
|
||||
files: [],
|
||||
msgCode: '',
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.msgCode = options.msgCode
|
||||
},
|
||||
// 计算
|
||||
computed: {},
|
||||
// 监听
|
||||
watch: {},
|
||||
// 实例创建后
|
||||
onShow() {
|
||||
this.$dict.load('leaveMessageType').then(() => {
|
||||
this.dictList = this.$dict.getDict('leaveMessageType')
|
||||
})
|
||||
},
|
||||
// 实例渲染后
|
||||
mounted() {
|
||||
},
|
||||
// 方法
|
||||
methods: {
|
||||
data(e) {
|
||||
this.files.push(e)
|
||||
},
|
||||
// selectEventType(selecteds) {
|
||||
// this.eventType = selecteds?.[0]?.value
|
||||
// },
|
||||
change(e) {
|
||||
this.files = e
|
||||
},
|
||||
btn() {
|
||||
if (this.msg == '') {
|
||||
return uni.showToast({
|
||||
title: '请输入留言内容',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
this.$http
|
||||
.post(`/app/appleavemessagereply/addOrUpdate`, {
|
||||
images: JSON.stringify(this.files),
|
||||
content: this.msg,
|
||||
msgCode: this.msgCode,
|
||||
userType: '1',
|
||||
}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/casuallyask/truemsg?flag=1`,
|
||||
})
|
||||
}
|
||||
}).catch(err => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/casuallyask/truemsg?flag=0`,
|
||||
})
|
||||
this.$u.toast(err || '网络异常')
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reply {
|
||||
width: 100%;
|
||||
// height: 100%;
|
||||
.reply_content {
|
||||
height: 288px;
|
||||
padding: 0 20px 0 20px;
|
||||
background-color: #fff;
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
font-size: 42px;
|
||||
vertical-align: middle;
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.msgfont {
|
||||
// overflow: auto;
|
||||
// word-wrap: break-word;
|
||||
// word-break: break-all;
|
||||
display: inline-block;
|
||||
width: 226px;
|
||||
height: 44px;
|
||||
margin-top: 34px;
|
||||
line-height: 44px;
|
||||
font-size: 32px;
|
||||
// font-weight: 800;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
height: 190px;
|
||||
width: 700px;
|
||||
// overflow: auto;
|
||||
// word-wrap: break-word;
|
||||
// word-break: break-all;
|
||||
}
|
||||
}
|
||||
|
||||
.reply_img {
|
||||
margin-top: 20px;
|
||||
background-color: #fff;
|
||||
padding: 0 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.img {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
font-size: 32px;
|
||||
// font-weight: 800;
|
||||
}
|
||||
|
||||
.img_text {
|
||||
font-size: 32px;
|
||||
color: #999;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.upload {
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
padding: 12px 12px 12px 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
::v-deep .ai-uploader .fileList .default {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.submit {
|
||||
padding: 50px 32px;
|
||||
|
||||
.btn {
|
||||
background-color: #1365dd;
|
||||
color: #fff;
|
||||
font-size: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/pages/casuallyask/truemsg.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="closemsg">
|
||||
<img :src="imgSrc" alt=""/>
|
||||
<text>{{ text }}</text>
|
||||
<u-button
|
||||
type="primary"
|
||||
:custom-style="{ width: '100%', borderRadius: '4px', marginTop: '48px' }"
|
||||
@click="goBack"
|
||||
>{{ btnText }}
|
||||
</u-button>
|
||||
<back></back>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import back from '../../components/AiBack'
|
||||
|
||||
export default {
|
||||
name: 'CloseMsg',
|
||||
components: {back},
|
||||
data() {
|
||||
return {
|
||||
flag: true
|
||||
}
|
||||
},
|
||||
onLoad(val) {
|
||||
this.flag = val?.flag == 1
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack({
|
||||
delta: 2
|
||||
})
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
text() {
|
||||
return this.flag ? '提交成功!' : '处理失败'
|
||||
},
|
||||
btnText() {
|
||||
return this.flag ? '查看详情' : '我知道了'
|
||||
},
|
||||
imgSrc() {
|
||||
return this.flag
|
||||
? this.imgOtherUrl + 'kztcg.png'
|
||||
: this.imgOtherUrl + 'kztsb.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.closemsg {
|
||||
min-height: 100%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 96px;
|
||||
|
||||
img {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: #333333;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
src/pages/documentFlow/components/approval.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="approval">
|
||||
<div class="card">
|
||||
<header>批示意见</header>
|
||||
<textarea placeholder="请输入批示意见" v-model.trim="description" maxlength="200"></textarea>
|
||||
<u-row justify="between">
|
||||
<span @click="description=''">清空内容</span>
|
||||
<span>{{description.length || 0}}/200</span>
|
||||
</u-row>
|
||||
</div>
|
||||
<ai-back/>
|
||||
<u-button type="primary" @click="submit">提交</u-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiBack from "../../../components/AiBack";
|
||||
|
||||
export default {
|
||||
name: "approval",
|
||||
components: {AiBack},
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
description: ""
|
||||
}
|
||||
},
|
||||
onLoad(opt) {
|
||||
this.id = opt.id
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.$http.post("/app/appofficialdocumentinfo/instructionById", null, {
|
||||
params: {
|
||||
id: this.id,
|
||||
description: this.description
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$u.toast("批示成功")
|
||||
uni.navigateBack()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.approval {
|
||||
background: #F5F5F5;
|
||||
|
||||
.card {
|
||||
background-color: #FFFFFF;
|
||||
box-sizing: border-box;
|
||||
padding: 32px;
|
||||
|
||||
header {
|
||||
font-size: 32px;
|
||||
color: #333333;
|
||||
margin-bottom: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
span:first-child {
|
||||
font-size: 28px;
|
||||
color: #1365DD;
|
||||
}
|
||||
|
||||
span:last-child {
|
||||
font-size: 24px;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
.u-btn {
|
||||
width: 100%;
|
||||
height: 112px !important;
|
||||
font-size: 32px;
|
||||
color: #FFFFFF;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: #197DF0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
432
src/pages/documentFlow/components/detail.vue
Normal file
@@ -0,0 +1,432 @@
|
||||
<template>
|
||||
<div class="detail">
|
||||
<template v-if="!userSelect">
|
||||
<div class="card">
|
||||
<header>{{detail.documentName}}</header>
|
||||
<u-gap height="16"></u-gap>
|
||||
<u-row>
|
||||
<span>公文编号:</span>
|
||||
<span>{{detail.documentCode}}</span>
|
||||
</u-row>
|
||||
<u-gap height="8"></u-gap>
|
||||
<u-row>
|
||||
<span>公文类型:</span>
|
||||
<span>{{$dict.getLabel("officialDocumentName",detail.documentType)}}</span>
|
||||
</u-row>
|
||||
<u-gap height="8"></u-gap>
|
||||
<u-row>
|
||||
<span>紧急程度:</span>
|
||||
<span :style="{color:$dict.getColor('documentEmergencyLevel',detail.emergencyLevel)}">{{$dict.getLabel("documentEmergencyLevel",detail.emergencyLevel)}}</span>
|
||||
</u-row>
|
||||
<u-gap height="8"></u-gap>
|
||||
<u-row>
|
||||
<span>发文机关:</span>
|
||||
<span>{{detail.issuingUnit}}</span>
|
||||
</u-row>
|
||||
<u-gap height="8"></u-gap>
|
||||
<u-row>
|
||||
<span>发文字号:</span>
|
||||
<span>{{detail.issuingFont}}</span>
|
||||
</u-row>
|
||||
<u-gap height="8"></u-gap>
|
||||
<u-row>
|
||||
<span>签发人:</span>
|
||||
<span>{{detail.signer}}</span>
|
||||
</u-row>
|
||||
<u-gap height="16"></u-gap>
|
||||
<img v-if="detail.confidentialityLevel" :src="$cdn + tag(detail.confidentialityLevel)" alt="">
|
||||
</div>
|
||||
<div class="card" style="margin-bottom: 0;padding-top: 0">
|
||||
<div class="label">备注</div>
|
||||
<span>{{detail.remark}}</span>
|
||||
</div>
|
||||
<div class="card" style="padding-top: 0" v-if="detail.files && detail.files.length">
|
||||
<div class="label">相关附件</div>
|
||||
<div class="file" v-for="(item,index) in detail.files" :key="index" @click="preFile(item)">
|
||||
<u-row justify="between">
|
||||
<label class="left">
|
||||
<img :src="$cdn + 'common/appendix.png'" alt="">
|
||||
<span>{{item.fileName}}.{{item.postfix}}</span>
|
||||
</label>
|
||||
<span>{{(item.size/1024).toFixed(2)}}KB</span>
|
||||
</u-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="label" style="96px;">{{detail.readType ==0 ? "流转信息" : "传阅情况"}}
|
||||
<em>({{$dict.getLabel("documentStatus",detail.status)}})</em></div>
|
||||
<div class="progress">
|
||||
<div class="item" v-for="(item,index) in detail.flowUsers" :key="index">
|
||||
<div class="avatar">{{item.flowUserName && item.flowUserName.substr(-2)}}</div>
|
||||
<div class="right">
|
||||
<u-row justify="between">
|
||||
<text class="status" :style="{color:item.readStatus==1?'#FF8822':'#1365DD'}">{{$dict.getLabel(detail.readType ==1 ? "readingStatus" :
|
||||
"documentFlowStatus",detail.readType ==0 ? item.flowStatus : item.readStatus)}}
|
||||
</text>
|
||||
<text class="date">{{item.flowTime}}</text>
|
||||
</u-row>
|
||||
<u-row justify="between">
|
||||
<text class="name">{{item.flowUserName}}</text>
|
||||
</u-row>
|
||||
<u-row justify="between">
|
||||
<text class="note">{{item.description}}</text>
|
||||
</u-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer" v-if="detail.flowRight==1 && detail.readType==0">
|
||||
<div @click="handleClick(0)">批示</div>
|
||||
<div @click="handleClick(1)">流转</div>
|
||||
</div>
|
||||
|
||||
<div class="footer" v-if="detail.readType==1 && detail.flowRight==1" @click="read" style="background-color: #1365DD;color: #FFFFFF">我已阅完</div>
|
||||
|
||||
</template>
|
||||
|
||||
<AiSelectEnterprise :visible.sync="userSelect" v-if="userSelect" :multiple="false"
|
||||
@change="change"></AiSelectEnterprise>
|
||||
|
||||
<AiBack v-if="!userSelect"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiBack from "../../../components/AiBack";
|
||||
import AiSelectEnterprise from "../../../components/AiSelectEnterprise/AiSelectEnterprise";
|
||||
import {mapActions} from "vuex";
|
||||
|
||||
export default {
|
||||
name: "detail",
|
||||
components: {AiBack, AiSelectEnterprise},
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
detail: {},
|
||||
userSelect: false,
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(opt) {
|
||||
this.$dict.load("officialDocumentName", "documentEmergencyLevel", "documentStatus", "readingStatus", "documentFlowStatus")
|
||||
this.id = opt.id
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(['previewFile', 'injectJWeixin']),
|
||||
read() {
|
||||
this.$http.post("/app/appofficialdocumentinfo/readById", null, {
|
||||
params: {
|
||||
id: this.id
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$u.toast("已阅读")
|
||||
this.getDetail()
|
||||
}
|
||||
})
|
||||
},
|
||||
preFile(e) {
|
||||
if([".jpg",".png",".gif"].includes(e.postfix.toLowerCase())){
|
||||
uni.previewImage({
|
||||
current: e.url,
|
||||
urls: [e.url]
|
||||
})
|
||||
}else {
|
||||
this.previewFile({...e})
|
||||
}
|
||||
},
|
||||
change(e) {
|
||||
this.$http.post("/app/appofficialdocumentinfo/flowById", null, {
|
||||
params: {
|
||||
flowUserId: e[0].id,
|
||||
flowUserName: e[0].name,
|
||||
id: this.id,
|
||||
avatar: e[0].avatar,
|
||||
flag: 0
|
||||
}
|
||||
}).then(res => {
|
||||
if (res.code == 0) {
|
||||
this.$u.toast("流转成功")
|
||||
this.getDetail()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
tag(status) {
|
||||
return {
|
||||
"0": "common/mm.png",
|
||||
"1": "common/jm.png",
|
||||
"2": "common/tm.png"
|
||||
}[status]
|
||||
},
|
||||
getDetail() {
|
||||
this.$http.post("/app/appofficialdocumentinfo/queryDetailById", null, {
|
||||
params: {
|
||||
id: this.id,
|
||||
flag: 1
|
||||
}
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
this.detail = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
handleClick(status) {
|
||||
if (status == 0) {
|
||||
uni.navigateTo({
|
||||
url: "/pages/documentFlow/components/approval?id=" + this.id
|
||||
})
|
||||
} else {
|
||||
this.userSelect = true
|
||||
}
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.getDetail()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail {
|
||||
min-height: 100%;
|
||||
background-color: #F5F5F5;
|
||||
padding-bottom: 140px;
|
||||
position: relative;
|
||||
|
||||
.card {
|
||||
background-color: #FFFFFF;
|
||||
margin-bottom: 8px;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 32px;
|
||||
position: relative;
|
||||
|
||||
header {
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
line-height: 64px;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.u-row {
|
||||
& > div {
|
||||
background-color: #2266FF;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
& > span:first-child {
|
||||
font-size: 30px;
|
||||
color: #999999;;
|
||||
line-height: 48px;
|
||||
}
|
||||
|
||||
& > span:last-child {
|
||||
font-size: 30px;
|
||||
color: #343D65;
|
||||
margin-left: 16px;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
& > img {
|
||||
width: 190px;
|
||||
height: 190px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 74px;
|
||||
}
|
||||
|
||||
& > span {
|
||||
font-size: 32px;
|
||||
color: #333333;
|
||||
line-height: 48px;
|
||||
letter-spacing: 1px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.label {
|
||||
height: 80px;
|
||||
font-size: 32px;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
& > em {
|
||||
font-style: normal;
|
||||
font-size: 32px;
|
||||
color: #1365DD;
|
||||
}
|
||||
}
|
||||
|
||||
.file {
|
||||
height: 128px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #CCCCCC;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
margin-bottom: 32px;
|
||||
|
||||
& > .u-row {
|
||||
height: 100%;
|
||||
|
||||
.left {
|
||||
width: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > img {
|
||||
flex-shrink: 0;
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
& > span {
|
||||
font-size: 32px;
|
||||
color: #333333;
|
||||
display: inline-block;
|
||||
line-height: 44px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display:-webkit-box;
|
||||
-webkit-box-orient:vertical;
|
||||
-webkit-line-clamp:2;
|
||||
}
|
||||
}
|
||||
|
||||
& > span {
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #F3F6F9;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-top: 8px;
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
min-height: 136px;
|
||||
margin-bottom: 80px;
|
||||
|
||||
.avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background-color: #2266FF;
|
||||
font-size: 28px;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > .u-row {
|
||||
margin-left: 40px;
|
||||
|
||||
.status {
|
||||
font-size: 32px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 28px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 28px;
|
||||
color: #666666;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 28px;
|
||||
color: #343D65;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
background-color: #EEEEEE;
|
||||
position: absolute;
|
||||
left: 40px;
|
||||
top: 112px;
|
||||
}
|
||||
|
||||
&:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
height: 112px;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36px;
|
||||
|
||||
& > div {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
& > div:first-child {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
& > div:last-child {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #1365DD;
|
||||
}
|
||||
|
||||
& > label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #1365DD;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
166
src/pages/documentFlow/documentFlow.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="document-flow">
|
||||
<ai-top-fixed>
|
||||
<header class="pad">
|
||||
<u-search placeholder="请输入公文名称" v-model="documentName" @clear="documentName='',getList()" @search="getList" clearabled :show-action="false" height="64"></u-search>
|
||||
</header>
|
||||
</ai-top-fixed>
|
||||
<div class="list pad" v-if="list.length">
|
||||
<div class="card" v-for="(item,index) in list" :key="index" @click="handleClick(item)">
|
||||
<u-row>
|
||||
<em v-if="item.redStatus==0"></em>
|
||||
<span>{{item.documentName}}</span>
|
||||
</u-row>
|
||||
<u-gap height="16"></u-gap>
|
||||
<u-row>
|
||||
<label>公文类型:</label>
|
||||
<text style="color: #1365DD;">{{$dict.getLabel("officialDocumentName",item.documentType)}}</text>
|
||||
</u-row>
|
||||
<u-gap height="8"></u-gap>
|
||||
<u-row>
|
||||
<label>登记人:</label>
|
||||
<text>{{item.createUserName}}</text>
|
||||
</u-row>
|
||||
<u-gap height="8"></u-gap>
|
||||
<u-row>
|
||||
<label>登记日期:</label>
|
||||
<text>{{item.createTime}}</text>
|
||||
</u-row>
|
||||
<img :src=" $cdn + tag(item.readType)" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<AiEmpty v-else></AiEmpty>
|
||||
<u-loadmore :status="status" v-if="list.length"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiTopFixed from "../../components/AiTopFixed";
|
||||
import AiEmpty from "../../components/AiEmpty/AiEmpty";
|
||||
|
||||
export default {
|
||||
name: "documentFlow",
|
||||
components: {AiTopFixed,AiEmpty},
|
||||
data() {
|
||||
return {
|
||||
documentName: "",
|
||||
current: 1,
|
||||
list: [],
|
||||
status: "加载更多"
|
||||
}
|
||||
},
|
||||
onLoad(){
|
||||
this.$dict.load("officialDocumentName")
|
||||
},
|
||||
methods: {
|
||||
tag(status){
|
||||
return {
|
||||
"0": 'common/1ps.png',
|
||||
"1": 'common/2cy.png'
|
||||
}[status]
|
||||
},
|
||||
getList() {
|
||||
this.$http.post("/app/appofficialdocumentinfo/appList", null, {
|
||||
params:{
|
||||
documentName: this.documentName,
|
||||
size: 10,
|
||||
current: this.current
|
||||
}
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
if (this.current > 1 && this.current > res.data.pages) {
|
||||
this.status = "已经到底啦"
|
||||
}
|
||||
this.list = this.current > 1 ? [...this.list, ...res.data.records] : res.data.records
|
||||
}
|
||||
})
|
||||
},
|
||||
handleClick({id}) {
|
||||
uni.navigateTo({
|
||||
url: "/pages/documentFlow/components/detail?id=" + id
|
||||
})
|
||||
}
|
||||
},
|
||||
onShow(){
|
||||
this.getList()
|
||||
},
|
||||
onReachBottom() {
|
||||
this.current = this.current + 1;
|
||||
this.getList()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.document-flow {
|
||||
min-height: 100%;
|
||||
background: #F5F5F5;
|
||||
|
||||
::v-deep .content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
height: 112px;
|
||||
background-color: #FFFFFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin: 32px 0;
|
||||
|
||||
.card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
padding: 32px;
|
||||
position: relative;
|
||||
margin-bottom: 32px;
|
||||
|
||||
.u-row{
|
||||
flex-wrap: nowrap !important;
|
||||
}
|
||||
|
||||
em {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background-color: #FF4466;
|
||||
font-style: normal;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 30px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 30px;
|
||||
color: #343D65;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pad {
|
||||
box-sizing: border-box;
|
||||
padding: 32px 32px 0 32px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
146
src/pages/guardianship/component/areaSelector.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<section class="areaSelector">
|
||||
<ai-search-popup mode="bottom" ref="areaSelector">
|
||||
<template #btn>
|
||||
<div class="areaSelector">
|
||||
<span v-for="area in fullArea" :key="area.id" v-text="area.name"
|
||||
:class="{current:area.id==areaId}" @tap="index=area.id,getChildAreas(area.id)"/>
|
||||
</div>
|
||||
</template>
|
||||
<div class="areaSelector">
|
||||
<span v-for="area in fullArea" :key="area.id" v-text="area.name"
|
||||
:class="{current:area.id==index}"
|
||||
@click="index=area.id,getChildAreas(area.id)"/>
|
||||
</div>
|
||||
<div class="pendingItem" flex v-for="op in list" :key="op.id" @tap="handleSelect(op)">
|
||||
<div class="fill" :class="{self:index==op.id}" v-html="op.name"/>
|
||||
<u-icon name="arrow-right" color="#ddd"/>
|
||||
</div>
|
||||
</ai-search-popup>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiSearchPopup from "../../../components/AiSearchPopup";
|
||||
import AiCell from "../../../components/AiCell";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: "areaSelector",
|
||||
components: {AiCell, AiSearchPopup},
|
||||
props: {
|
||||
areaId: {default: ""}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
dataRange() {
|
||||
let rules = [10, 8, 6, 3, 0], level = 2
|
||||
rules.some((e, i) => {
|
||||
let reg = new RegExp(`0{${e}}`, 'g')
|
||||
if (reg.test(this.user.areaId)) {
|
||||
return level = i
|
||||
}
|
||||
})
|
||||
return level
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fullArea: [],
|
||||
index: "",
|
||||
list: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
areaId(v) {
|
||||
v && this.getFullArea()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFullArea() {
|
||||
let {areaId} = this
|
||||
return areaId && this.$http.post("/admin/area/getAllParentAreaId", null, {
|
||||
params: {areaId}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.fullArea = res.data.reverse().slice(this.dataRange)
|
||||
}
|
||||
})
|
||||
},
|
||||
getChildAreas(id) {
|
||||
id && this.$http.post("/admin/area/queryAreaByParentId", null, {
|
||||
params: {id}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.list = res.data
|
||||
let self = this.fullArea.find(e => e.id == this.index)
|
||||
this.list.unshift(self)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSelect(op) {
|
||||
this.$emit('select', op)
|
||||
this.$refs.areaSelector?.handleSelect()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.index = this.areaId
|
||||
this.getFullArea()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.areaSelector {
|
||||
::v-deep .AiSearchPopup {
|
||||
.areaSelector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
|
||||
&:first-of-type:before {
|
||||
content: "";
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
color: #333;
|
||||
content: "/";
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.current {
|
||||
color: #3F8DF5;
|
||||
}
|
||||
}
|
||||
|
||||
.u-drawer-content {
|
||||
position: fixed;
|
||||
|
||||
.areaSelector {
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 16px solid #f5f5f5;
|
||||
|
||||
span {
|
||||
line-height: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pendingItem {
|
||||
margin-left: 32px;
|
||||
padding-right: 32px;
|
||||
height: 104px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
||||
.self {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
73
src/pages/guardianship/component/makeCalls.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<section class="makeCalls">
|
||||
<div v-if="$slots.default" @tap="calls=true">
|
||||
<slot/>
|
||||
</div>
|
||||
<div v-else flex class="column" @tap="calls=true">
|
||||
<img :src="`${$cdn}guardianship/dh.png`"/>
|
||||
<span v-html="label"/>
|
||||
</div>
|
||||
<u-popup v-model="calls" mode="bottom">
|
||||
<div flex class="column option" v-for="item in list" @click="handleCall(item.guardianPhone)">
|
||||
{{ [item.guardianName, item.guardianPhone].join(' ') }}
|
||||
</div>
|
||||
<div class="option" @tap="calls=false">取消</div>
|
||||
</u-popup>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "makeCalls",
|
||||
props: {
|
||||
label: {default: "联系ta"},
|
||||
list: {default: () => []}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
calls: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCall(phone) {
|
||||
location.href = "tel:" + phone
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.makeCalls {
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
::v-deep span {
|
||||
margin-left: 0;
|
||||
color: #999;
|
||||
font-size: 20px;
|
||||
|
||||
p {
|
||||
color: #3D94FB;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .u-drawer {
|
||||
text-align: center;
|
||||
.uni-scroll-view-content{
|
||||
max-height: 672px;
|
||||
}
|
||||
.option {
|
||||
font-size: 32px;
|
||||
cursor: pointer;
|
||||
line-height: 112px;
|
||||
border-bottom: 1px solid #D8DDE6;
|
||||
|
||||
&:first-of-type {
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
40
src/pages/guardianship/component/openMap.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<section class="openMap">
|
||||
<div flex class="column" shrink @tap="handleOpenMap">
|
||||
<img :src="`${$cdn}guardianship/seat.png`"/>
|
||||
<span v-text="'地图/导航'"/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "openMap",
|
||||
props: {
|
||||
data: {default: () => ({})}
|
||||
},
|
||||
methods: {
|
||||
handleOpenMap() {
|
||||
let {lng, lat, gpsDesc} = this.data
|
||||
location.href = `https://uri.amap.com/marker?callnative=1&position=${[lng, lat].toString()}&name=${gpsDesc}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.openMap {
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0;
|
||||
color: #999;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
169
src/pages/guardianship/earlyWarning.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<section class="earlyWarning">
|
||||
<ai-top-fixed>
|
||||
<u-search v-model="search.name" placeholder="请输入姓名" :show-action="false"
|
||||
search-icon-color="#ccc" placeholder-color="#999"
|
||||
@change="page.current=1,getList()"/>
|
||||
<div flex>
|
||||
<ai-date class="fill" placeholder="日期选择" mode="range" @change="handleDateSearch"/>
|
||||
<ai-select class="fill" dict="intelligentGuardianshipItem2" @data="handleTypeSearch">
|
||||
<div>{{ $dict.getLabel('intelligentGuardianshipItem2', search.item) || '全部预警' }}</div>
|
||||
<i class="iconfont iconfont-iconArrow_Down"/>
|
||||
</ai-select>
|
||||
</div>
|
||||
</ai-top-fixed>
|
||||
<div class="card" v-for="row in list" :key="row.id" @tap="handleShow(row)">
|
||||
<div class="header" flex>
|
||||
<img :src="top.cdn(typeIcons[row.item])"/>
|
||||
<b v-text="row.desc"/>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<div class="start" flex>
|
||||
<span v-text="`上报时间:`"/>
|
||||
<div v-text="row.createTime"/>
|
||||
</div>
|
||||
<div class="start" flex>
|
||||
<span v-text="`上报地点:`"/>
|
||||
<div v-text="row.gpsDesc"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiTopFixed from "../../components/AiTopFixed";
|
||||
import {mapState} from "vuex";
|
||||
import AiDate from "../../components/AiDate";
|
||||
import AiSelect from "../../components/AiSelect";
|
||||
|
||||
export default {
|
||||
name: "earlyWarning",
|
||||
components: {AiSelect, AiDate, AiTopFixed},
|
||||
inject: ['top'],
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
typeIcons() {
|
||||
return {
|
||||
0: "icon4",
|
||||
1: "icon2",
|
||||
2: "icon5",
|
||||
3: "icon6",
|
||||
4: "icon3",
|
||||
5: "icon1",
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
search: {name: "", createTimeRange: ",", item: ""},
|
||||
areaId: "",
|
||||
page: {current: 1, size: 10, total: 0},
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
let {areaId} = this
|
||||
this.$http.post("/app/appintelligentguardianshipalarm/list", null, {
|
||||
params: {areaId, ...this.search, ...this.page}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
let data = res.data.records.map(e => {
|
||||
return {...e, desc: [e.name, this.$dict.getLabel('intelligentGuardianshipItem2', e.item)].join('的')}
|
||||
})
|
||||
if (this.page.current > 1) {
|
||||
this.list = [...this.list, ...data]
|
||||
} else this.list = data
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
reachBottom() {
|
||||
if (this.page.total > this.list.length) {
|
||||
this.page.current++
|
||||
this.getList()
|
||||
}
|
||||
},
|
||||
handleDateSearch(v) {
|
||||
let {startDate: start, endDate: end} = v
|
||||
start = this.$dateFormat(start)
|
||||
end = this.$dateFormat(end)
|
||||
this.search.createTimeRange = [start, end || start].toString()
|
||||
this.page.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleTypeSearch(v) {
|
||||
this.search.item = v?.[0]?.value
|
||||
this.page.current = 1
|
||||
this.getList()
|
||||
},
|
||||
handleShow(row) {
|
||||
uni.navigateTo({url: `./warningDetail?id=${row.id}`})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.areaId = JSON.parse(JSON.stringify(this.user.areaId))
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.earlyWarning {
|
||||
padding-bottom: 130px;
|
||||
background: #f5f5f5;
|
||||
|
||||
::v-deep .AiDate > div {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
::v-deep .u-drawer-content {
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
::v-deep .display {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
::v-deep .iconfont-iconArrow_Down {
|
||||
margin-left: 4px;
|
||||
font-size: 32px;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
::v-deep .card {
|
||||
margin: 32px 32px 0;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.02);
|
||||
border-radius: 8px;
|
||||
|
||||
.header {
|
||||
padding: 0 32px;
|
||||
height: 104px;
|
||||
border-bottom: 2px solid #EFEFF4;
|
||||
font-size: 36px;
|
||||
|
||||
img {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
color: #343D65;
|
||||
font-size: 30px;
|
||||
margin-bottom: 8px;
|
||||
padding: 18px 32px;
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
color: #999;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
461
src/pages/guardianship/gsLocation.vue
Normal file
@@ -0,0 +1,461 @@
|
||||
<template>
|
||||
<section class="gsLocation">
|
||||
<ai-map class="fill" :map.sync="amap" :lib.sync="mapLib"/>
|
||||
<div class="searchZone">
|
||||
<u-search v-model="search" placeholder="请输入姓名" shape="square" bg-color="#fff"
|
||||
:show-action="false" @search="getList()"/>
|
||||
<div class="searchResult" v-if="searchResult">
|
||||
<div class="item" v-for="row in list" :key="row.id" @tap="handleSelect(row)">
|
||||
<img :src="cdn(row.onlineStatus==1?'zxtx':'lxtx')"/>
|
||||
<div flex class="column fill">
|
||||
<b v-html="searchName(row.name)"/>
|
||||
<div v-text="row.gpsDesc"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<u-popup v-model="popup" mode="bottom" :mask="false">
|
||||
<div class="headerIcon" flex @touchstart="handleTouchStart" @touchmove="handleTouchmoveClose"/>
|
||||
<div class="selectedInfo">
|
||||
<div class="header" flex>
|
||||
<img :src="`${$cdn}guardianship/tx.png`" @tap="handleShowDetail(selected)"/>
|
||||
<b v-text="selected.name" @tap="handleShowDetail(selected)"/>
|
||||
<div v-if="selected.abnormalStatus==1" class="abnormal" @tap="handleShowDetail(selected)">异常</div>
|
||||
<u-icon name="arrow-right" color="#ddd" class="fill" @tap="handleShowDetail(selected)"/>
|
||||
<make-calls :list="phoneList"/>
|
||||
</div>
|
||||
<div flex class="spb wrap">
|
||||
<div class="detail" v-for="(op,i) in quotas" :key="i" flex>
|
||||
<img :src="op.icon"/>
|
||||
<div class="fill" v-text="op.label"/>
|
||||
<div :class="{abnormal:op.abnormal}" v-text="op.value"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navigation">
|
||||
<div class="content" flex>
|
||||
<div flex class="spb wrap">
|
||||
<div class="fill" v-text="selected.gpsDesc"/>
|
||||
<span>最后更新:{{ selected.lastUpdateTime }}</span>
|
||||
<div class="battery" flex>
|
||||
<img :src="batteryIcon"/>
|
||||
<div v-text="`剩余${selected.electricQuantity}%`"/>
|
||||
</div>
|
||||
</div>
|
||||
<open-map :data="selected"/>
|
||||
</div>
|
||||
</div>
|
||||
</u-popup>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiSearchPopup from "../../components/AiSearchPopup";
|
||||
import {mapState} from "vuex";
|
||||
import UPopup from "../../uview/components/u-popup/u-popup";
|
||||
import MakeCalls from "./component/makeCalls";
|
||||
import OpenMap from "./component/openMap";
|
||||
import AiMap from "../../components/AiMap";
|
||||
|
||||
export default {
|
||||
name: "gsLocation",
|
||||
components: {AiMap, OpenMap, MakeCalls, UPopup, AiSearchPopup},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
markers() {
|
||||
return this.list.filter(e => e.lng).map(e => {
|
||||
let abnormal = 'offline'
|
||||
if (e.onlineStatus == 1) {
|
||||
switch (e.abnormalStatus) {
|
||||
case '1':
|
||||
abnormal = 'warning';
|
||||
break;
|
||||
case '2':
|
||||
abnormal = 'abnormal';
|
||||
break;
|
||||
default:
|
||||
abnormal = ''
|
||||
}
|
||||
}
|
||||
return new this.mapLib.Marker({
|
||||
position: new this.mapLib.LngLat(e.lng, e.lat),
|
||||
anchor: 'bottom-center',
|
||||
content: `<div class="marker ${abnormal}">${e.name}</div>`,
|
||||
extData: e,
|
||||
topWhenClick: true
|
||||
}).on('click', () => {
|
||||
this.handleSelect(e)
|
||||
})
|
||||
})
|
||||
},
|
||||
quotas() {
|
||||
let quota = [
|
||||
{key: "0", icon: "1"},
|
||||
{key: "1", icon: "2"},
|
||||
{key: "2", icon: "3"},
|
||||
{key: "3", icon: "4"},
|
||||
]
|
||||
return quota.map(e => {
|
||||
let item = this.detail.find(d => d.item == e.key)
|
||||
let label = this.$dict.getLabel('intelligentGuardianshipItem', e.key)
|
||||
return {
|
||||
label, icon: this.cdn(e.icon),
|
||||
value: item?.itemValue || "-",
|
||||
abnormal: item?.abnormalStatus == 1
|
||||
}
|
||||
})
|
||||
},
|
||||
batteryIcon() {
|
||||
return this.cdn(this.selected.electricQuantity == 100 ? 'dcm' : 'dcq')
|
||||
},
|
||||
phoneList() {
|
||||
let {name: guardianName, phone: guardianPhone} = this.selected
|
||||
return [{guardianName, guardianPhone}, ...(this.selected.guardians || [])]
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mapLib: null,
|
||||
amap: null,
|
||||
search: "",
|
||||
selected: {},
|
||||
list: [],
|
||||
popup: false,//被监护人信息弹窗
|
||||
detail: [],
|
||||
moveDistance: 0,
|
||||
searchResult: false //搜索下拉弹窗
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
amap(v) {
|
||||
v && this.getList().then(() => {
|
||||
this.amap?.add(this.markers)
|
||||
this.getMapArea()
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cdn(icon) {
|
||||
return `${this.$cdn}guardianship/${icon}.png`
|
||||
},
|
||||
getMapArea() {
|
||||
if (this.mapLib) {
|
||||
new this.mapLib.DistrictSearch({
|
||||
subdistrict: 0, //获取边界不需要返回下级行政区
|
||||
extensions: 'all', //返回行政区边界坐标组等具体信息
|
||||
level: 'district' //查询行政级别为 市
|
||||
}).search(this.user.areaId.substring(0, 6), (status, result) => {
|
||||
let bounds = result?.districtList?.[0]?.boundaries;
|
||||
let polygons = []
|
||||
bounds?.forEach(path => polygons.push(new this.mapLib.Polygon({
|
||||
strokeWeight: 1,
|
||||
path,
|
||||
strokeStyle: 'dashed',
|
||||
fillOpacity: 0.1,
|
||||
fillColor: '#80d8ff',
|
||||
strokeColor: '#0091ea'
|
||||
})))
|
||||
this.amap.add(polygons)
|
||||
this.amap.setFitView();//视口自适应
|
||||
})
|
||||
}
|
||||
},
|
||||
getList() {
|
||||
this.searchResult = !!this.search
|
||||
return this.$http.post("/app/appintelligentguardianshipdevice/list", null, {
|
||||
params: {name: this.search, size: 999, areaId: this.user.areaId}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.list = res.data.records
|
||||
}
|
||||
})
|
||||
},
|
||||
getDetail(deviceId) {
|
||||
this.$http.post("/app/appintelligentguardianshipdevice/queryMonitorList", null, {
|
||||
params: {type: 1, deviceId}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.detail = res.data.records
|
||||
}
|
||||
})
|
||||
this.$http.post("/app/appintelligentguardianshipdevice/queryDetailById", null, {
|
||||
params: {id: deviceId}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.selected = {...this.selected, ...res.data}
|
||||
}
|
||||
})
|
||||
},
|
||||
searchName(name) {
|
||||
return name?.replace(this.search, `<span>${this.search}</span>`)
|
||||
},
|
||||
handleSelect(e) {
|
||||
this.amap.setCenter(new this.mapLib.LngLat(e.lng, e.lat))
|
||||
this.selected = e
|
||||
this.popup = true
|
||||
this.searchResult = false
|
||||
this.getDetail(e.id)
|
||||
},
|
||||
handleShowDetail(user) {
|
||||
uni.navigateTo({url: `./userDetail?id=${user.id}`})
|
||||
},
|
||||
handleTouchmoveClose(e) {
|
||||
if (e.touches?.[0]?.clientY > this.moveDistance + 10) {
|
||||
this.popup = false
|
||||
}
|
||||
},
|
||||
handleTouchStart(e) {
|
||||
this.moveDistance = e.touches?.[0]?.clientY
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.gsLocation {
|
||||
height: 100%;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 112px;
|
||||
border-top: 1px solid #d8dde6;
|
||||
|
||||
&:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.headerIcon {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
justify-content: center;
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 64px;
|
||||
height: 10px;
|
||||
background: #CCCCCC;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .makeCalls {
|
||||
.option:last-of-type {
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .selectedInfo {
|
||||
width: 100%;
|
||||
border-radius: 20px 20px 0 0;
|
||||
padding: 0 32px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
.header {
|
||||
margin-bottom: 40px;
|
||||
font-size: 40px;
|
||||
|
||||
& > img {
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.abnormal {
|
||||
color: #FF4466;
|
||||
font-size: 24px;
|
||||
padding: 12px;
|
||||
background: rgba(#EC4461, .1);
|
||||
border-radius: 8px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0;
|
||||
color: #999;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detail {
|
||||
width: 318px;
|
||||
height: 84px;
|
||||
background: #F4F5F6;
|
||||
border-radius: 8px;
|
||||
padding: 0 24px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 36px;
|
||||
|
||||
img {
|
||||
margin-right: 16px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.abnormal {
|
||||
color: #FF4466;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navigation {
|
||||
padding-bottom: 100px;
|
||||
|
||||
.content {
|
||||
padding: 32px 40px;
|
||||
font-size: 26px;
|
||||
|
||||
& > .spb {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.fill {
|
||||
min-width: 100%;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0;
|
||||
color: #999;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.battery > img {
|
||||
margin-right: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #F4F5F6;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .searchZone {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: 24px 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.u-search {
|
||||
box-shadow: 0 4px 8px 0 rgba(192, 185, 185, 0.5);
|
||||
}
|
||||
|
||||
.searchResult {
|
||||
margin-top: 16px;
|
||||
padding: 0 28px;
|
||||
background: #fff;
|
||||
|
||||
.item {
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
line-height: 36px;
|
||||
padding: 24px 0;
|
||||
border-bottom: 3px solid #DEDFE1;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
& > .fill {
|
||||
align-items: unset;
|
||||
|
||||
b > span {
|
||||
color: #1365DD;
|
||||
}
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .marker {
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 32px;
|
||||
height: 56px;
|
||||
white-space: nowrap;
|
||||
background: #5088FF;
|
||||
border-color: #5088FF;
|
||||
border-radius: 52px;
|
||||
position: relative;
|
||||
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: " ";
|
||||
bottom: -12px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 12px solid transparent;
|
||||
border-bottom: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
border-top-color: inherit;
|
||||
}
|
||||
|
||||
&.offline {
|
||||
background: #C4CAD4;
|
||||
border-color: #C4CAD4;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background: #FFAA44;
|
||||
border-color: #FFAA44;
|
||||
}
|
||||
|
||||
&.abnormal {
|
||||
background: #F46159;
|
||||
border-color: #F46159;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
bottom: -40px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background-color: #F46159;
|
||||
transform: translate(-50%, -50%);
|
||||
animation: mapWarn 1s ease-out 0s infinite;
|
||||
content: " ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AiMap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
src/pages/guardianship/guardianship.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<section class="guardianship">
|
||||
<component ref="currentTab" :is="currentTab.comp"/>
|
||||
<ai-tabbar :active.sync="active" :list="bottomBar"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiLoading from "../../components/AiLoading";
|
||||
import GsLocation from "./gsLocation";
|
||||
import AiTabbar from "../../components/AiTabbar";
|
||||
import WardList from "./wardList";
|
||||
import EarlyWarning from "./earlyWarning";
|
||||
|
||||
export default {
|
||||
name: "guardianship",
|
||||
components: {AiTabbar, AiLoading},
|
||||
provide() {
|
||||
return {
|
||||
top: this
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
bottomBar() {
|
||||
return [
|
||||
{text: "定位", iconPath: "bardwn", selectedIconPath: "bardwh", comp: GsLocation},
|
||||
{text: "人员", iconPath: "barryn", selectedIconPath: "barryh", comp: WardList},
|
||||
{text: "预警", iconPath: "baryjn", selectedIconPath: "baryjh", comp: EarlyWarning},
|
||||
].map(e => ({
|
||||
...e,
|
||||
iconPath: this.cdn(e.iconPath),
|
||||
selectedIconPath: this.cdn(e.selectedIconPath)
|
||||
}))
|
||||
},
|
||||
currentTab() {
|
||||
return this.bottomBar?.[this.active] || {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cdn(icon) {
|
||||
return `${this.$cdn}guardianship/${icon}.png`
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$dict.load("intelligentGuardianshipItem", 'intelligentGuardianshipItem2', 'intelligentGuardianshipAbnormalStatus')
|
||||
},
|
||||
onReachBottom() {
|
||||
if (typeof this.$refs?.currentTab?.reachBottom == 'function') this.$refs?.currentTab.reachBottom()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.guardianship {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
75
src/pages/guardianship/historyList.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<section class="historyList">
|
||||
<div flex>
|
||||
<b class="header" v-text="itemLabel"/>
|
||||
</div>
|
||||
<div v-for="row in list" :key="row.id" flex class="spb row">
|
||||
<div :class="{abnormal:row.abnormalStatus==1}" v-text="row.itemValue"/>
|
||||
<span v-text="row.sampleTime"/>
|
||||
</div>
|
||||
<ai-back/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiBack from "../../components/AiBack";
|
||||
export default {
|
||||
name: "historyList",
|
||||
components: {AiBack},
|
||||
computed: {
|
||||
itemLabel() {
|
||||
return '历史'+this.$dict.getLabel('intelligentGuardianshipItem', this.$route.query.type)+`(${this.$dict.getLabel('intelligentGuardianshipItemUnit',this.$route.query.type)})`
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getHistory() {
|
||||
let {type: item, id: deviceId} = this.$route.query
|
||||
this.$http.post("/app/appintelligentguardianshipdevice/queryMonitorList", null, {
|
||||
params: {deviceId, size: 999, item}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.list = res.data.records
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$dict.load("intelligentGuardianshipItem",'intelligentGuardianshipItemUnit')
|
||||
this.getHistory()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.historyList {
|
||||
font-size: 30px;
|
||||
|
||||
& > div {
|
||||
padding: 0 32px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
height: 96px;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.header {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.row {
|
||||
color: #5AAD6A;
|
||||
|
||||
.abnormal {
|
||||
color: #CD413A;
|
||||
}
|
||||
|
||||
& > span {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
279
src/pages/guardianship/userDetail.vue
Normal file
@@ -0,0 +1,279 @@
|
||||
<template>
|
||||
<section class="userDetail">
|
||||
<div class="selectedInfo">
|
||||
<div class="header" flex>
|
||||
<img :src="`${$cdn}guardianship/tx.png`"/>
|
||||
<b v-text="detail.name"/>
|
||||
<div v-if="detail.abnormalStatus==1" class="abnormal">异常</div>
|
||||
<div class="fill"/>
|
||||
<make-calls :list="phoneList" :label="`<p>拨打电话</p>`"/>
|
||||
</div>
|
||||
<div class="content">
|
||||
<ai-cell label="所属地区">{{ detail.areaName }}</ai-cell>
|
||||
<ai-cell label="联系电话">{{ detail.phone }}</ai-cell>
|
||||
<ai-cell label="性别">{{ $dict.getLabel('sex', detail.sex) }}</ai-cell>
|
||||
<ai-cell label="年龄">{{ $calcAge(detail.idNumber) }}</ai-cell>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div flex class="spb header">
|
||||
<b v-text="`设备状况`"/>
|
||||
<span class="onlineStatus" v-html="detail.onlineStatus==1?'设备在线':'<p>设备离线</p>'"/>
|
||||
</div>
|
||||
<div flex class="spb wrap quotas">
|
||||
<div class="quota" v-for="(op,i) in quotas" :key="i" flex @tap="handleShowHistory(op)">
|
||||
<img :src="op.icon"/>
|
||||
<div class="fill" v-text="op.label"/>
|
||||
<div :class="{abnormal:op.abnormal}" v-text="op.value"/>
|
||||
<u-icon name="arrow-right" color="#ddd"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navigation">
|
||||
<div class="content spb" flex>
|
||||
<div flex class="spb wrap">
|
||||
<div class="fill" v-text="detail.gpsDesc"/>
|
||||
<span>最后更新:{{ detail.lastUpdateTime }}</span>
|
||||
<div class="battery" flex>
|
||||
<img :src="batteryIcon"/>
|
||||
<div v-text="`剩余${detail.electricQuantity||0}%`"/>
|
||||
</div>
|
||||
</div>
|
||||
<open-map :data="detail"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div flex class="spb header">
|
||||
<b v-text="`监护人信息`"/>
|
||||
</div>
|
||||
<div flex class="spb guardian" v-for="row in detail.guardians" :key="row.id">
|
||||
<span v-text="row.guardianName"/>
|
||||
<div v-text="row.guardianPhone"/>
|
||||
</div>
|
||||
</div>
|
||||
<ai-back/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MakeCalls from "./component/makeCalls";
|
||||
import OpenMap from "./component/openMap";
|
||||
import AiCell from "../../components/AiCell";
|
||||
import AiBack from "../../components/AiBack";
|
||||
|
||||
export default {
|
||||
name: "userDetail",
|
||||
components: {AiBack, AiCell, OpenMap, MakeCalls},
|
||||
computed: {
|
||||
batteryIcon() {
|
||||
return this.cdn(this.detail.electricQuantity == 100 ? 'dcm' : 'dcq')
|
||||
},
|
||||
quotas() {
|
||||
let quota = [
|
||||
{key: "0", icon: "1"},
|
||||
{key: "1", icon: "2"},
|
||||
{key: "2", icon: "3"},
|
||||
{key: "3", icon: "4"},
|
||||
]
|
||||
return quota.map(e => {
|
||||
let item = this.detail.quota?.find(d => d.item == e.key)
|
||||
let label = this.$dict.getLabel('intelligentGuardianshipItem', e.key)
|
||||
return {
|
||||
label, icon: this.cdn(e.icon), type: e.key,
|
||||
value: item?.itemValue || "-",
|
||||
abnormal: item?.abnormalStatus == 1
|
||||
}
|
||||
})
|
||||
},
|
||||
phoneList() {
|
||||
let {name: guardianName, phone: guardianPhone} = this.detail
|
||||
return [{guardianName, guardianPhone}, ...this.detail.guardians]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: {
|
||||
guardians: []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cdn(icon) {
|
||||
return `${this.$cdn}guardianship/${icon}.png`
|
||||
},
|
||||
getDetail(deviceId) {
|
||||
this.$http.post("/app/appintelligentguardianshipdevice/queryMonitorList", null, {
|
||||
params: {type: 1, deviceId}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.$set(this.detail, 'quota', res.data.records)
|
||||
}
|
||||
})
|
||||
this.$http.post("/app/appintelligentguardianshipdevice/queryDetailById", null, {
|
||||
params: {id: deviceId}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.detail = {...this.detail, ...res.data}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleShowHistory(item) {
|
||||
uni.navigateTo({url: `./historyList?type=${item.type}&id=${this.$route.query.id}`})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$dict.load("intelligentGuardianshipItem", 'sex')
|
||||
this.getDetail(this.$route.query.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.userDetail {
|
||||
padding-bottom: 60px;
|
||||
|
||||
& > * {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
font-size: 32px;
|
||||
box-shadow: 0 1px 1px 0 rgba(221, 221, 221, 1);
|
||||
|
||||
.header {
|
||||
height: 96px;
|
||||
background: #FFFFFF;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
& > .spb {
|
||||
padding: 0 32px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .selectedInfo {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
.header {
|
||||
height: 136px;
|
||||
font-size: 40px;
|
||||
padding: 0 32px;
|
||||
border-bottom: 1px solid #D8DDE6;
|
||||
|
||||
& > img {
|
||||
width: 82px;
|
||||
height: 82px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.abnormal {
|
||||
color: #FF4466;
|
||||
font-size: 24px;
|
||||
padding: 12px;
|
||||
background: rgba(#EC4461, .1);
|
||||
border-radius: 8px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0;
|
||||
color: #999;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
& > .content {
|
||||
padding: 32px;
|
||||
font-size: 30px;
|
||||
|
||||
::v-deep .AiCell {
|
||||
padding: 0;
|
||||
min-height: 58px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.quotas {
|
||||
margin-top: 24px;
|
||||
|
||||
.quota {
|
||||
cursor: pointer;
|
||||
width: 318px;
|
||||
height: 84px;
|
||||
background: #F4F5F6;
|
||||
border-radius: 8px;
|
||||
padding: 0 8px 0 24px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 36px;
|
||||
font-size: 28px;
|
||||
|
||||
img {
|
||||
margin-right: 16px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.abnormal {
|
||||
color: #FF4466;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.navigation {
|
||||
.content {
|
||||
padding: 10px 40px 32px;
|
||||
font-size: 26px;
|
||||
|
||||
& > .spb {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.fill {
|
||||
min-width: 100%;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0;
|
||||
color: #999;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.battery > img {
|
||||
margin-left: 32px;
|
||||
margin-right: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .onlineStatus {
|
||||
font-size: 30px;
|
||||
color: #5AAD6A;
|
||||
|
||||
p {
|
||||
color: #F5A319;
|
||||
}
|
||||
}
|
||||
|
||||
.guardian {
|
||||
height: 96px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
font-size: 28px;
|
||||
color: #222;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
106
src/pages/guardianship/wardList.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<section class="wardList">
|
||||
<ai-top-fixed>
|
||||
<u-search v-model="search" placeholder="请输入姓名" :show-action="false"
|
||||
search-icon-color="#ccc" placeholder-color="#999"
|
||||
@change="page.current=1,getUser()"/>
|
||||
<area-selector :areaId="areaId" @select="handleSelectArea"/>
|
||||
</ai-top-fixed>
|
||||
<div class="userList">
|
||||
<div v-for="row in list" :key="row.id" flex class="row" @tap="handleShowDetail(row)">
|
||||
<img :src="top.cdn(row.onlineStatus==1?'zxtx':'lxtx')"/>
|
||||
<b class="fill" v-text="row.name"/>
|
||||
<div class="status" :style="{color:$dict.getColor('intelligentGuardianshipAbnormalStatus',row.abnormalStatus)}"
|
||||
v-text="$dict.getLabel('intelligentGuardianshipAbnormalStatus',row.abnormalStatus)"/>
|
||||
<u-icon name="arrow-right" color="#ddd"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiTopFixed from "../../components/AiTopFixed";
|
||||
import {mapState} from "vuex";
|
||||
import AreaSelector from "./component/areaSelector";
|
||||
|
||||
export default {
|
||||
name: "wardList",
|
||||
components: {AreaSelector, AiTopFixed},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
},
|
||||
inject: ['top'],
|
||||
data() {
|
||||
return {
|
||||
search: "",
|
||||
areaId: "",
|
||||
page: {current: 1, size: 20, total: 0},
|
||||
list: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getUser() {
|
||||
let {areaId, search: name} = this
|
||||
this.$http.post("/app/appintelligentguardianshipdevice/list", null, {
|
||||
params: {areaId, name, ...this.page}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
let data = res.data.records.reverse()
|
||||
if (this.page.current > 1) {
|
||||
this.list = [...this.list, ...data]
|
||||
} else this.list = data
|
||||
this.page.total = res.data.total
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSelectArea({id}) {
|
||||
this.areaId = id
|
||||
this.getUser()
|
||||
},
|
||||
reachBottom() {
|
||||
if (this.page.total > this.list.length) {
|
||||
this.page.current++
|
||||
this.getUser()
|
||||
}
|
||||
},
|
||||
handleShowDetail(user) {
|
||||
uni.navigateTo({url: `./userDetail?id=${user.id}`})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.areaId = JSON.parse(JSON.stringify(this.user.areaId))
|
||||
this.getUser()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wardList {
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 130px;
|
||||
font-size: 30px;
|
||||
|
||||
::v-deep .userList {
|
||||
margin-top: 16px;
|
||||
background: #fff;
|
||||
|
||||
.row {
|
||||
font-size: 36px;
|
||||
margin-left: 32px;
|
||||
padding-right: 32px;
|
||||
height: 104px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
||||
img {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin-right: 38px;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
185
src/pages/guardianship/warningDetail.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<section class="warningDetail">
|
||||
<div flex class="header">
|
||||
<b v-text="detail.name"/>
|
||||
<div>
|
||||
{{ $dict.getLabel('intelligentGuardianshipItem2', detail.item) }}
|
||||
{{ detail.itemValue }}
|
||||
</div>
|
||||
</div>
|
||||
<ai-map class="fill" :map.sync="amap" :lib.sync="mapLib"/>
|
||||
<div class="navigation">
|
||||
<div class="content spb" flex>
|
||||
<div flex class="spb wrap">
|
||||
<div class="fill" v-text="detail.gpsDesc"/>
|
||||
<span>最后更新:{{ detail.createTime }}</span>
|
||||
</div>
|
||||
<open-map :data="detail"/>
|
||||
</div>
|
||||
</div>
|
||||
<make-calls :list="phoneList">
|
||||
<div class="bottomBtn">拨打电话</div>
|
||||
</make-calls>
|
||||
<ai-back/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OpenMap from "./component/openMap";
|
||||
import MakeCalls from "./component/makeCalls";
|
||||
import AiBack from "../../components/AiBack";
|
||||
import AiMap from "../../components/AiMap";
|
||||
|
||||
export default {
|
||||
name: "warningDetail",
|
||||
components: {AiMap, AiBack, MakeCalls, OpenMap},
|
||||
computed: {
|
||||
phoneList() {
|
||||
let {name: guardianName, phone: guardianPhone} = this.detail
|
||||
return [{guardianName, guardianPhone}, ...this.detail.guardians]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
detail: {guardians: []},
|
||||
mapLib: null,
|
||||
amap: null,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDetail(id) {
|
||||
return this.$http.post("/app/appintelligentguardianshipalarm/queryDetailById", null, {
|
||||
params: {id},
|
||||
withoutToken: true
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.detail = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
initMap() {
|
||||
if (this.mapLib) {
|
||||
let pos = new this.mapLib.LngLat(this.detail.lng, this.detail.lat)
|
||||
this.amap.add(new this.mapLib.Marker({
|
||||
position: pos,
|
||||
anchor: 'bottom-center',
|
||||
content: `<div class="marker">${this.detail.name}</div>`,
|
||||
}))
|
||||
this.amap.setFitView()
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
mapLib(v) {
|
||||
this.detail.id && v && this.initMap()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$dict.load("intelligentGuardianshipItem",'intelligentGuardianshipItem2', 'sex')
|
||||
},
|
||||
mounted() {
|
||||
this.getDetail(this.$route.query.id).then(() => this.initMap())
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.warningDetail {
|
||||
padding: 48px 48px 112px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
|
||||
.header {
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
justify-content: center;
|
||||
margin-bottom: 48px;
|
||||
|
||||
& > div {
|
||||
color: #EC4461;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation {
|
||||
.content {
|
||||
padding: 10px 0 32px;
|
||||
font-size: 28px;
|
||||
color: #555;
|
||||
|
||||
& > .spb {
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.fill {
|
||||
min-width: 100%;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 0;
|
||||
color: #999;
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottomBtn {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background: #1365DD;
|
||||
line-height: 112px;
|
||||
}
|
||||
|
||||
.AiMap {
|
||||
margin-bottom: 72px;
|
||||
}
|
||||
|
||||
|
||||
::v-deep .marker {
|
||||
border-radius: 52px;
|
||||
background: #F46159;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
padding: 4px 16px;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: calc(50% - 30px);
|
||||
bottom: -34px;
|
||||
z-index: -1;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background-color: #F46159;
|
||||
animation: mapWarn 1s ease-out 0s infinite;
|
||||
content: " ";
|
||||
}
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: " ";
|
||||
bottom: -8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 8px solid transparent;
|
||||
border-bottom: none;
|
||||
border-top-color: #F46159;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
184
src/pages/interview/detail.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="interviewDetail">
|
||||
<template v-if="isEdit">
|
||||
<u-form ref="interviewForm" label-position="top" :rules="rules" :model="form">
|
||||
<u-form-item label="调查走访事项" prop="title" required>
|
||||
<u-input v-model="form.title" placeholder="请输入,最多30字" maxlength="30"/>
|
||||
</u-form-item>
|
||||
<u-form-item label="调查走访内容" prop="content">
|
||||
<ai-textarea v-model="form.content" placeholder="请输入,最多500字" :maxlength="500"/>
|
||||
</u-form-item>
|
||||
<u-form-item label="图片(最多9张)">
|
||||
<ai-uploader multiple :limit="9" :def.sync="form.fileList" action="/admin/file/add2"/>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<div bottom>
|
||||
<u-button type="primary" @tap="submitForm">保存</u-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="headerPane">
|
||||
<b>{{ form.title }}</b>
|
||||
<div>记录时间:{{ form.createTime }}</div>
|
||||
</div>
|
||||
<div class="contentPane">
|
||||
<div v-html="form.content"/>
|
||||
<div flex class="wrap">
|
||||
<ai-image v-for="(op,i) in form.fileList" :src="op.accessUrl" preview :key="i"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ai-back/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UButton from "../../uview/components/u-button/u-button";
|
||||
import AiUploader from "../../components/AiUploader";
|
||||
import UInput from "../../uview/components/u-input/u-input";
|
||||
import AiImage from "../../components/AiImage";
|
||||
import AiTextarea from "../../components/AiTextarea";
|
||||
import UFormItem from "../../uview/components/u-form-item/u-form-item";
|
||||
import AiBack from "../../components/AiBack";
|
||||
|
||||
export default {
|
||||
name: 'interviewDetail',
|
||||
components: {AiBack, UFormItem, AiTextarea, AiImage, UInput, AiUploader, UButton},
|
||||
computed: {
|
||||
isEdit() {
|
||||
let flag = this.$route.query?.detail != 1
|
||||
!flag && uni.setNavigationBarTitle({title: "走访详情"})
|
||||
return flag
|
||||
},
|
||||
rules() {
|
||||
return {
|
||||
title: [{required: true, message: '请输入 调查走访事项'}],
|
||||
// content: [{required: true, message: '请输入 调查走访内容'}],
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
fileList: []
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.searchDetail();
|
||||
},
|
||||
methods: {
|
||||
submitForm() {
|
||||
this.$refs.interviewForm?.validate(v => {
|
||||
if (v) {
|
||||
this.$http.post(`/app/appinterview/add-xcx`, {
|
||||
...this.form
|
||||
}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.$u.toast("提交成功!")
|
||||
uni.navigateBack()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
searchDetail() {
|
||||
let {id} = this.$route.query
|
||||
id && this.$http.post(`/app/appinterview/queryDetailById`, null, {
|
||||
params: {id}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.form = {...res.data};
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.interviewDetail {
|
||||
background: #F3F6F9;
|
||||
min-height: 100%;
|
||||
|
||||
.u-form {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: #f3f6f9;
|
||||
position: relative;
|
||||
padding: 0 0 188px;
|
||||
box-sizing: border-box;
|
||||
font-size: 30px;
|
||||
|
||||
::v-deep textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .u-form-item {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.u-form-item--left__content__label {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
div[flex] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div[bottom] {
|
||||
z-index: 99;
|
||||
padding: 0;
|
||||
height: 112px;
|
||||
|
||||
.u-btn {
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .headerPane {
|
||||
width: 100%;
|
||||
background: #3975C6;
|
||||
color: #fff;
|
||||
padding: 24px 32px 32px;
|
||||
box-sizing: border-box;
|
||||
font-size: 28px;
|
||||
|
||||
b {
|
||||
display: block;
|
||||
font-size: 40px;
|
||||
line-height: 64px;
|
||||
letter-spacing: 2px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .contentPane {
|
||||
padding: 32px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
line-height: 56px;
|
||||
|
||||
.wrap {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.AiImage {
|
||||
width: 31%;
|
||||
margin-bottom: 16px;
|
||||
margin-right: 16px;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 218px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
224
src/pages/interview/interview.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="interview">
|
||||
<ai-top-fixed>
|
||||
<div flex>
|
||||
<ai-date placeholder="日期选择" mode="range" @change="handleDateSearch"/>
|
||||
<u-search placeholder="请输入标题" :show-action="false" v-model="search.title" @search="current=1,getList()"/>
|
||||
</div>
|
||||
</ai-top-fixed>
|
||||
<template v-if="list.length>0">
|
||||
<ai-card v-for="(e,index) in list" :key="index" @click.native="goDetail(e.id,1)">
|
||||
<template #custom>
|
||||
<div flex>
|
||||
<b class="fill">{{ e.title }}</b>
|
||||
</div>
|
||||
<div flex v-if="!!e.fileList" class="wrap">
|
||||
<ai-image v-for="(op,i) in e.fileList.slice(0,3)" :src="op.accessUrl" preview :key="i"/>
|
||||
</div>
|
||||
<div class="bottom">{{ e.createTime }}</div>
|
||||
</template>
|
||||
<template #menu>
|
||||
<div class="menu" @tap.stop="goDetail(e.id)">编辑</div>
|
||||
<div class="menu" @tap.stop="handleDelete(e.id)">删除</div>
|
||||
</template>
|
||||
</ai-card>
|
||||
<u-loadmore :status="loadmore" color="#999" font-size="24"
|
||||
margin-top="32" margin-bottom="80"/>
|
||||
</template>
|
||||
<div class="no-message" v-else>
|
||||
<image src="https://cdn.cunwuyun.cn/wxAdmin/img/message.png"/>
|
||||
<p>您还未添加过入户调查走访<br>点击<b>新增按钮</b>试试吧~</p>
|
||||
</div>
|
||||
<ai-fixed-btn>
|
||||
<div class="addBtn iconfont iconfont-iconfangda" @tap="gotoAdd()"/>
|
||||
</ai-fixed-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiSelect from "../../components/AiSelect";
|
||||
import AiTopFixed from "../../components/AiTopFixed";
|
||||
import AiCard from "../../components/AiCard";
|
||||
import AiImage from "../../components/AiImage";
|
||||
import AiDate from "../../components/AiDate";
|
||||
import AiFixedBtn from "../../components/AiFixedBtn";
|
||||
|
||||
export default {
|
||||
name: "interview",
|
||||
components: {AiFixedBtn, AiDate, AiImage, AiCard, AiTopFixed, AiSelect},
|
||||
data() {
|
||||
return {
|
||||
search: {title: ""},
|
||||
list: [],
|
||||
current: 1,
|
||||
pages: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loadmore() {
|
||||
return this.pages <= this.current ? 'loading ' : 'nomore'
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.current = 1;
|
||||
this.getList()
|
||||
},
|
||||
onReachBottom() {
|
||||
this.current++;
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
getList() {
|
||||
this.$http.post('/app/appinterview/list-xcx', null, {
|
||||
params: {
|
||||
current: this.current,
|
||||
size: 10,
|
||||
...this.search
|
||||
}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.list = this.current > 1 ? [...this.list, ...res.data.records] : res.data.records
|
||||
this.pages = res.data.pages
|
||||
}
|
||||
})
|
||||
},
|
||||
goDetail(id, readonly) {
|
||||
let url = `./detail?id=${id}`
|
||||
readonly && (url += "&detail=1")
|
||||
uni.navigateTo({url})
|
||||
},
|
||||
gotoAdd() {
|
||||
uni.navigateTo({url: `./detail`})
|
||||
},
|
||||
handleDelete(ids) {
|
||||
this.$confirm("是否要删除该调查走访").then(() => {
|
||||
this.$http.post("/app/appinterview/delete", null, {
|
||||
params: {ids}
|
||||
}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.$u.toast("删除成功!")
|
||||
this.getList()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handleDateSearch(v) {
|
||||
this.search.startTime = v.startDate
|
||||
this.search.endTime = v.endDate || v.startDate
|
||||
this.current = 1
|
||||
this.getList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.interview {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
|
||||
.no-message {
|
||||
margin-top: 140px;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-size: 30px;
|
||||
|
||||
b {
|
||||
font-size: 32px;
|
||||
color: $uni-color-primary;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 320px;
|
||||
height: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .AiCard {
|
||||
width: 100%;
|
||||
min-height: 160px;
|
||||
background: #FFFFFF;
|
||||
padding: 32px 32px 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
b {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: 30px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: #333;
|
||||
margin-right: 60px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.iconfont-iconMore {
|
||||
color: #666;
|
||||
position: absolute;
|
||||
right: 32px;
|
||||
top: 32px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
font-size: 24px;
|
||||
color: #999999;
|
||||
padding: 24px 0;
|
||||
border-bottom: 1px solid rgba(221, 221, 221, .4);
|
||||
}
|
||||
|
||||
.AiImage {
|
||||
width: 30%;
|
||||
margin-bottom: 8px;
|
||||
margin-right: 8px;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 218px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addBtn {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
flex-shrink: 0;
|
||||
background: $uni-color-primary;
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
|
||||
font-size: 48px;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menu {
|
||||
text-align: center;
|
||||
line-height: 80px;
|
||||
width: 192px;
|
||||
height: 80px;
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
::v-deep .u-search {
|
||||
margin-bottom: 0 !important;
|
||||
padding-left: 146px;
|
||||
box-shadow: none;
|
||||
|
||||
.u-content {
|
||||
padding-left: 50px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
174
src/pages/loading.vue
Normal file
@@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<section class="loading">
|
||||
<!-- <div class="iconfont iconfont-iconWeChat"/>-->
|
||||
<!-- <div class="iconfont iconfont-iconjuminxinxi"/>-->
|
||||
<!-- <div class="iconfont iconfont-iconLogo"/>-->
|
||||
<ai-result v-if="result.tips" v-bind="result"/>
|
||||
<template v-if="isDev">
|
||||
<input v-if="!!$route.query.code" class="codeText" :value="$route.query.code"/>
|
||||
<div class="codeBtn" @click="devGetCode">获取code</div>
|
||||
<div flex class="appsPane wrap">
|
||||
<b v-for="app in apps" :key="app.key" @tap="gotoApp(app.key)">{{ app.name }}</b>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapState} from 'vuex'
|
||||
import AiResult from "../components/AiResult";
|
||||
import UTag from "../uview/components/u-tag/u-tag";
|
||||
|
||||
export default {
|
||||
name: 'loading',
|
||||
components: {UTag, AiResult},
|
||||
inject: ['root'],
|
||||
computed: {
|
||||
...mapState(['token', 'apps', 'openUser', 'user']),
|
||||
currentApp() {
|
||||
return this.apps.find(e => e.key == this.$route.query.app) || {}
|
||||
},
|
||||
isDev() {
|
||||
return this.$route.hash == "#dev"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getToken', 'getAccount', 'agentSign', 'getUserInfo', 'getCode', 'closeAgent']),
|
||||
initAccess() {
|
||||
if (this.$route.hash == "#error" || this.isDev) {
|
||||
return Promise.resolve()
|
||||
} else if (this.$route.hash == "#form") {
|
||||
if (this.openUser?.openId || !!this.$route.query.preview) {
|
||||
this.openForm()
|
||||
} else if (this.$route.query?.code) {
|
||||
this.getToken(this.$route.query?.code)
|
||||
.then(() => this.getUserInfo())
|
||||
.then(() => this.openForm())
|
||||
} else this.getCode(location.href)
|
||||
} else if (this.token) {//获取账号信息
|
||||
return this.getAccount()
|
||||
} else if (this.$route.query?.code) {//获取token
|
||||
return this.getToken(this.$route.query?.code)
|
||||
} else {//获取应用配置
|
||||
this.getCode(location.href)
|
||||
}
|
||||
},
|
||||
openForm() {
|
||||
this.redirectTo("/askForm/askForm")
|
||||
},
|
||||
redirectTo(path) {
|
||||
let {query, hash} = this.$route
|
||||
delete query.app
|
||||
uni.redirectTo({
|
||||
url: `/pages${path}`, success: () => {
|
||||
this.$router.push({query, hash})
|
||||
}
|
||||
})
|
||||
},
|
||||
gotoApp(app) {
|
||||
uni.reLaunch({url: '/pages/loading?app=' + app})
|
||||
},
|
||||
devGetCode() {
|
||||
this.getCode(location.origin + '/pages/loading#dev')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
uni.showLoading({
|
||||
title: "加载中"
|
||||
})
|
||||
this.initAccess()?.then(() => {
|
||||
uni.hideLoading()
|
||||
if (this.token) {
|
||||
if (this.currentApp.name) {
|
||||
this.redirectTo(this.currentApp.path)
|
||||
} else if (this.$route.query.url) {
|
||||
this.redirectTo(this.$route.query.url)
|
||||
} else {
|
||||
this.result = {
|
||||
status: "error",
|
||||
tips: "应用加载失败",
|
||||
btn: "重新加载",
|
||||
btnTap() {
|
||||
location.href = location.href?.replace("#error", '')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (this.isDev) {
|
||||
this.result = {
|
||||
tips: "欢迎进入开发应用",
|
||||
}
|
||||
} else {
|
||||
this.result = {
|
||||
status: "error",
|
||||
tips: "应用加载失败",
|
||||
btn: "重新加载",
|
||||
btnTap() {
|
||||
location.href = location.href?.replace("#error", '')
|
||||
}
|
||||
}
|
||||
}
|
||||
})?.catch(() => {
|
||||
uni.navigateTo({url: './login'})
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
& > span {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.codeText {
|
||||
font-size: 24px;
|
||||
margin-top: 16px;
|
||||
padding: 4px 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.codeBtn {
|
||||
width: 180px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
background: $uni-color-warning;
|
||||
color: #fff;
|
||||
padding: 8px;
|
||||
margin-top: 16px;
|
||||
border-radius: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.appsPane {
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
padding: 0 16px;
|
||||
|
||||
b {
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
background: $uni-color-primary;
|
||||
color: #fff;
|
||||
padding: 8px;
|
||||
margin: 4px;
|
||||
border-radius: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
96
src/pages/login.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<section class="login">
|
||||
<u-form :model="form" ref="loginForm" label-width="140">
|
||||
<u-form-item label="账号" prop="phone" :errory-type="['message']">
|
||||
<u-input v-model="form.phone" placeholder="请输入手机号"/>
|
||||
</u-form-item>
|
||||
<u-form-item label="验证码" prop="vcode">
|
||||
<u-input v-model="form.vcode" placeholder="请输入短信验证码"/>
|
||||
<u-verification-code ref="vcode" secords="60" @change="v=>tips=v"/>
|
||||
<div class="vcode" @tap="$u.debounce(getVCode)">{{ tips }}</div>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
<div bottom>
|
||||
<u-button type="primary" @tap="handleLogin">绑定并登录</u-button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: "login",
|
||||
inject: ['root'],
|
||||
computed: {
|
||||
...mapState(['lastPage']),
|
||||
rules() {
|
||||
return {
|
||||
phone: [{required: true, message: "请选择分组"}],
|
||||
vcode: [{required: true, message: "请选择快捷回复类型"}],
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
tips: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['getCode']),
|
||||
getVCode() {
|
||||
if (this.form.phone) {
|
||||
this.$http.post("/admin/user/sendCode", null, {
|
||||
withoutToken: 1,
|
||||
params: {phone: this.form.phone}
|
||||
}).then(() => {
|
||||
this.$u.toast("验证已发送!")
|
||||
this.$refs.vcode?.start()
|
||||
})
|
||||
} else {
|
||||
this.$u.toast("请先填写手机号!")
|
||||
}
|
||||
},
|
||||
handleLogin() {
|
||||
this.$refs.loginForm.validate(v => {
|
||||
if (v) {
|
||||
let params = {
|
||||
...this.form, code: this.$route.query?.code, then: res => {
|
||||
let last = uni.getStorageSync("lastApp")
|
||||
if (last) {
|
||||
this.$store.commit("login", [res?.token_type, res?.access_token].join(" ").trim())
|
||||
uni.removeStorageSync("lastApp")
|
||||
// this.root.getCode(location.origin + last)
|
||||
uni.reLaunch({url: "./loading?app=" + last})
|
||||
} else this.$u.toast("绑定成功,请重新打开应用页面!")
|
||||
}
|
||||
}
|
||||
this.$store.commit("bindAccount", params)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
!this.$route.query?.code && this.getCode()
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => this.$refs.loginForm?.setRules(this.rules))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login {
|
||||
border-top: 1px solid #D4D4D4;
|
||||
padding: 0 0 208px;
|
||||
background: #F5F5F5;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
|
||||
.vcode {
|
||||
color: $uni-color-primary;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||