代码提交-3-13
This commit is contained in:
92
App.vue
92
App.vue
@@ -19,14 +19,94 @@
|
||||
},
|
||||
initConfig() {
|
||||
this.globalData.config = config
|
||||
// 运行路径自动开关 CAS:/app-cas 启用,/app 禁用
|
||||
try {
|
||||
const path = (window.location && window.location.pathname) || ''
|
||||
if (/^\/app-cas\/?/.test(path)) {
|
||||
this.globalData.config.casEnable = true
|
||||
} else if (/^\/app\/?/.test(path)) {
|
||||
this.globalData.config.casEnable = false
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
},
|
||||
checkLogin() {
|
||||
// if (!getToken()) {
|
||||
// this.$tab.reLaunch('/pages/login')
|
||||
// }
|
||||
const whiteList = ['/pages/work/sidebar/safetyDeclaratio/index'] // 添加不需要登录的页面路径
|
||||
const currentPath = this.$route.path
|
||||
if (!whiteList.includes(currentPath) && !getToken()) {
|
||||
const whiteList = ['/pages/work/sidebar/safetyDeclaratio/index', '/pages/cas/callback']
|
||||
const currentPath = this.$route && this.$route.path
|
||||
const hasToken = getToken()
|
||||
const cfg = this.globalData && this.globalData.config
|
||||
const casEnable = cfg && cfg.casEnable
|
||||
const casServer = cfg && cfg.casServer
|
||||
const casService = cfg && cfg.casService
|
||||
|
||||
// 检查当前路径
|
||||
const path = (window.location && window.location.pathname) || ''
|
||||
|
||||
// 1) 如果当前是 CAS 回调地址,根据来源处理
|
||||
const isCasCallbackPath = /\/cas\/cas-callback$/.test(path) || /\/app-cas\/cas-callback$/.test(path)
|
||||
if (isCasCallbackPath) {
|
||||
try {
|
||||
const from = sessionStorage.getItem('pasd_sso_from')
|
||||
// 如果是从移动端来的请求且当前在PC端回调路径,则重定向到移动端回调路径
|
||||
if (from === 'app-cas' && /^\/cas\/cas-callback$/.test(path)) {
|
||||
sessionStorage.removeItem('pasd_sso_from')
|
||||
window.location.href = `${window.location.origin}/app-cas/cas-callback${window.location.search || ''}`
|
||||
return
|
||||
}
|
||||
// 如果是从PC端来的请求且当前在移动端回调路径,则重定向到PC端回调路径
|
||||
if (!from && /^\/app-cas\/cas-callback$/.test(path)) {
|
||||
window.location.href = `${window.location.origin}/cas/cas-callback${window.location.search || ''}`
|
||||
return
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
try {
|
||||
const u = new URL(window.location.href)
|
||||
const ticket = u.searchParams.get('ticket')
|
||||
const q = ticket ? `?ticket=${encodeURIComponent(ticket)}` : ''
|
||||
this.$tab.reLaunch(`/pages/cas/callback${q}`)
|
||||
return
|
||||
} catch (e) {
|
||||
this.$tab.reLaunch('/pages/cas/callback')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在开启 CAS 时走统一认证
|
||||
if (casEnable && casServer && casService) {
|
||||
// 1.1) 如果落在 /login 并携带 redirect=...cas-callback,强制跳到回调地址(避免看到PC登录页)
|
||||
try {
|
||||
const u2 = new URL(window.location.href)
|
||||
const p2 = u2.pathname || ''
|
||||
const red = u2.searchParams.get('redirect') || ''
|
||||
if (/^\/login$/.test(p2) && /\/(cas|app-cas)\/cas-callback/.test(red)) {
|
||||
window.location.href = `${window.location.origin}${red}`
|
||||
return
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
|
||||
// 2) 在 /app-cas/ 路径下,强制跳转到统一认证登录页(无论是否已登录)
|
||||
if (/^\/app-cas\//.test(path)) {
|
||||
let effectiveService = casService
|
||||
// 使用统一的CAS回调地址,但通过sessionStorage记录来源
|
||||
try {
|
||||
sessionStorage.setItem('pasd_sso_from', 'app-cas')
|
||||
} catch (e) { /* noop */ }
|
||||
window.location.href = `${casServer}?service=${encodeURIComponent(effectiveService)}`
|
||||
return
|
||||
}
|
||||
|
||||
// 3) 其它未登录入口,直接跳转到统一认证登录页
|
||||
if (!hasToken) {
|
||||
let effectiveService = casService
|
||||
// 记录PC端访问来源
|
||||
try {
|
||||
sessionStorage.removeItem('pasd_sso_from')
|
||||
} catch (e) { /* noop */ }
|
||||
window.location.href = `${casServer}?service=${encodeURIComponent(effectiveService)}`
|
||||
return
|
||||
}
|
||||
}
|
||||
// 未启用 CAS 或非 /app-cas/ 路径且未登录的场景,仍回到系统账号登录页
|
||||
if (!hasToken && !whiteList.includes(currentPath)) {
|
||||
this.$tab.reLaunch('/pages/login')
|
||||
}
|
||||
}
|
||||
|
||||
44
api/inspection/abnormal.js
Normal file
44
api/inspection/abnormal.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询巡检异常列表
|
||||
export function listAbnormal(query) {
|
||||
return request({
|
||||
url: '/inspection/abnormal/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询巡检异常详细
|
||||
export function getAbnormal(id) {
|
||||
return request({
|
||||
url: '/inspection/abnormal/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增巡检异常
|
||||
export function addAbnormal(data) {
|
||||
return request({
|
||||
url: '/inspection/abnormal',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改巡检异常
|
||||
export function updateAbnormal(data) {
|
||||
return request({
|
||||
url: '/inspection/abnormal',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除巡检异常
|
||||
export function delAbnormal(id) {
|
||||
return request({
|
||||
url: '/inspection/abnormal/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
39
api/login.js
39
api/login.js
@@ -66,3 +66,42 @@ export const getRouters = () => {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// CAS 登录方法(通过请求参数传递 ticket 与 service)
|
||||
export function casLogin(ticket, service) {
|
||||
return request({
|
||||
url: '/cas/login',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
// 后端使用 @RequestParam 接收参数
|
||||
params: { ticket, service }
|
||||
})
|
||||
}
|
||||
|
||||
// 移动端CAS登录方法
|
||||
export function casAppLogin(ticket) {
|
||||
return request({
|
||||
url: '/cas/app/login',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
// 后端使用 @RequestParam 接收参数
|
||||
params: { ticket }
|
||||
})
|
||||
}
|
||||
|
||||
// 统一CAS登录方法
|
||||
export function casUnifiedLogin(ticket) {
|
||||
return request({
|
||||
url: '/cas/unified/login',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
// 后端使用 @RequestParam 接收参数
|
||||
params: { ticket }
|
||||
})
|
||||
}
|
||||
17
config.js
17
config.js
@@ -1,9 +1,22 @@
|
||||
// 应用全局配置
|
||||
module.exports = {
|
||||
// baseUrl: 'http://172.16.129.101:8080/dev-api/',
|
||||
baseUrl: 'https://pasd.gxsdxy.cn/prod-api/',
|
||||
// baseUrl: 'https://pasd.gxsdxy.cn/prod-api/',
|
||||
// baseUrl: 'http://172.16.129.101:8080',//172.16.129.101
|
||||
// baseUrl: 'https://192.168.211.59:8080/',
|
||||
// baseUrl: 'http://localhost:8080',
|
||||
// 生产环境:通过 Nginx /prod-api 代理到后端
|
||||
baseUrl: 'https://pasd.gxsdxy.cn/prod-api',
|
||||
// 是否启用 CAS 登录(与系统账号登录并存,H5 自动跳转)
|
||||
// 发布 CAS 端:开启
|
||||
casEnable: true,
|
||||
// CAS 服务端登录地址(示例为统一认证平台登录地址)
|
||||
casServer: 'https://rsso.gxsdxy.cn/login',
|
||||
// PC 端
|
||||
casService: 'https://pasd.gxsdxy.cn/cas',
|
||||
|
||||
// 移动端(关键) - 现在统一使用相同的回调地址,由后端处理设备检测
|
||||
casServiceMobile: 'https://pasd.gxsdxy.cn/cas',
|
||||
|
||||
// 应用信息
|
||||
appInfo: {
|
||||
// 应用名称
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
"router" : {
|
||||
"mode" : "hash",
|
||||
"base" : "./"
|
||||
}
|
||||
},
|
||||
"publicPath": "./"
|
||||
}
|
||||
}
|
||||
|
||||
75
pages.json
75
pages.json
@@ -4,6 +4,12 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
}, {
|
||||
"path": "pages/cas/callback",
|
||||
"style": {
|
||||
"navigationBarTitleText": "CAS回调",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}, {
|
||||
"path": "pages/register",
|
||||
"style": {
|
||||
@@ -79,7 +85,7 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "申报记录",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#bbbbbb",
|
||||
"navigationBarBackgroundColor": "#1677ff",
|
||||
"enablePullDownRefresh": true, //配置后,可以下拉刷新,上拉加载`
|
||||
"onReachBottomDistance": 100
|
||||
}
|
||||
@@ -88,8 +94,16 @@
|
||||
"path": "pages/work/sidebar/safetyDeclaratio/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#bbbbbb",
|
||||
"navigationBarTitleText": "隐患申报"
|
||||
"navigationBarBackgroundColor": "#1677ff",
|
||||
"navigationBarTitleText": "异常隐患巡检"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/work/sidebar/filingDetail/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#1677ff",
|
||||
"navigationBarTitleText": "申报详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -113,7 +127,7 @@
|
||||
},
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#bbbbbb",
|
||||
"navigationBarBackgroundColor": "#1677ff",
|
||||
"navigationBarTitleText": "扫码签到"
|
||||
}
|
||||
},
|
||||
@@ -124,7 +138,7 @@
|
||||
"path": "pages/work/inspection/inspectionEx/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#bbbbbb",
|
||||
"navigationBarBackgroundColor": "#1677ff",
|
||||
"navigationBarTitleText": "异常巡检"
|
||||
}
|
||||
},
|
||||
@@ -132,13 +146,60 @@
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
},
|
||||
"path": "pages/work/inspection/checkInUserList/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户打卡列表",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#1677ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"app-plus": { "titleNView": false },
|
||||
"path": "pages/work/inspection/checkInRecord/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#bbbbbb",
|
||||
"navigationBarBackgroundColor": "#1677ff",
|
||||
"navigationBarTitleText": "打卡记录"
|
||||
}
|
||||
},
|
||||
"usingComponents": true
|
||||
{
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
},
|
||||
"path": "pages/work/inspection/abnormalList/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "巡检异常列表",
|
||||
"navigationBarBackgroundColor": "#1677ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
},
|
||||
"path": "pages/work/inspection/abnormalDetail/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarTitleText": "异常详情",
|
||||
"navigationBarBackgroundColor": "#1677ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/work/inspection/checkInDetail/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "打卡详情",
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#1677ff"
|
||||
}
|
||||
},
|
||||
{
|
||||
"app-plus": { "titleNView": false },
|
||||
"path": "pages/work/inspection/recordList/index",
|
||||
"style": {
|
||||
"navigationBarTextStyle": "white",
|
||||
"navigationBarBackgroundColor": "#1677ff",
|
||||
"navigationBarTitleText": "巡检记录"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
|
||||
42
pages/cas/callback.vue
Normal file
42
pages/cas/callback.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<view class="cas-callback">
|
||||
<text>正在处理统一认证登录...</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import appConfig from '@/config'
|
||||
import { casUnifiedLogin } from '@/api/login'
|
||||
|
||||
export default {
|
||||
onLoad(options) {
|
||||
// H5 环境下 options 可携带 ticket 参数
|
||||
const ticket = options?.ticket || (this.$route && this.$route.query && this.$route.query.ticket)
|
||||
if (!ticket) {
|
||||
this.$modal.msgError('CAS 未返回票据')
|
||||
this.$tab.reLaunch('/pages/login')
|
||||
return
|
||||
}
|
||||
|
||||
// 使用移动端CAS登录API,因为现在Nginx会根据User-Agent和请求参数自动路由
|
||||
this.$store.dispatch('CasAppLogin', ticket).then(() => {
|
||||
// 拉取用户信息并进入工作台
|
||||
this.$store.dispatch('GetInfo').then(() => {
|
||||
this.$tab.reLaunch('/pages/work/index')
|
||||
}).catch(() => {
|
||||
this.$tab.reLaunch('/pages/work/index')
|
||||
})
|
||||
}).catch(err => {
|
||||
this.$modal.msgError('移动端CAS登录失败:' + (err && err.toString ? err.toString() : '未知错误'))
|
||||
this.$tab.reLaunch('/pages/login')
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.cas-callback {
|
||||
padding: 40rpx;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<view class="content">
|
||||
<image class="logo" src="@/static/logo.png"></image>
|
||||
<view class="text-area">
|
||||
<text class="title">Hello RuoYi</text>
|
||||
<text class="title">平安水电</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -25,13 +25,17 @@
|
||||
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
|
||||
</view>
|
||||
<view class="reg text-center" v-if="register">
|
||||
<text class="text-grey1">没有账号?</text>
|
||||
<text class="text-grey1">没有账号?</text>
|
||||
<text @click="handleUserRegister" class="text-blue">立即注册</text>
|
||||
</view>
|
||||
<view class="xieyi text-center">
|
||||
<text class="text-grey1">登录即代表同意</text>
|
||||
<text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
|
||||
<text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
|
||||
<text @click="handleUserAgrement" class="text-blue">
|
||||
<用户协议>
|
||||
</text>
|
||||
<text @click="handlePrivacy" class="text-blue">
|
||||
<隐私协议>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -60,6 +64,22 @@
|
||||
},
|
||||
created() {
|
||||
this.getCode()
|
||||
// H5 场景下:若启用 CAS 且未登录,直接跳统一认证
|
||||
//#ifdef H5
|
||||
try {
|
||||
const cfg = getApp().globalData && getApp().globalData.config
|
||||
const hasToken = this.$store && this.$store.state && this.$store.state.user && this.$store.state.user.token
|
||||
if (cfg && cfg.casEnable && !hasToken) {
|
||||
let service = cfg.casService
|
||||
const p = (window.location && window.location.pathname) || ''
|
||||
if (/^\/app-cas\//.test(p)) {
|
||||
service = cfg.casServiceMobile
|
||||
}
|
||||
const url = `${cfg.casServer}?service=${encodeURIComponent(service)}`
|
||||
window.location.href = url
|
||||
}
|
||||
} catch (e) { /* noop */ }
|
||||
//#endif
|
||||
},
|
||||
methods: {
|
||||
// 用户注册
|
||||
@@ -95,7 +115,7 @@
|
||||
} else if (this.loginForm.code === "" && this.captchaEnabled) {
|
||||
this.$modal.msgError("请输入验证码")
|
||||
} else {
|
||||
this.$modal.loading("登录中,请耐心等待...")
|
||||
this.$modal.loading("登录中,请耐心等待...")
|
||||
this.pwdLogin()
|
||||
}
|
||||
},
|
||||
@@ -110,7 +130,7 @@
|
||||
}
|
||||
})
|
||||
},
|
||||
// 登录成功后,处理函数
|
||||
// 登录成功后,处理函数
|
||||
loginSuccess(result) {
|
||||
// 设置用户信息
|
||||
this.$store.dispatch('GetInfo').then(res => {
|
||||
@@ -199,5 +219,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<view class="list-cell list-cell-arrow">
|
||||
<view class="menu-item-box">
|
||||
<view>官方邮箱</view>
|
||||
<view class="text-right">ruoyi@xx.com</view>
|
||||
<view class="text-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow">
|
||||
<view class="menu-item-box">
|
||||
<view>服务热线</view>
|
||||
<view class="text-right">400-999-9999</view>
|
||||
<view class="text-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow">
|
||||
@@ -38,7 +38,7 @@
|
||||
</view>
|
||||
|
||||
<view class="copyright">
|
||||
<view>Copyright © 2022 ruoyi.vip All Rights Reserved.</view>
|
||||
<view>Copyright © 2022 平安水电 All Rights Reserved.</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -20,21 +20,7 @@
|
||||
data() {
|
||||
return {
|
||||
list: [{
|
||||
icon: 'iconfont icon-github',
|
||||
title: '若依问题',
|
||||
childList: [{
|
||||
title: '若依开源吗?',
|
||||
content: '开源'
|
||||
}, {
|
||||
title: '若依可以商用吗?',
|
||||
content: '可以'
|
||||
}, {
|
||||
title: '若依官网地址多少?',
|
||||
content: 'http://ruoyi.vip'
|
||||
}, {
|
||||
title: '若依文档地址多少?',
|
||||
content: 'http://doc.ruoyi.vip'
|
||||
}]
|
||||
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-help',
|
||||
|
||||
@@ -27,22 +27,6 @@
|
||||
|
||||
<view class="content-section">
|
||||
<view class="mine-actions grid col-4 text-center">
|
||||
<view class="action-item" @click="handleJiaoLiuQun">
|
||||
<view class="iconfont icon-friendfill text-pink icon"></view>
|
||||
<text class="text">交流群</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleBuilding">
|
||||
<view class="iconfont icon-service text-blue icon"></view>
|
||||
<text class="text">在线客服</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleBuilding">
|
||||
<view class="iconfont icon-community text-mauve icon"></view>
|
||||
<text class="text">反馈社区</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleBuilding">
|
||||
<view class="iconfont icon-dianzan text-green icon"></view>
|
||||
<text class="text">点赞我们</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-list">
|
||||
@@ -52,7 +36,7 @@
|
||||
<view>编辑资料</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow" @click="handleHelp">
|
||||
<!-- <view class="list-cell list-cell-arrow" @click="handleHelp">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-help menu-icon"></view>
|
||||
<view>常见问题</view>
|
||||
@@ -63,7 +47,7 @@
|
||||
<view class="iconfont icon-aixin menu-icon"></view>
|
||||
<view>关于我们</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<view class="list-cell list-cell-arrow" @click="handleToSetting">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-setting menu-icon"></view>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
uniGridList: [{
|
||||
text: '打卡记录',
|
||||
type: "calendar-filled",
|
||||
ifData: "checkRole(['admin'])"
|
||||
ifData: false
|
||||
},
|
||||
{
|
||||
text: '异常巡检',
|
||||
@@ -64,6 +64,11 @@
|
||||
type: "navigate-filled",
|
||||
ifData: "checkRole(['admin',common])"
|
||||
},
|
||||
{
|
||||
text: '巡检记录',
|
||||
type: "list",
|
||||
ifData: "checkRole(['admin'])"
|
||||
},
|
||||
// {
|
||||
// text: '申报记录',
|
||||
// type: "list",
|
||||
@@ -105,10 +110,12 @@
|
||||
if (index == 1) {
|
||||
this.$tab.navigateTo("/pages/work/inspection/checkInRecord/index");
|
||||
} else if (index == 2) {
|
||||
this.$tab.navigateTo("/pages/work/inspection/inspectionEx/index");
|
||||
this.$tab.navigateTo("/pages/work/inspection/abnormalList/index");
|
||||
} else if (index == 3) {
|
||||
this.$tab.navigateTo("/pages/work/sidebar/safetyDeclaratio/index");
|
||||
this.$tab.navigateTo("/pages/work/sidebar/filingLog/index");
|
||||
} else if (index == 4) {
|
||||
this.$tab.navigateTo("/pages/work/inspection/recordList/index");
|
||||
} else if (index == 5) {
|
||||
this.$tab.navigateTo("/pages/work/sidebar/filingLog/index");
|
||||
}
|
||||
|
||||
|
||||
101
pages/work/inspection/abnormalDetail/index.vue
Normal file
101
pages/work/inspection/abnormalDetail/index.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<uni-section title="异常详情" type="line" class="mb-10" />
|
||||
|
||||
<uni-card :title="detail.inspectionPoint" :is-shadow="true" @click="onCardClick">
|
||||
<uni-row class="row" :width="730">
|
||||
<uni-col :span="16">
|
||||
<view>
|
||||
<text class="uni-body">巡检类型:{{ typeLabel(detail.inspectionType) }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">巡检人:{{ detail.inspectorId }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">巡检时间:{{ formatDate(detail.inspectionTime) }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">备注:{{ detail.remark || '-' }}</text>
|
||||
</view>
|
||||
</uni-col>
|
||||
<uni-col :span="8">
|
||||
<view class="thumbs">
|
||||
<image v-for="(img,idx) in imageArray(detail.inspectionImg)" :key="idx" :src="imageUrl(img)" mode="aspectFill" class="thumb" @click.stop="preview(img, detail.inspectionImg)" />
|
||||
</view>
|
||||
</uni-col>
|
||||
</uni-row>
|
||||
</uni-card>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getAbnormal } from '@/api/inspection/abnormal.js'
|
||||
import { listData } from '@/api/system/dict/data.js'
|
||||
import config from '@/config'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
detail: {},
|
||||
typeDict: [],
|
||||
}
|
||||
},
|
||||
async onLoad(options) {
|
||||
this.id = options?.id || null
|
||||
await this.initDict()
|
||||
if (this.id) {
|
||||
await this.fetchDetail()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async initDict() {
|
||||
const typeData = await listData({ dictType: 'inspection_type' })
|
||||
this.typeDict = typeData.rows || []
|
||||
},
|
||||
typeLabel(val) {
|
||||
const item = this.typeDict.find(i => String(i.dictValue) === String(val))
|
||||
return item ? item.dictLabel : (val ?? '-')
|
||||
},
|
||||
async fetchDetail() {
|
||||
const res = await getAbnormal(this.id)
|
||||
this.detail = res?.data || {}
|
||||
},
|
||||
imageArray(inspectionImg) {
|
||||
if (!inspectionImg) return []
|
||||
return inspectionImg.split(',').filter(Boolean)
|
||||
},
|
||||
imageUrl(path) {
|
||||
if (!path) return ''
|
||||
return config.baseUrl + path
|
||||
},
|
||||
preview(current, all) {
|
||||
const urls = this.imageArray(all).map(p => this.imageUrl(p))
|
||||
uni.previewImage({ current: this.imageUrl(current), urls })
|
||||
},
|
||||
formatDate(val) {
|
||||
if (!val) return ''
|
||||
try {
|
||||
const d = new Date(val)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth()+1).padStart(2,'0')
|
||||
const dd = String(d.getDate()).padStart(2,'0')
|
||||
const hh = String(d.getHours()).padStart(2,'0')
|
||||
const mm = String(d.getMinutes()).padStart(2,'0')
|
||||
const ss = String(d.getSeconds()).padStart(2,'0')
|
||||
return `${y}-${m}-${dd} ${hh}:${mm}:${ss}`
|
||||
} catch(e) { return val }
|
||||
},
|
||||
onCardClick(type) {
|
||||
// 可按需处理卡片各区域点击,目前不做特殊逻辑
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container { padding: 10px; }
|
||||
.thumbs { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }
|
||||
.thumb { width: 60px; height: 60px; border-radius: 6px; background: #f5f5f5; }
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
</style>
|
||||
188
pages/work/inspection/abnormalList/index.vue
Normal file
188
pages/work/inspection/abnormalList/index.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<uni-section title="巡检异常列表" type="line" class="mb-10">
|
||||
<template v-slot:right>
|
||||
<button size="mini" type="primary" @click.stop="goAdd">新增</button>
|
||||
</template>
|
||||
</uni-section>
|
||||
|
||||
<view class="filters">
|
||||
<uni-data-select v-model="queryParams.inspectionType" :localdata="selectList" placeholder="巡检类型" />
|
||||
<uni-datetime-picker type="daterange" v-model="dateRange" :clear-icon="true" start="1990-01-01" end="2099-12-31" />
|
||||
<button class="ml-6" size="mini" type="primary" @click="handleQuery">搜索</button>
|
||||
<button class="ml-6" size="mini" @click="resetQuery">重置</button>
|
||||
</view>
|
||||
|
||||
<uni-card v-for="item in list" :key="item.id" :title="item.inspectionPoint" @click="onCardClick($event, item)">
|
||||
<uni-row class="row" :width="730">
|
||||
<uni-col :span="16">
|
||||
<view>
|
||||
<text class="uni-body">巡检人:{{ item.inspectorId }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">巡检时间:{{ formatDate(item.inspectionTime) }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">备注:{{ item.remark || '-' }}</text>
|
||||
</view>
|
||||
</uni-col>
|
||||
<uni-col :span="8">
|
||||
<view class="thumbs">
|
||||
<image v-for="(img,idx) in firstThreeImages(item.inspectionImg)" :key="idx" :src="imageUrl(img)" mode="aspectFill" class="thumb" />
|
||||
</view>
|
||||
</uni-col>
|
||||
</uni-row>
|
||||
</uni-card>
|
||||
<uni-load-more :status="loadStatus" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listAbnormal } from '@/api/inspection/abnormal.js';
|
||||
import { listData } from '@/api/system/dict/data.js';
|
||||
import config from '@/config';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loadStatus: 'more',
|
||||
loading: false,
|
||||
total: 0,
|
||||
list: [],
|
||||
selectList: [],
|
||||
dateRange: [],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
inspectionPoint: '',
|
||||
inspectorId: '',
|
||||
inspectionType: null,
|
||||
params: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initDict()
|
||||
this.getList()
|
||||
},
|
||||
// 触底加载更多(页面滚动到底部)
|
||||
onReachBottom() {
|
||||
if (this.loadStatus !== 'more' || this.loading) return
|
||||
this.loadStatus = 'loading'
|
||||
this.queryParams.pageNum += 1
|
||||
this.fetchList({ append: true })
|
||||
},
|
||||
// 页面重新显示时自动刷新列表(新增返回后生效)
|
||||
onShow() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
async initDict() {
|
||||
const typeData = await listData({ dictType: 'inspection_type' })
|
||||
this.selectList = typeData.rows.map(i => ({ value: i.dictValue, text: i.dictLabel }))
|
||||
},
|
||||
imageUrl(path) {
|
||||
if (!path) return ''
|
||||
return config.baseUrl + path
|
||||
},
|
||||
firstThreeImages(inspectionImg) {
|
||||
if (!inspectionImg) return []
|
||||
return inspectionImg.split(',').slice(0,3)
|
||||
},
|
||||
formatDate(val) {
|
||||
if (!val) return ''
|
||||
try {
|
||||
const d = new Date(val)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth()+1).padStart(2,'0')
|
||||
const dd = String(d.getDate()).padStart(2,'0')
|
||||
const hh = String(d.getHours()).padStart(2,'0')
|
||||
const mm = String(d.getMinutes()).padStart(2,'0')
|
||||
const ss = String(d.getSeconds()).padStart(2,'0')
|
||||
return `${y}-${m}-${dd} ${hh}:${mm}:${ss}`
|
||||
} catch(e) { return val }
|
||||
},
|
||||
buildParams() {
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
this.queryParams.params = {
|
||||
beginTime: this.dateRange[0],
|
||||
endTime: this.dateRange[1]
|
||||
}
|
||||
} else {
|
||||
this.queryParams.params = {}
|
||||
}
|
||||
},
|
||||
async fetchList({ append = false } = {}) {
|
||||
try {
|
||||
this.loading = true
|
||||
this.buildParams()
|
||||
const res = await listAbnormal(this.queryParams)
|
||||
const rows = res?.rows || []
|
||||
// 若接口提供 total 字段则使用,否则根据 pageSize 判断是否还有更多
|
||||
this.total = typeof res?.total === 'number' ? res.total : (append ? this.list.length + rows.length : rows.length)
|
||||
if (append) {
|
||||
this.list = this.list.concat(rows)
|
||||
} else {
|
||||
this.list = rows
|
||||
}
|
||||
// 根据是否还有更多数据设置加载状态
|
||||
if (typeof res?.total === 'number') {
|
||||
this.loadStatus = this.list.length < this.total ? 'more' : 'noMore'
|
||||
} else {
|
||||
// 当本次返回数量等于 pageSize,默认还有下一页
|
||||
const hasMore = rows.length === this.queryParams.pageSize
|
||||
this.loadStatus = hasMore ? 'more' : 'noMore'
|
||||
}
|
||||
} catch (e) {
|
||||
this.loadStatus = 'noMore'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getList() {
|
||||
// 初始化查询第一页
|
||||
this.queryParams.pageNum = 1
|
||||
this.loadStatus = 'loading'
|
||||
await this.fetchList({ append: false })
|
||||
},
|
||||
onCardClick(type, item) {
|
||||
// 仅在内容区域点击时跳转(title/extra也可按需)
|
||||
if (!item) return
|
||||
this.goDetail(item)
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
inspectionPoint: '',
|
||||
inspectorId: '',
|
||||
inspectionType: null,
|
||||
params: {}
|
||||
}
|
||||
this.dateRange = []
|
||||
this.getList()
|
||||
},
|
||||
goAdd() {
|
||||
this.$tab.navigateTo('/pages/work/inspection/inspectionEx/index')
|
||||
},
|
||||
goDetail(item) {
|
||||
const id = item?.id
|
||||
if (!id) return
|
||||
this.$tab.navigateTo(`/pages/work/inspection/abnormalDetail/index?id=${id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container { padding: 10px; }
|
||||
.filters { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; }
|
||||
.thumbs { display: flex; gap: 6px; justify-content: flex-end; }
|
||||
.thumb { width: 60px; height: 60px; border-radius: 6px; background: #f5f5f5; }
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
.ml-6 { margin-left: 6px; }
|
||||
</style>
|
||||
96
pages/work/inspection/checkInDetail/index.vue
Normal file
96
pages/work/inspection/checkInDetail/index.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<view class="detail-page">
|
||||
<uni-section class="mb-10" title="打卡详情" type="line"></uni-section>
|
||||
|
||||
<uni-card isShadow>
|
||||
<view class="detail-row">
|
||||
<text class="label">巡检点:</text>
|
||||
<text class="value">{{ detail.inspectionPoint || '-' }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="label">巡检人:</text>
|
||||
<text class="value">{{ detail.inspectorUser || detail.inspectorName || '-' }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="label">打卡时间:</text>
|
||||
<text class="value">{{ formatDate(detail.inspectionTime) }}</text>
|
||||
</view>
|
||||
<view class="detail-row" v-if="detail.remark">
|
||||
<text class="label">备注:</text>
|
||||
<text class="value">{{ detail.remark }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="images.length" class="img-group">
|
||||
<image v-for="(img, idx) in images" :key="idx" :src="imageUrl(img)" mode="aspectFill" class="img" @click="previewImage(idx)" />
|
||||
</view>
|
||||
</uni-card>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRecord } from '@/api/inspection/record.js'
|
||||
import config from '@/config'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
detail: {},
|
||||
images: []
|
||||
}
|
||||
},
|
||||
onLoad(query) {
|
||||
this.id = query.id
|
||||
this.fetchDetail()
|
||||
},
|
||||
methods: {
|
||||
async fetchDetail() {
|
||||
if (!this.id) {
|
||||
uni.showToast({ title: '缺少记录ID', icon: 'none' })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getRecord(this.id)
|
||||
this.detail = (res && res.data) ? res.data : {}
|
||||
this.images = this.resolveImages(this.detail.inspectionImg || this.detail.clockInImg || this.detail.signImg)
|
||||
} catch (e) {
|
||||
console.error('fetch record detail error', e)
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
resolveImages(imgs) {
|
||||
if (!imgs) return []
|
||||
if (Array.isArray(imgs)) return imgs
|
||||
if (typeof imgs === 'string') return imgs.split(',').filter(Boolean)
|
||||
return []
|
||||
},
|
||||
imageUrl(path) {
|
||||
if (!path) return ''
|
||||
if (/^https?:\/\//.test(path)) return path
|
||||
return `${config.baseUrl}${path.startsWith('/') ? path : '/' + path}`
|
||||
},
|
||||
previewImage(index = 0) {
|
||||
if (!this.images.length) return
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: this.images.map(this.imageUrl)
|
||||
})
|
||||
},
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '-'
|
||||
const d = new Date(dateStr)
|
||||
if (isNaN(d.getTime())) return dateStr
|
||||
const pad = n => (n < 10 ? ('0' + n) : n)
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-page { padding: 10px; }
|
||||
.detail-row { display: flex; margin-bottom: 8px; }
|
||||
.label { color: #666; width: 80px; }
|
||||
.value { color: #333; }
|
||||
.img-group { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
|
||||
.img { width: 100px; height: 100px; border-radius: 6px; background: #f5f5f5; }
|
||||
</style>
|
||||
@@ -7,24 +7,84 @@
|
||||
</template>
|
||||
</uni-section>
|
||||
|
||||
<!-- 汇总图表 -->
|
||||
<qiun-data-charts type="ring" :opts="opts" :chartData="chartData" :errorShow="false" :tapLegend="false" />
|
||||
|
||||
<!-- 统计数据卡片 -->
|
||||
<uni-row :width="730" class="stats">
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">总打卡</text><text class="stat-value">{{ stats.total }}</text></view></uni-col>
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">活跃天</text><text class="stat-value">{{ stats.activeDays }}</text></view></uni-col>
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">平均/天</text><text class="stat-value">{{ stats.avgPerDay }}</text></view></uni-col>
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">最长连打</text><text class="stat-value">{{ stats.maxStreak }}</text></view></uni-col>
|
||||
</uni-row>
|
||||
|
||||
<!-- 统计概览 -->
|
||||
<uni-section class="mb-10" title="统计概览" type="line" />
|
||||
<view class="charts">
|
||||
<view class="chart-box">
|
||||
<qiun-data-charts type="column" :opts="optsBar" :chartData="chartWeek" :errorShow="false" />
|
||||
</view>
|
||||
<view class="chart-box">
|
||||
<qiun-data-charts type="column" :opts="optsBar" :chartData="chartHour" :errorShow="false" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 打卡数据卡片(样式与异常巡检一致) -->
|
||||
<uni-card v-if="summary.clockIn > 0 || latestRecord" :title="'本月总打卡:' + (summary.clockIn || 0)" :thumbnail="avatar" @click="onSummaryClick">
|
||||
<uni-row class="demo-uni-row" :width="nvueWidth">
|
||||
<!-- 左侧信息 -->
|
||||
<uni-col :span="16">
|
||||
<view>
|
||||
<text class="uni-body">打卡状态:{{ summary.clockState || '-' }}</text>
|
||||
</view>
|
||||
<view v-if="latestRecord">
|
||||
<text class="uni-body">最近时间:{{ formatDate(latestRecord.inspectionTime) }}</text>
|
||||
</view>
|
||||
<view v-if="latestRecord">
|
||||
<text class="uni-body">巡检点:{{ latestRecord.inspectionPoint || '-' }}</text>
|
||||
</view>
|
||||
<view v-if="latestRecord">
|
||||
<text class="uni-body">巡检人:{{ latestRecord.inspectorUser || '-' }}</text>
|
||||
</view>
|
||||
</uni-col>
|
||||
<!-- 右侧缩略图 -->
|
||||
<uni-col :span="8" v-if="latestRecord">
|
||||
<view class="thumbs" v-if="firstThreeImages(latestImages).length">
|
||||
<image v-for="(img,i) in firstThreeImages(latestImages)" :key="i" :src="imageUrl(img)" mode="aspectFill" class="thumb" @click.stop="previewImages(latestImages, i)" />
|
||||
</view>
|
||||
<view class="tag-view">
|
||||
<uni-tag :inverted="true" :circle="true" text="已打卡" size="small" />
|
||||
</view>
|
||||
</uni-col>
|
||||
</uni-row>
|
||||
</uni-card>
|
||||
|
||||
<uni-section class="mb-10" title="打卡列表" List="info">
|
||||
<template v-slot:right>
|
||||
<uni-data-select style="width:70px;" :clear=false v-model="queryform.weekType" :localdata="weekList"
|
||||
@change="weekTypeSelectChange"></uni-data-select>
|
||||
</template>
|
||||
</uni-section>
|
||||
<uni-card v-for="item of cardList" :title="item.inspectionPoint" :thumbnail="avatar">
|
||||
<!-- 列表卡片,点击可查看详情 -->
|
||||
<uni-card v-for="item of cardList" :key="item.id || item.recordId" :title="item.inspectionPoint" :thumbnail="avatar" @click="onCardClick($event, item)">
|
||||
<uni-row class="demo-uni-row" :width="nvueWidth">
|
||||
<uni-col :span="20">
|
||||
<!-- 左侧信息 -->
|
||||
<uni-col :span="16">
|
||||
<view>
|
||||
<text class="uni-body">
|
||||
打卡时间:{{item.inspectionTime}}
|
||||
</text>
|
||||
<text class="uni-body">打卡时间:{{ formatDate(item.inspectionTime) }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">巡检人:{{ item.inspectorUser || '-' }}</text>
|
||||
</view>
|
||||
<view v-if="item.remark">
|
||||
<text class="uni-body">备注:{{ item.remark }}</text>
|
||||
</view>
|
||||
</uni-col>
|
||||
<uni-col :span="4">
|
||||
<!-- 右侧缩略图 -->
|
||||
<uni-col :span="8">
|
||||
<view class="thumbs" v-if="firstThreeImages(item.inspectionImg || item.clockInImg || item.signImg).length">
|
||||
<image v-for="(img,i) in firstThreeImages(item.inspectionImg || item.clockInImg || item.signImg)" :key="i" :src="imageUrl(img)" mode="aspectFill" class="thumb" @click.stop="previewImages(item.inspectionImg || item.clockInImg || item.signImg, i)" />
|
||||
</view>
|
||||
<view class="tag-view">
|
||||
<uni-tag :inverted="true" :circle="true" text="已打卡" size="small" />
|
||||
</view>
|
||||
@@ -38,9 +98,8 @@
|
||||
|
||||
|
||||
<script>
|
||||
import {
|
||||
listRecordView
|
||||
} from '@/api/inspection/record.js'
|
||||
import { listRecordView, getRecord } from '@/api/inspection/record.js'
|
||||
import config from '@/config'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -50,6 +109,10 @@
|
||||
// 数据可视化
|
||||
inspectionRecordViewTable: [],
|
||||
cardList: [],
|
||||
// 汇总卡片数据
|
||||
summary: { clockIn: 0, clockState: '' },
|
||||
latestRecord: null,
|
||||
latestImages: [],
|
||||
weekList: [{
|
||||
text: "全部",
|
||||
value: 0
|
||||
@@ -213,7 +276,12 @@
|
||||
labelFontColor: "#666666"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 新增:统计与柱状图数据
|
||||
stats: { total: 0, activeDays: 0, avgPerDay: 0, maxStreak: 0 },
|
||||
optsBar: { legend: { show: false }, xAxis: { disableGrid: true }, yAxis: { disableGrid: false } },
|
||||
chartWeek: { categories: [], series: [{ name: '按周几', data: [] }] },
|
||||
chartHour: { categories: [], series: [{ name: '按时段', data: [] }] },
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -223,6 +291,12 @@
|
||||
this.queryform.month = currentMonth;
|
||||
listRecordView(this.queryform).then(res => {
|
||||
let data = res.data
|
||||
// 汇总卡片数据
|
||||
this.summary.clockIn = data.clockIn || 0
|
||||
this.summary.clockState = data.clockState || ''
|
||||
this.latestRecord = (data.inspectionRecordTables && data.inspectionRecordTables.length) ? data.inspectionRecordTables[0] : null
|
||||
this.latestImages = this.latestRecord ? (this.latestRecord.inspectionImg || this.latestRecord.clockInImg || this.latestRecord.signImg || []) : []
|
||||
// 图表数据
|
||||
if (data.clockIn != 0) {
|
||||
let obj = [{
|
||||
"name": data.clockState,
|
||||
@@ -232,7 +306,11 @@
|
||||
} else {
|
||||
this.inspectionRecordViewTable = []
|
||||
}
|
||||
this.cardList = data.inspectionRecordTables
|
||||
this.cardList = data.inspectionRecordTables || []
|
||||
// 新增:构建统计与柱状图
|
||||
this.buildWeekChart(this.cardList)
|
||||
this.buildHourChart(this.cardList)
|
||||
this.buildStats(this.cardList)
|
||||
})
|
||||
this.getServerData()
|
||||
},
|
||||
@@ -278,9 +356,83 @@
|
||||
this.chartData = JSON.parse(JSON.stringify(res));
|
||||
}, 1000);
|
||||
},
|
||||
// 统一图片数组处理(支持字符串或数组)
|
||||
firstThreeImages(imgs) {
|
||||
if (!imgs) return []
|
||||
if (Array.isArray(imgs)) return imgs.slice(0,3)
|
||||
if (typeof imgs === 'string') return imgs.split(',').filter(Boolean).slice(0,3)
|
||||
return []
|
||||
},
|
||||
imageUrl(path) {
|
||||
if (!path) return ''
|
||||
if (/^https?:\/\//.test(path)) return path
|
||||
return `${config.baseUrl}${path.startsWith('/') ? path : '/' + path}`
|
||||
},
|
||||
previewImages(imgs, index=0) {
|
||||
const list = Array.isArray(imgs) ? imgs : (typeof imgs === 'string' ? imgs.split(',').filter(Boolean) : [])
|
||||
if (!list.length) return
|
||||
uni.previewImage({
|
||||
current: index,
|
||||
urls: list.map(this.imageUrl)
|
||||
})
|
||||
},
|
||||
formatDate(dateStr) {
|
||||
if (!dateStr) return '-'
|
||||
const d = new Date(dateStr)
|
||||
if (isNaN(d.getTime())) return dateStr
|
||||
const pad = n => (n<10?('0'+n):n)
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
},
|
||||
onCardClick(e, item) {
|
||||
this.goDetail(item)
|
||||
},
|
||||
onSummaryClick() {
|
||||
// 若有最近一条记录,点击汇总卡片也进入其详情
|
||||
if (this.latestRecord) {
|
||||
this.goDetail(this.latestRecord)
|
||||
}
|
||||
},
|
||||
goDetail(item) {
|
||||
const id = item.id || item.recordId
|
||||
if (!id) {
|
||||
uni.showToast({ title: '记录ID缺失', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: `/pages/work/inspection/checkInDetail/index?id=${id}` })
|
||||
},
|
||||
// 新增:统计构建方法
|
||||
buildWeekChart(rows) {
|
||||
const names = ['周一','周二','周三','周四','周五','周六','周日']
|
||||
const counts = Array(7).fill(0)
|
||||
(rows || []).forEach(r => { const d = new Date(r.inspectionTime); if (!isNaN(d)) { const idx = (d.getDay()+6)%7; counts[idx]++ } })
|
||||
this.chartWeek = { categories: names, series: [{ name: '按周几', data: counts }] }
|
||||
},
|
||||
buildHourChart(rows) {
|
||||
const buckets = Array(24).fill(0)
|
||||
(rows || []).forEach(r => { const d = new Date(r.inspectionTime); if (!isNaN(d)) { buckets[d.getHours()]++ } })
|
||||
const labels = buckets.map((_,h)=> (h<10?('0'+h):h)+':00')
|
||||
this.chartHour = { categories: labels, series: [{ name: '按时段', data: buckets }] }
|
||||
},
|
||||
buildStats(rows) {
|
||||
const list = rows || []
|
||||
const total = list.length
|
||||
const daySet = new Set(list.map(r => this.formatDate(r.inspectionTime).slice(0,10)))
|
||||
const activeDays = daySet.size
|
||||
const avgPerDay = activeDays ? (total/activeDays).toFixed(2) : 0
|
||||
// 计算最长连续天数
|
||||
const days = Array.from(daySet).sort()
|
||||
let maxStreak = 0, cur = 0, prev = null
|
||||
const toDate = s => new Date(s+'T00:00:00')
|
||||
days.forEach(s => { if (!prev) { cur=1; } else { const diff=(toDate(s)-toDate(prev))/(24*3600*1000); cur = diff===1 ? cur+1 : 1 } maxStreak = Math.max(maxStreak, cur); prev = s })
|
||||
this.stats = { total, activeDays, avgPerDay, maxStreak }
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
onShow() {
|
||||
// 返回页面时自动刷新列表,保持数据最新
|
||||
this.getList()
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -304,6 +456,19 @@
|
||||
/* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */
|
||||
.charts-box {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.thumbs { display:flex; gap:6px; justify-content:flex-end; }
|
||||
.thumb { width: 60px; height: 60px; border-radius: 4px; background:#f5f5f5; }
|
||||
.tag-view { margin-top: 8px; display:flex; justify-content:flex-end; }
|
||||
|
||||
/* 新增:统计与图表布局样式 */
|
||||
.stats { margin: 10px 0; }
|
||||
.stat { background:#fff; border-radius:8px; padding:10px; display:flex; flex-direction:column; align-items:center }
|
||||
.stat-label { color:#666; font-size:12px }
|
||||
.stat-value { color:#1677ff; font-weight:bold; font-size:18px }
|
||||
.charts { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
||||
@media (min-width: 380px) { .charts { grid-template-columns: 1fr 1fr; } }
|
||||
.chart-box { background: #fff; border-radius: 8px; padding: 6px; }
|
||||
</style>
|
||||
171
pages/work/inspection/checkInUserList/index.vue
Normal file
171
pages/work/inspection/checkInUserList/index.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<uni-section title="用户打卡列表" type="line" class="mb-10">
|
||||
<template v-slot:right>
|
||||
<button class="ml-6" size="mini" type="primary" @click="handleQuery">搜索</button>
|
||||
<button class="ml-6" size="mini" @click="resetQuery">重置</button>
|
||||
</template>
|
||||
</uni-section>
|
||||
|
||||
<view class="filters">
|
||||
<uni-datetime-picker type="daterange" v-model="dateRange" :clear-icon="true" start="1990-01-01" end="2099-12-31" />
|
||||
</view>
|
||||
|
||||
<uni-card v-for="item in list" :key="item.id || item.recordId" :title="item.inspectionPoint" @click="onCardClick($event, item)">
|
||||
<uni-row class="row" :width="730">
|
||||
<uni-col :span="16">
|
||||
<view>
|
||||
<text class="uni-body">打卡时间:{{ formatDate(item.inspectionTime) }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">巡检人:{{ item.inspectorUser || item.inspectorId || '-' }}</text>
|
||||
</view>
|
||||
<view v-if="item.remark">
|
||||
<text class="uni-body">备注:{{ item.remark }}</text>
|
||||
</view>
|
||||
</uni-col>
|
||||
<uni-col :span="8">
|
||||
<view class="thumbs">
|
||||
<image v-for="(img,idx) in firstThreeImages(item.inspectionImg || item.clockInImg || item.signImg)" :key="idx" :src="imageUrl(img)" mode="aspectFill" class="thumb" />
|
||||
</view>
|
||||
</uni-col>
|
||||
</uni-row>
|
||||
</uni-card>
|
||||
<uni-load-more :status="loadStatus" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listRecord } from '@/api/inspection/record.js';
|
||||
import config from '@/config';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loadStatus: 'more',
|
||||
loading: false,
|
||||
total: 0,
|
||||
list: [],
|
||||
dateRange: [],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
inspectionPoint: '',
|
||||
inspectorUser: this.$store?.state?.user?.nickName || '',
|
||||
params: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.loadStatus !== 'more' || this.loading) return
|
||||
this.loadStatus = 'loading'
|
||||
this.queryParams.pageNum += 1
|
||||
this.fetchList({ append: true })
|
||||
},
|
||||
onShow() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
buildParams() {
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
this.queryParams.params = {
|
||||
beginTime: this.dateRange[0],
|
||||
endTime: this.dateRange[1]
|
||||
}
|
||||
} else {
|
||||
this.queryParams.params = {}
|
||||
}
|
||||
},
|
||||
imageUrl(path) {
|
||||
if (!path) return ''
|
||||
if (/^https?:\/\//.test(path)) return path
|
||||
return config.baseUrl + (path.startsWith('/') ? path : ('/' + path))
|
||||
},
|
||||
firstThreeImages(imgs) {
|
||||
if (!imgs) return []
|
||||
if (Array.isArray(imgs)) return imgs.slice(0,3)
|
||||
if (typeof imgs === 'string') return imgs.split(',').filter(Boolean).slice(0,3)
|
||||
return []
|
||||
},
|
||||
formatDate(val) {
|
||||
if (!val) return ''
|
||||
try {
|
||||
const d = new Date(val)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth()+1).padStart(2,'0')
|
||||
const dd = String(d.getDate()).padStart(2,'0')
|
||||
const hh = String(d.getHours()).padStart(2,'0')
|
||||
const mm = String(d.getMinutes()).padStart(2,'0')
|
||||
const ss = String(d.getSeconds()).padStart(2,'0')
|
||||
return `${y}-${m}-${dd} ${hh}:${mm}:${ss}`
|
||||
} catch(e) { return val }
|
||||
},
|
||||
async fetchList({ append = false } = {}) {
|
||||
try {
|
||||
this.loading = true
|
||||
this.buildParams()
|
||||
const res = await listRecord(this.queryParams)
|
||||
const rows = res?.rows || []
|
||||
this.total = typeof res?.total === 'number' ? res.total : (append ? this.list.length + rows.length : rows.length)
|
||||
if (append) {
|
||||
this.list = this.list.concat(rows)
|
||||
} else {
|
||||
this.list = rows
|
||||
}
|
||||
if (typeof res?.total === 'number') {
|
||||
this.loadStatus = this.list.length < this.total ? 'more' : 'noMore'
|
||||
} else {
|
||||
const hasMore = rows.length === this.queryParams.pageSize
|
||||
this.loadStatus = hasMore ? 'more' : 'noMore'
|
||||
}
|
||||
} catch (e) {
|
||||
this.loadStatus = 'noMore'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getList() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.loadStatus = 'loading'
|
||||
await this.fetchList({ append: false })
|
||||
},
|
||||
onCardClick(type, item) {
|
||||
if (!item) return
|
||||
this.goDetail(item)
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
inspectionPoint: '',
|
||||
inspectorUser: this.$store?.state?.user?.nickName || '',
|
||||
params: {}
|
||||
}
|
||||
this.dateRange = []
|
||||
this.getList()
|
||||
},
|
||||
goDetail(item) {
|
||||
const id = item?.id || item?.recordId
|
||||
if (!id) return
|
||||
this.$tab.navigateTo(`/pages/work/inspection/checkInDetail/index?id=${id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container { padding: 10px; }
|
||||
.filters { display: flex; gap: 8px; align-items: center; margin-bottom: 10px; }
|
||||
.thumbs { display: flex; gap: 6px; justify-content: flex-end; }
|
||||
.thumb { width: 60px; height: 60px; border-radius: 6px; background: #f5f5f5; }
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
.ml-6 { margin-left: 6px; }
|
||||
.row { }
|
||||
</style>
|
||||
@@ -34,8 +34,8 @@
|
||||
listData
|
||||
} from '@/api/system/dict/data.js'
|
||||
import {
|
||||
addRecord
|
||||
} from "@/api/inspection/record.js"
|
||||
addAbnormal
|
||||
} from "@/api/inspection/abnormal.js"
|
||||
import { addWatermarkToImage } from "@/utils/watermark.js"
|
||||
export default {
|
||||
// vue
|
||||
@@ -109,8 +109,9 @@
|
||||
submit() {
|
||||
this.$refs.dynamicForm.validate().then(res => {
|
||||
this.form.inspectionImg = this.joinList()
|
||||
this.form.inspectionTime = new Date() // 添加当前时间
|
||||
console.log("this.form",this.form);
|
||||
addRecord(this.form).then(res => {
|
||||
addAbnormal(this.form).then(res => {
|
||||
console.log(res);
|
||||
if (res.code==200) {
|
||||
uni.showToast({
|
||||
|
||||
295
pages/work/inspection/recordList/index.vue
Normal file
295
pages/work/inspection/recordList/index.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<uni-section title="巡检记录" type="line" class="mb-10">
|
||||
<template v-slot:right>
|
||||
<button class="ml-6" size="mini" type="primary" @click="handleQuery">搜索</button>
|
||||
<button class="ml-6" size="mini" @click="resetQuery">重置</button>
|
||||
</template>
|
||||
</uni-section>
|
||||
|
||||
<view class="filters">
|
||||
<uni-datetime-picker type="daterange" v-model="dateRange" :clear-icon="true" start="1990-01-01" end="2099-12-31" />
|
||||
<uni-data-select class="ml-6" v-model="pointValue" :localdata="pointOptions" placeholder="巡检点" @change="onPointChange" />
|
||||
</view>
|
||||
|
||||
<!-- 数据卡片概览 -->
|
||||
<uni-row :width="730" class="stats">
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">总打卡</text><text class="stat-value">{{ stats.total }}</text></view></uni-col>
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">活跃天</text><text class="stat-value">{{ stats.activeDays }}</text></view></uni-col>
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">平均/天</text><text class="stat-value">{{ stats.avgPerDay }}</text></view></uni-col>
|
||||
<uni-col :span="6"><view class="stat"><text class="stat-label">最长连打</text><text class="stat-value">{{ stats.maxStreak }}</text></view></uni-col>
|
||||
</uni-row>
|
||||
|
||||
<!-- 统计概览 -->
|
||||
<uni-section title="统计概览" type="line" class="mb-10" />
|
||||
<view class="charts">
|
||||
<view class="chart-box">
|
||||
<qiun-data-charts type="line" :opts="optsLine" :chartData="chartDaily" :errorShow="false" />
|
||||
</view>
|
||||
<view class="chart-box">
|
||||
<qiun-data-charts type="ring" :opts="optsRing" :chartData="chartPointTop" :errorShow="false" />
|
||||
</view>
|
||||
<view class="chart-box">
|
||||
<qiun-data-charts type="column" :opts="optsBar" :chartData="chartWeek" :errorShow="false" />
|
||||
</view>
|
||||
<view class="chart-box">
|
||||
<qiun-data-charts type="column" :opts="optsBar" :chartData="chartHour" :errorShow="false" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<uni-card v-for="item in displayList" :key="item.id || item.recordId" :title="item.inspectionPoint" @click="onCardClick($event, item)">
|
||||
<uni-row class="row" :width="730">
|
||||
<uni-col :span="16">
|
||||
<view>
|
||||
<text class="uni-body">巡检人:{{ item.inspectorUser || item.inspectorId || '-' }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">巡检时间:{{ formatDate(item.inspectionTime) }}</text>
|
||||
</view>
|
||||
<view v-if="item.remark">
|
||||
<text class="uni-body">备注:{{ item.remark }}</text>
|
||||
</view>
|
||||
</uni-col>
|
||||
<uni-col :span="8">
|
||||
<view class="thumbs">
|
||||
<image v-for="(img,idx) in firstThreeImages(item.inspectionImg || item.clockInImg || item.signImg)" :key="idx" :src="imageUrl(img)" mode="aspectFill" class="thumb" @click.stop="previewImages(item.inspectionImg || item.clockInImg || item.signImg, idx)" />
|
||||
</view>
|
||||
</uni-col>
|
||||
</uni-row>
|
||||
</uni-card>
|
||||
<uni-load-more :status="loadStatus" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listRecord } from '@/api/inspection/record.js'
|
||||
import config from '@/config'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
displayList: [],
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
loadStatus: 'more',
|
||||
dateRange: [],
|
||||
pointOptions: [{ text: '全部', value: '' }],
|
||||
pointValue: '',
|
||||
stats: { total: 0, activeDays: 0, avgPerDay: 0, maxStreak: 0 },
|
||||
// 图表配置与数据
|
||||
optsLine: { xAxis: { disableGrid: true }, yAxis: { disableGrid: false }, legend: { show: false }, extra: { line: { width: 2 } } },
|
||||
chartDaily: { categories: [], series: [{ name: '打卡次数', data: [] }] },
|
||||
optsRing: { legend: { position: 'bottom' } },
|
||||
chartPointTop: { series: [{ name: '巡检点占比', data: [] }] },
|
||||
optsBar: { legend: { show: false }, xAxis: { disableGrid: true }, yAxis: { disableGrid: false } },
|
||||
chartWeek: { categories: [], series: [{ name: '按周几', data: [] }] },
|
||||
chartHour: { categories: [], series: [{ name: '按时段', data: [] }] },
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.resetQuery()
|
||||
},
|
||||
methods: {
|
||||
async fetchList(reset = false) {
|
||||
if (reset) { this.pageNum = 1; this.list = []; this.loadStatus = 'loading' }
|
||||
const params = this.buildParams()
|
||||
try {
|
||||
const res = await listRecord(params)
|
||||
const rows = res?.rows || res?.data?.rows || []
|
||||
this.total = res?.total || res?.data?.total || rows.length
|
||||
this.list = reset ? rows : this.list.concat(rows)
|
||||
this.loadStatus = this.list.length >= this.total ? 'noMore' : 'more'
|
||||
this.refreshPointOptions()
|
||||
this.applyDisplayList()
|
||||
// 生成统计数据(基于过滤后列表)
|
||||
const used = this.displayList
|
||||
this.buildDailyChart(used)
|
||||
this.buildPointTopChart(used)
|
||||
this.buildWeekChart(used)
|
||||
this.buildHourChart(used)
|
||||
this.buildStats(used)
|
||||
} catch (err) {
|
||||
console.error('fetchList error', err)
|
||||
this.loadStatus = 'noMore'
|
||||
}
|
||||
},
|
||||
onPointChange() { this.applyDisplayList(); const used = this.displayList; this.buildDailyChart(used); this.buildPointTopChart(used); this.buildWeekChart(used); this.buildHourChart(used); this.buildStats(used) },
|
||||
refreshPointOptions() {
|
||||
const set = new Set(this.list.map(i => i.inspectionPoint).filter(Boolean))
|
||||
const arr = Array.from(set).map(n => ({ text: n, value: n }))
|
||||
this.pointOptions = [{ text: '全部', value: '' }, ...arr]
|
||||
},
|
||||
applyDisplayList() {
|
||||
this.displayList = this.pointValue ? this.list.filter(i => i.inspectionPoint === this.pointValue) : this.list.slice()
|
||||
},
|
||||
// ===== 图表数据构建 =====
|
||||
buildDailyChart(rows) {
|
||||
if (!rows || !rows.length) { this.chartDaily = { categories: [], series: [{ name: '打卡次数', data: [] }] }; return }
|
||||
const map = new Map()
|
||||
rows.forEach(r => { const d = this.formatDate(r.inspectionTime).slice(0, 10); map.set(d, (map.get(d) || 0) + 1) })
|
||||
const categories = Array.from(map.keys()).sort()
|
||||
const data = categories.map(d => map.get(d))
|
||||
this.chartDaily = { categories, series: [{ name: '打卡次数', data }] }
|
||||
},
|
||||
buildPointTopChart(rows) {
|
||||
if (!rows || !rows.length) { this.chartPointTop = { series: [{ name: '巡检点占比', data: [] }] }; return }
|
||||
const map = new Map()
|
||||
rows.forEach(r => { const p = r.inspectionPoint || '未知点位'; map.set(p, (map.get(p) || 0) + 1) })
|
||||
const sorted = Array.from(map.entries()).sort((a,b)=>b[1]-a[1]).slice(0,8)
|
||||
const data = sorted.map(([name, value]) => ({ name, value }))
|
||||
this.chartPointTop = { series: [{ name: '巡检点占比', data }] }
|
||||
},
|
||||
buildWeekChart(rows) {
|
||||
const names = ['周一','周二','周三','周四','周五','周六','周日']
|
||||
const counts = Array(7).fill(0)
|
||||
rows.forEach(r => { const d = new Date(r.inspectionTime); const idx = (d.getDay()+6)%7; counts[idx]++ })
|
||||
this.chartWeek = { categories: names, series: [{ name: '按周几', data: counts }] }
|
||||
},
|
||||
buildHourChart(rows) {
|
||||
const buckets = Array(24).fill(0)
|
||||
rows.forEach(r => { const d = new Date(r.inspectionTime); buckets[d.getHours()]++ })
|
||||
const labels = buckets.map((_,h)=> (h<10?('0'+h):h)+':00')
|
||||
this.chartHour = { categories: labels, series: [{ name: '按时段', data: buckets }] }
|
||||
},
|
||||
buildStats(rows) {
|
||||
const total = rows.length
|
||||
const daySet = new Set(rows.map(r => this.formatDate(r.inspectionTime).slice(0,10)))
|
||||
const activeDays = daySet.size
|
||||
const avgPerDay = activeDays ? (total/activeDays).toFixed(2) : 0
|
||||
// 计算最长连续天数
|
||||
const days = Array.from(daySet).sort()
|
||||
let maxStreak = 0, cur = 0, prev = null
|
||||
const toDate = s => new Date(s+'T00:00:00')
|
||||
days.forEach(s => { if (!prev) { cur=1; } else { const diff=(toDate(s)-toDate(prev))/(24*3600*1000); cur = diff===1 ? cur+1 : 1 } maxStreak = Math.max(maxStreak, cur); prev = s })
|
||||
this.stats = { total, activeDays, avgPerDay, maxStreak }
|
||||
},
|
||||
buildParams() {
|
||||
const params = {
|
||||
pageNum: this.pageNum,
|
||||
pageSize: this.pageSize,
|
||||
}
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
params.params = {
|
||||
beginTime: this.dateRange[0],
|
||||
endTime: this.dateRange[1]
|
||||
}
|
||||
}
|
||||
return params
|
||||
},
|
||||
handleQuery() {
|
||||
this.fetchList(true)
|
||||
},
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.fetchList(true)
|
||||
},
|
||||
onReachBottom() {
|
||||
if (this.loadStatus !== 'more') return
|
||||
this.pageNum += 1
|
||||
this.fetchList(false)
|
||||
},
|
||||
onCardClick(e, item) {
|
||||
const id = item.id || item.recordId
|
||||
if (!id) return
|
||||
this.$tab.navigateTo(`/pages/work/inspection/checkInDetail/index?id=${id}`)
|
||||
},
|
||||
// ===== 工具方法 =====
|
||||
buildDailyChart(rows) {
|
||||
if (!rows || !rows.length) {
|
||||
this.chartDaily = { categories: [], series: [{ name: '打卡次数', data: [] }] }
|
||||
return
|
||||
}
|
||||
const map = new Map()
|
||||
rows.forEach(r => {
|
||||
const d = this.formatDate(r.inspectionTime).slice(0, 10)
|
||||
map.set(d, (map.get(d) || 0) + 1)
|
||||
})
|
||||
const categories = Array.from(map.keys()).sort()
|
||||
const data = categories.map(d => map.get(d))
|
||||
this.chartDaily = { categories, series: [{ name: '打卡次数', data }] }
|
||||
},
|
||||
buildPointTopChart(rows) {
|
||||
if (!rows || !rows.length) {
|
||||
this.chartPointTop = { series: [{ name: '巡检点占比', data: [] }] }
|
||||
return
|
||||
}
|
||||
const map = new Map()
|
||||
rows.forEach(r => {
|
||||
const p = r.inspectionPoint || '未知点位'
|
||||
map.set(p, (map.get(p) || 0) + 1)
|
||||
})
|
||||
const sorted = Array.from(map.entries()).sort((a,b)=>b[1]-a[1]).slice(0,8)
|
||||
const data = sorted.map(([name, value]) => ({ name, value }))
|
||||
this.chartPointTop = { series: [{ name: '巡检点占比', data }] }
|
||||
},
|
||||
// ===== 工具方法 =====
|
||||
firstThreeImages(imgs) {
|
||||
if (!imgs) return []
|
||||
const arr = Array.isArray(imgs) ? imgs : String(imgs).split(',').filter(Boolean)
|
||||
return arr.slice(0, 3)
|
||||
},
|
||||
imageUrl(name) {
|
||||
if (!name) return ''
|
||||
if (/^https?:\/\//.test(name)) return name
|
||||
return `${config.baseUrl}/profile/upload/${name}`
|
||||
},
|
||||
formatDate(val) {
|
||||
if (!val) return '-'
|
||||
const d = new Date(val)
|
||||
const pad = (n) => (n < 10 ? '0' + n : n)
|
||||
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
|
||||
},
|
||||
previewImages(imgs, idx) {
|
||||
const arr = this.firstThreeImages(imgs)
|
||||
const urls = arr.map(this.imageUrl)
|
||||
if (!urls.length) return
|
||||
uni.previewImage({
|
||||
current: urls[idx] || urls[0],
|
||||
urls
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.charts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
@media (min-width: 380px) {
|
||||
.charts { grid-template-columns: 1fr 1fr; }
|
||||
}
|
||||
.chart-box { background: #fff; border-radius: 8px; padding: 6px; }
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
.thumbs {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.thumb {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.stats { margin-bottom: 10px; }
|
||||
.stat { background:#fff; border-radius:8px; padding:10px; display:flex; flex-direction:column; align-items:center }
|
||||
.stat-label { color:#666; font-size:12px }
|
||||
.stat-value { color:#1677ff; font-weight:bold; font-size:18px }
|
||||
.mb-10 { margin-bottom: 10px; }
|
||||
.ml-6 { margin-left: 6px; }
|
||||
</style>
|
||||
@@ -41,6 +41,7 @@
|
||||
import { addRecord } from "@/api/inspection/record.js"
|
||||
import { addWatermarkToImage } from "@/utils/watermark.js"
|
||||
import appConfig from "@/config"
|
||||
import { getToken } from "@/utils/auth"
|
||||
|
||||
// ------- URL 安全拼接,避免出现 /undefined/common/upload -------
|
||||
function joinURL(base, path) {
|
||||
@@ -124,7 +125,8 @@
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.showMessage("error", `图片上传失败:${err.message || err}`)
|
||||
const msg = err?.errMsg || err?.message || (typeof err === "string" ? err : JSON.stringify(err))
|
||||
this.showMessage("error", `图片上传失败:${msg}`)
|
||||
} finally {
|
||||
this.isUploading = false
|
||||
}
|
||||
@@ -260,19 +262,45 @@
|
||||
url: UPLOAD_URL,
|
||||
name: "file",
|
||||
file, // H5 传 File;不要传 filePath
|
||||
header: { Authorization: "Bearer " + getToken() },
|
||||
success: (res) => {
|
||||
try {
|
||||
if (res.statusCode && res.statusCode !== 200) {
|
||||
throw new Error(`HTTP ${res.statusCode}`)
|
||||
}
|
||||
const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data
|
||||
if (data.code === 200) {
|
||||
if (data && data.code === 200) {
|
||||
resolve(data.fileName || data.url)
|
||||
} else {
|
||||
reject(new Error(data.msg || "上传失败"))
|
||||
reject(new Error((data && data.msg) || "上传失败"))
|
||||
}
|
||||
} catch (e) {
|
||||
reject(new Error("上传响应解析失败"))
|
||||
}
|
||||
},
|
||||
fail: (err) => reject(err)
|
||||
fail: async (err) => {
|
||||
const msg = err?.errMsg || "uploadFile fail"
|
||||
if (msg.includes("file error") || msg.includes("fail")) {
|
||||
try {
|
||||
const form = new FormData()
|
||||
form.append("file", file)
|
||||
const resp = await fetch(UPLOAD_URL, {
|
||||
method: "POST",
|
||||
headers: { Authorization: "Bearer " + getToken() },
|
||||
body: form
|
||||
})
|
||||
if (!resp.ok) return reject(new Error(`HTTP ${resp.status}`))
|
||||
const data = await resp.json()
|
||||
if (data && data.code === 200) {
|
||||
return resolve(data.fileName || data.url)
|
||||
}
|
||||
return reject(new Error((data && data.msg) || "上传失败"))
|
||||
} catch (e) {
|
||||
return reject(new Error(e?.message || msg))
|
||||
}
|
||||
}
|
||||
reject(new Error(msg))
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
@@ -298,7 +326,7 @@
|
||||
|
||||
dialogConfirm() {
|
||||
if (this.msgType === "success") {
|
||||
uni.reLaunch({ url: "/pages/work/index" })
|
||||
uni.reLaunch({ url: "/pages/work/inspection/recordList/index" })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
319
pages/work/sidebar/filingDetail/index.vue
Normal file
319
pages/work/sidebar/filingDetail/index.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<uni-section title="隐患申报详情" type="line" class="mb-10" />
|
||||
|
||||
<view v-if="detail.id" class="detail-content">
|
||||
<!-- 基本信息卡片 -->
|
||||
<uni-card title="基本信息" :is-shadow="true" class="info-card">
|
||||
<view class="info-grid">
|
||||
<view class="info-row">
|
||||
<text class="info-label">申报类型:</text>
|
||||
<text class="info-value">{{ detail.declarationLabel || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">申报人:</text>
|
||||
<text class="info-value">{{ detail.applyUser || '-' }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">申报时间:</text>
|
||||
<text class="info-value">{{ formatDate(detail.occurTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</uni-card>
|
||||
|
||||
<!-- 申报内容卡片 -->
|
||||
<uni-card title="申报内容" :is-shadow="true" class="content-card">
|
||||
<view class="content-text">
|
||||
{{ detail.requirementDescription || '暂无内容' }}
|
||||
</view>
|
||||
<view v-if="detail.remark" class="remark-section">
|
||||
<text class="remark-label">备注:</text>
|
||||
<text class="remark-text">{{ detail.remark }}</text>
|
||||
</view>
|
||||
</uni-card>
|
||||
|
||||
<!-- 图片展示卡片 -->
|
||||
<uni-card title="相关图片" :is-shadow="true" class="image-card" v-if="imageArray(detail.declarationImg).length > 0">
|
||||
<view class="image-grid">
|
||||
<view v-for="(img, idx) in imageArray(detail.declarationImg)" :key="idx" class="image-item">
|
||||
<image :src="imageUrl(img)" mode="aspectFill" class="detail-image" @click="previewImage(img, detail.declarationImg)" />
|
||||
</view>
|
||||
</view>
|
||||
</uni-card>
|
||||
|
||||
<!-- 无图片提示 -->
|
||||
<uni-card title="相关图片" :is-shadow="true" class="image-card" v-else>
|
||||
<view class="no-image-tip">
|
||||
<uni-icons type="image" size="40" color="#ccc"></uni-icons>
|
||||
<text class="no-image-text">暂无相关图片</text>
|
||||
</view>
|
||||
</uni-card>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons" v-if="detail.status === '0' || detail.status === 0">
|
||||
<button class="edit-btn" size="default" type="primary" @click="editDeclaration">编辑申报</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<view v-else-if="loading" class="loading-state">
|
||||
<uni-load-more status="loading" />
|
||||
</view>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<view v-else class="error-state">
|
||||
<uni-icons type="info" size="60" color="#ccc"></uni-icons>
|
||||
<text class="error-text">数据加载失败或记录不存在</text>
|
||||
<button size="mini" type="default" @click="goBack">返回列表</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSafetyDeclaration } from '@/api/sidebar/sidebar.js'
|
||||
import { listData } from '@/api/system/dict/data.js'
|
||||
import config from '@/config'
|
||||
|
||||
export default {
|
||||
name: "FilingDetail",
|
||||
data() {
|
||||
return {
|
||||
id: null,
|
||||
detail: {},
|
||||
loading: false,
|
||||
typeDict: []
|
||||
}
|
||||
},
|
||||
async onLoad(options) {
|
||||
this.id = options?.id || null
|
||||
if (this.id) {
|
||||
await this.initDict()
|
||||
await this.fetchDetail()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async initDict() {
|
||||
try {
|
||||
const typeData = await listData({ dictType: 'hs_declaration_type' })
|
||||
this.typeDict = typeData.rows || []
|
||||
} catch (e) {
|
||||
console.error('获取字典数据失败:', e)
|
||||
this.typeDict = []
|
||||
}
|
||||
},
|
||||
async fetchDetail() {
|
||||
if (!this.id) return
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getSafetyDeclaration(this.id)
|
||||
this.detail = res?.data || {}
|
||||
// 设置申报类型标签
|
||||
if (this.detail.declarationType) {
|
||||
const typeItem = this.typeDict.find(i => String(i.dictValue) === String(this.detail.declarationType))
|
||||
this.detail.declarationLabel = typeItem ? typeItem.dictLabel : this.detail.declarationType
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取详情失败:', e)
|
||||
uni.showToast({
|
||||
title: '获取详情失败',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
imageArray(declarationImg) {
|
||||
if (!declarationImg) return []
|
||||
if (typeof declarationImg === 'string') {
|
||||
return declarationImg.split(',').filter(Boolean)
|
||||
}
|
||||
return declarationImg.filter(Boolean)
|
||||
},
|
||||
imageUrl(path) {
|
||||
if (!path) return ''
|
||||
return config.baseUrl + path
|
||||
},
|
||||
previewImage(current, all) {
|
||||
const urls = this.imageArray(all).map(p => this.imageUrl(p))
|
||||
uni.previewImage({
|
||||
current: this.imageUrl(current),
|
||||
urls
|
||||
})
|
||||
},
|
||||
formatDate(val) {
|
||||
if (!val) return '-'
|
||||
try {
|
||||
const d = new Date(val)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(d.getDate()).padStart(2, '0')
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${y}-${m}-${dd} ${hh}:${mm}`
|
||||
} catch (e) {
|
||||
return val
|
||||
}
|
||||
},
|
||||
getStatusText(status) {
|
||||
const statusMap = {
|
||||
'0': '待处理',
|
||||
'1': '处理中',
|
||||
'2': '已完成',
|
||||
'3': '已关闭'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
},
|
||||
getStatusType(status) {
|
||||
const typeMap = {
|
||||
'0': 'error', // 待处理 - 红色
|
||||
'1': 'warning', // 处理中 - 橙色
|
||||
'2': 'success', // 已完成 - 绿色
|
||||
'3': 'info' // 已关闭 - 蓝色
|
||||
}
|
||||
return typeMap[status] || 'default'
|
||||
},
|
||||
editDeclaration() {
|
||||
if (!this.detail.id) return
|
||||
this.$tab.navigateTo(`/pages/work/sidebar/safetyDeclaratio/index?id=${this.detail.id}`)
|
||||
},
|
||||
goBack() {
|
||||
this.$tab.navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.container {
|
||||
padding: 15px;
|
||||
background-color: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.mb-10 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
.info-card,
|
||||
.content-card,
|
||||
.image-card {
|
||||
margin-bottom: 15px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
min-width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.remark-section {
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #eee;
|
||||
|
||||
.remark-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.remark-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.image-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
|
||||
.image-item {
|
||||
.detail-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 8px;
|
||||
background: #f5f5f5;
|
||||
border: 2px solid #fff;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-image-tip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
|
||||
.no-image-text {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-top: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
.edit-btn {
|
||||
width: 100%;
|
||||
border-radius: 25px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.error-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
|
||||
.error-text {
|
||||
margin: 15px 0;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,150 +1,239 @@
|
||||
<template>
|
||||
<view class="work-container">
|
||||
|
||||
<uni-card v-for="(item,index) of dataList" class="parent">
|
||||
<template v-slot:title>
|
||||
<h3 class="title">{{item.declarationLabel}}</h3>
|
||||
<view class="container">
|
||||
<uni-section title="异常隐患申报记录" type="line" class="mb-10">
|
||||
<template v-slot:right>
|
||||
<button size="mini" type="primary" @click.stop="addNew">新增</button>
|
||||
</template>
|
||||
<h3>{{item.requirementDescription}}</h3>
|
||||
<h3>{{item.occurTime}}</h3>
|
||||
<view class="card-actions-item" @click="actionsClick(item.id)">
|
||||
<button size="mini">详情</button>
|
||||
</view>
|
||||
</uni-card>
|
||||
<uni-load-more status="已经没有更多数据了"></uni-load-more>
|
||||
</uni-section>
|
||||
|
||||
<view class="filters">
|
||||
<uni-data-select v-model="queryParams.declarationType" :localdata="selectList" placeholder="申报类型" />
|
||||
<uni-datetime-picker type="daterange" v-model="dateRange" :clear-icon="true" start="1990-01-01" end="2099-12-31" />
|
||||
<button class="ml-6" size="mini" type="primary" @click="handleQuery">搜索</button>
|
||||
<button class="ml-6" size="mini" @click="resetQuery">重置</button>
|
||||
</view>
|
||||
|
||||
<uni-card v-for="item in dataList" :key="item.id" :title="item.declarationLabel || '异常隐患申报'" @click="onCardClick($event, item)">
|
||||
<uni-row class="row" :width="730">
|
||||
<uni-col :span="16">
|
||||
<view>
|
||||
<text class="uni-body">申报人:{{ item.applyUser || '-' }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">申报时间:{{ formatDate(item.occurTime) }}</text>
|
||||
</view>
|
||||
<view>
|
||||
<text class="uni-body">备注:{{ item.remark || '-' }}</text>
|
||||
</view>
|
||||
</uni-col>
|
||||
<uni-col :span="8">
|
||||
<view class="thumbs">
|
||||
<image v-for="(img,idx) in firstThreeImages(item.declarationImg)" :key="idx" :src="imageUrl(img)" mode="aspectFill" class="thumb" />
|
||||
</view>
|
||||
</uni-col>
|
||||
</uni-row>
|
||||
</uni-card>
|
||||
<uni-load-more :status="loadStatus" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listData
|
||||
} from '@/api/system/dict/data.js'
|
||||
import {
|
||||
listSafetyDeclaration
|
||||
} from "@/api/sidebar/sidebar.js"
|
||||
import formatTime from '@/utils/formatTime.js'
|
||||
import { listSafetyDeclaration } from '@/api/sidebar/sidebar.js';
|
||||
import { listData } from '@/api/system/dict/data.js';
|
||||
import config from '@/config';
|
||||
|
||||
export default {
|
||||
name: "FilingLog",
|
||||
data() {
|
||||
return {
|
||||
loadStatus: 'more',
|
||||
loading: false,
|
||||
total: 0,
|
||||
dataList: [],
|
||||
selectList: [],
|
||||
dateRange: [],
|
||||
queryParams: {
|
||||
pageNum: 1, // 初始页码设为1
|
||||
pageSize: 10
|
||||
},
|
||||
isLoading: false, // 添加一个标志位,用于防止重复请求
|
||||
typeDataList: [],
|
||||
total: 0
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
declarationType: null,
|
||||
params: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initDict()
|
||||
this.getList()
|
||||
},
|
||||
// 触底加载更多(页面滚动到底部)
|
||||
onReachBottom() {
|
||||
if (this.loadStatus !== 'more' || this.loading) return
|
||||
this.loadStatus = 'loading'
|
||||
this.queryParams.pageNum += 1
|
||||
this.fetchList({ append: true })
|
||||
},
|
||||
// 页面重新显示时自动刷新列表(新增返回后生效)
|
||||
onShow() {
|
||||
this.getList()
|
||||
},
|
||||
methods: {
|
||||
actionsClick(e) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/work/sidebar/safetyDeclaratio/index?id=` + e
|
||||
})
|
||||
async initDict() {
|
||||
try {
|
||||
const typeData = await listData({ dictType: 'hs_declaration_type' })
|
||||
this.selectList = typeData.rows.map(i => ({ value: i.dictValue, text: i.dictLabel }))
|
||||
} catch (e) {
|
||||
console.error('获取字典数据失败:', e)
|
||||
this.selectList = []
|
||||
}
|
||||
},
|
||||
imageUrl(path) {
|
||||
if (!path) return ''
|
||||
return config.baseUrl + path
|
||||
},
|
||||
firstThreeImages(declarationImg) {
|
||||
if (!declarationImg) return []
|
||||
if (typeof declarationImg === 'string') {
|
||||
return declarationImg.split(',').slice(0, 3)
|
||||
}
|
||||
return declarationImg.slice(0, 3)
|
||||
},
|
||||
formatDate(val) {
|
||||
if (!val) return ''
|
||||
try {
|
||||
const d = new Date(val)
|
||||
const y = d.getFullYear()
|
||||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||||
const dd = String(d.getDate()).padStart(2, '0')
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
const ss = String(d.getSeconds()).padStart(2, '0')
|
||||
return `${y}-${m}-${dd} ${hh}:${mm}:${ss}`
|
||||
} catch (e) {
|
||||
return val
|
||||
}
|
||||
},
|
||||
buildParams() {
|
||||
if (this.dateRange && this.dateRange.length === 2) {
|
||||
this.queryParams.params = {
|
||||
beginTime: this.dateRange[0],
|
||||
endTime: this.dateRange[1]
|
||||
}
|
||||
} else {
|
||||
this.queryParams.params = {}
|
||||
}
|
||||
},
|
||||
async fetchList({ append = false } = {}) {
|
||||
try {
|
||||
this.loading = true
|
||||
this.buildParams()
|
||||
const res = await listSafetyDeclaration(this.queryParams)
|
||||
const rows = res?.rows || []
|
||||
// 若接口提供 total 字段则使用,否则根据 pageSize 判断是否还有更多
|
||||
this.total = typeof res?.total === 'number' ? res.total : (append ? this.dataList.length + rows.length : rows.length)
|
||||
if (append) {
|
||||
this.dataList = this.dataList.concat(rows)
|
||||
} else {
|
||||
this.dataList = rows
|
||||
}
|
||||
// 根据是否还有更多数据设置加载状态
|
||||
if (typeof res?.total === 'number') {
|
||||
this.loadStatus = this.dataList.length < this.total ? 'more' : 'noMore'
|
||||
} else {
|
||||
// 当本次返回数量等于 pageSize,默认还有下一页
|
||||
const hasMore = rows.length === this.queryParams.pageSize
|
||||
this.loadStatus = hasMore ? 'more' : 'noMore'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取列表失败:', e)
|
||||
this.loadStatus = 'noMore'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async getList() {
|
||||
if (this.isLoading) return; // 如果已经在加载中,则不执行
|
||||
this.isLoading = true;
|
||||
const res = await listSafetyDeclaration(this.queryParams);
|
||||
if (this.typeDataList.length == 0) {
|
||||
this.typeDataList = await listData({
|
||||
dictType: "hs_declaration_type"
|
||||
});
|
||||
// 初始化查询第一页
|
||||
this.queryParams.pageNum = 1
|
||||
this.loadStatus = 'loading'
|
||||
await this.fetchList({ append: false })
|
||||
},
|
||||
onCardClick(type, item) {
|
||||
// 仅在内容区域点击时跳转(title/extra也可按需)
|
||||
if (!item) return
|
||||
this.goDetail(item)
|
||||
},
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getList()
|
||||
},
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
declarationType: null,
|
||||
params: {}
|
||||
}
|
||||
this.total = res.total
|
||||
let processedDataList = res.rows.map(item => {
|
||||
const matchedType = this.typeDataList.rows.find(type => type.dictValue == item.declarationType);
|
||||
const time = formatTime(item.occurTime);
|
||||
return {
|
||||
...item,
|
||||
occurTime: time,
|
||||
declarationLabel: matchedType ? matchedType.dictLabel : "未知类型"
|
||||
};
|
||||
});
|
||||
// 合并新旧数据
|
||||
this.dataList = [...this.dataList, ...processedDataList];
|
||||
this.dateRange = []
|
||||
this.getList()
|
||||
},
|
||||
onLoad(options) {
|
||||
this.getList();
|
||||
addNew() {
|
||||
this.$tab.navigateTo('/pages/work/sidebar/safetyDeclaratio/index')
|
||||
},
|
||||
},
|
||||
onReachBottom() {
|
||||
this.isLoading = false;
|
||||
//上拉到底时触发,onReachBottom函数跟生命周期同级
|
||||
let allTotal = this.queryParams.pageNum * this.queryParams.pageSize
|
||||
console.log("allTotal",allTotal);
|
||||
console.log("this.total",this.total);
|
||||
if (allTotal < this.total) {
|
||||
this.queryParams.pageNum++;
|
||||
// this.queryParams.page++//当前条数小于总条数 则增加请求页数
|
||||
this.getList() //调用加载数据方法
|
||||
setTimeout(() => {
|
||||
//结束下拉刷新
|
||||
uni.stopPullDownRefresh();
|
||||
}, 1000);
|
||||
} else {
|
||||
console.log('已加载全部数据')
|
||||
goDetail(item) {
|
||||
const id = item?.id
|
||||
if (!id) return
|
||||
this.$tab.navigateTo(`/pages/work/sidebar/filingDetail/index?id=${id}`)
|
||||
}
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
this.queryParams.pageNum=1;
|
||||
this.isLoading = false;
|
||||
//下拉刷新触发,onPullDownRefresh函数跟生命周期同级
|
||||
this.dataList = []
|
||||
this.getList().then(res=>{
|
||||
setTimeout(() => {
|
||||
//结束下拉刷新
|
||||
uni.stopPullDownRefresh();
|
||||
}, 1000);
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
.container {
|
||||
padding: 10px;
|
||||
background-color: #f8f9fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
padding: 15px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.thumbs {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
.work-container {
|
||||
margin-left: 4px
|
||||
.thumb {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 8px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.parent {
|
||||
position: relative;
|
||||
|
||||
h3 {
|
||||
.uni-body {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
font-weight:600;
|
||||
color: rgb(153, 153, 153);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight:bold;
|
||||
padding: 3rpx;
|
||||
color: black;
|
||||
}
|
||||
// 工具类
|
||||
.mb-10 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.card-actions-item {
|
||||
display: inline-block;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
right: 30px;
|
||||
button {
|
||||
border-radius: 10px;
|
||||
}
|
||||
.ml-6 {
|
||||
margin-left: 6px;
|
||||
}
|
||||
</style>
|
||||
@@ -135,6 +135,8 @@
|
||||
},
|
||||
submit(ref) {
|
||||
this.$refs[ref].validate().then(res => {
|
||||
// 绑定当前用户为申报人
|
||||
this.SafetyDeclarationTable.applyUser = this.$store.getters.name
|
||||
this.SafetyDeclarationTable.declarationImg = this.joinList()
|
||||
console.log("提交成功");
|
||||
console.log("this.SafetyDeclarationTable", this.SafetyDeclarationTable);
|
||||
|
||||
@@ -5,7 +5,7 @@ const loginPage = "/pages/login"
|
||||
|
||||
// 页面白名单
|
||||
const whiteList = [
|
||||
'/pages/login', '/pages/register', '/pages/common/webview/index'
|
||||
'/pages/login', '/pages/register', '/pages/common/webview/index', '/pages/cas/callback'
|
||||
]
|
||||
|
||||
// 检查地址白名单
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import config from '@/config'
|
||||
import storage from '@/utils/storage'
|
||||
import constant from '@/utils/constant'
|
||||
import { login, logout, getInfo } from '@/api/login'
|
||||
import { login, logout, getInfo, casLogin, casAppLogin, casUnifiedLogin } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
|
||||
const baseUrl = config.baseUrl
|
||||
@@ -59,6 +59,43 @@ const user = {
|
||||
})
|
||||
})
|
||||
},
|
||||
// CAS 票据登录
|
||||
CasLogin({ commit }, payload) {
|
||||
const { ticket, service } = payload || {}
|
||||
return new Promise((resolve, reject) => {
|
||||
casLogin(ticket, service).then(res => {
|
||||
const token = res.token || res.data?.token
|
||||
if (token) {
|
||||
setToken(token)
|
||||
commit('SET_TOKEN', token)
|
||||
resolve(res)
|
||||
} else {
|
||||
reject('CAS 登录未返回令牌')
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 移动端CAS票据登录
|
||||
CasAppLogin({ commit }, ticket) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 使用统一CAS登录接口
|
||||
casUnifiedLogin(ticket).then(res => {
|
||||
const token = res.token || res.data?.token
|
||||
if (token) {
|
||||
setToken(token)
|
||||
commit('SET_TOKEN', token)
|
||||
resolve(res)
|
||||
} else {
|
||||
reject('移动端CAS登录未返回令牌')
|
||||
}
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
tansParams
|
||||
} from '@/utils/common'
|
||||
|
||||
let timeout = 10000
|
||||
const baseUrl = 'http://172.20.10.2:8081'
|
||||
let timeout = 30000
|
||||
const baseUrl = appConfig.baseUrl
|
||||
|
||||
const request = config => {
|
||||
// 是否需要设置 token
|
||||
@@ -30,7 +30,7 @@ const request = config => {
|
||||
uni.request({
|
||||
method: config.method || 'get',
|
||||
timeout: config.timeout || timeout,
|
||||
url: appConfig.baseUrl + config.url || baseUrl + config.url,
|
||||
url: baseUrl + config.url,
|
||||
data: config.data,
|
||||
header: config.header,
|
||||
dataType: 'json'
|
||||
|
||||
Reference in New Issue
Block a user