This commit is contained in:
@@ -27,8 +27,8 @@ const config = {
|
||||
// config.baseUrl = 'http://tc.cqsznc.com:7080/api';
|
||||
|
||||
//正式
|
||||
config.baseUrl = 'http://183.230.235.66:11010/api';
|
||||
// config.baseUrl = 'http://183.230.235.66:11010/api';
|
||||
|
||||
|
||||
// config.baseUrl = 'http://378a061a.r28.cpolar.top'
|
||||
config.baseUrl = 'http://799004da.r28.cpolar.top';
|
||||
export default config;
|
||||
|
@@ -64,7 +64,17 @@ const install = (Vue, vm) => {
|
||||
|
||||
getImageUrl:(params = {}, ossIds) => vm.$u.get(config.adminPath+`/resource/oss/listByIds/${ossIds}`,params),
|
||||
//巡检任务列表
|
||||
getInspection:(params = {})=>vm.$u.get(config.adminPath+'/property/item/list',params),
|
||||
getInspection:(params = {})=>vm.$u.get(config.adminPath+'/property/mobile/inspectionTask/list',params),
|
||||
|
||||
// getTaskList:(params = {},taskId)=>vm.$u.get(config.adminPath+`/property/mobile/taskDetail/list/${taskId}`,params),
|
||||
//巡检任务
|
||||
getTaskList:(params = {})=>vm.$u.get(config.adminPath+'/property/mobile/taskDetail/list',params),
|
||||
//巡检签到
|
||||
taskSignIn:(params = {})=>vm.$u.post(config.adminPath+'/property/mobile/taskDetail/signIn',params),
|
||||
//巡检提交
|
||||
taskSubmit:(params = {})=>vm.$u.post(config.adminPath+'/property/mobile/taskDetail/submit',params),
|
||||
//巡检工单提报
|
||||
taskOrderSubmit:(params = {})=>vm.$u.post(config.adminPath+'/property/mobile/taskDetail/reportedOrder',params),
|
||||
|
||||
// 基础服务:登录登出、身份信息、菜单授权、切换系统、字典数据等
|
||||
lang: (params = {}) => vm.$u.get('/lang/'+params.lang),
|
||||
|
300
components/CommonCalendar.vue
Normal file
300
components/CommonCalendar.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<view class="calendar-container">
|
||||
<!-- 展开/收起按钮,可以外部隐藏 -->
|
||||
<view class="calendar-header">
|
||||
<button size="mini" @click="toggleMode">
|
||||
{{ mode === 'month' ? '收起为周' : '展开为月' }}
|
||||
</button>
|
||||
<text>{{ displayTitle }}</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="calendar-content"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
:style="{ height: mode === 'month' ? '450rpx' : '120rpx', transition: 'height 0.3s' }"
|
||||
>
|
||||
<!-- 星期栏 -->
|
||||
<view class="calendar-week">
|
||||
<text v-for="(w, i) in weeks" :key="i">{{ w }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 日期 -->
|
||||
<view class="calendar-days">
|
||||
<view
|
||||
v-for="(item, i) in renderDays"
|
||||
:key="i"
|
||||
:class="{
|
||||
'calendar-day': true,
|
||||
'today': item.isToday,
|
||||
'selected': isSelected(item),
|
||||
'other-month': item.otherMonth
|
||||
}"
|
||||
@click="selectDate(item)"
|
||||
>
|
||||
{{ item.day || '' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CustomCalendar',
|
||||
props: {
|
||||
initialMode: { type: String, default: 'month' }, // 'month' or 'week'
|
||||
initialDate: { type: String, default: '' },
|
||||
allowWeekSwitch: { type: Boolean, default: true } // 控制是否允许上下滑切换
|
||||
},
|
||||
data() {
|
||||
const today = new Date()
|
||||
return {
|
||||
mode: this.initialMode,
|
||||
selected: this.initialDate || this.formatDate(today),
|
||||
weeks: ['日', '一', '二', '三', '四', '五', '六'],
|
||||
renderDays: [],
|
||||
touchStartX: 0,
|
||||
touchStartY: 0,
|
||||
touchMoveX: 0,
|
||||
touchMoveY: 0,
|
||||
anchorDate: new Date() // 当前视图基准日期
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
displayTitle() {
|
||||
return `${this.anchorDate.getFullYear()}年${this.anchorDate.getMonth() + 1}月`
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
mode() {
|
||||
this.generateRenderDays()
|
||||
},
|
||||
selected() {
|
||||
this.generateRenderDays()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.anchorDate = this.safeDate(this.selected)
|
||||
this.generateRenderDays()
|
||||
},
|
||||
methods: {
|
||||
// =================== 日期工具 ===================
|
||||
formatDate(date) {
|
||||
const y = date.getFullYear()
|
||||
const m = date.getMonth() + 1
|
||||
const d = date.getDate()
|
||||
return `${y}-${m < 10 ? '0' + m : m}-${d < 10 ? '0' + d : d}`
|
||||
},
|
||||
safeDate(date) {
|
||||
return new Date(date)
|
||||
},
|
||||
startOfMonth(date) {
|
||||
const d = this.safeDate(date)
|
||||
d.setDate(1)
|
||||
return d
|
||||
},
|
||||
startOfWeek(date) {
|
||||
const d = this.safeDate(date)
|
||||
const day = d.getDay()
|
||||
d.setDate(d.getDate() - day)
|
||||
return d
|
||||
},
|
||||
pad(num) {
|
||||
return num < 10 ? '0' + num : '' + num
|
||||
},
|
||||
|
||||
// =================== 渲染日历 ===================
|
||||
generateRenderDays() {
|
||||
const days = []
|
||||
if (this.mode === 'month') {
|
||||
const year = this.anchorDate.getFullYear()
|
||||
const month = this.anchorDate.getMonth() + 1
|
||||
const firstDayWeek = new Date(year, month - 1, 1).getDay()
|
||||
const monthDays = new Date(year, month, 0).getDate()
|
||||
|
||||
// 上月补位
|
||||
const lastMonth = month === 1 ? 12 : month - 1
|
||||
const lastMonthYear = month === 1 ? year - 1 : year
|
||||
const lastMonthDays = new Date(lastMonthYear, lastMonth, 0).getDate()
|
||||
for (let i = 0; i < firstDayWeek; i++) {
|
||||
days.push({
|
||||
day: lastMonthDays - firstDayWeek + 1 + i,
|
||||
date: this.formatDate(new Date(lastMonthYear, lastMonth - 1, lastMonthDays - firstDayWeek + 1 + i)),
|
||||
otherMonth: true,
|
||||
isToday: false
|
||||
})
|
||||
}
|
||||
|
||||
// 本月
|
||||
const todayStr = this.formatDate(new Date())
|
||||
for (let i = 1; i <= monthDays; i++) {
|
||||
const dateStr = `${year}-${this.pad(month)}-${this.pad(i)}`
|
||||
days.push({
|
||||
day: i,
|
||||
date: dateStr,
|
||||
otherMonth: false,
|
||||
isToday: dateStr === todayStr
|
||||
})
|
||||
}
|
||||
|
||||
// 下月补位
|
||||
while (days.length % 7 !== 0) {
|
||||
const nextDay = days.length - (firstDayWeek + monthDays) + 1
|
||||
days.push({
|
||||
day: nextDay,
|
||||
date: this.formatDate(new Date(month === 12 ? year + 1 : year, month % 12, nextDay)),
|
||||
otherMonth: true,
|
||||
isToday: false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 周视图
|
||||
const base = this.safeDate(this.selected)
|
||||
const weekDay = base.getDay()
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const d = new Date(base)
|
||||
d.setDate(base.getDate() - weekDay + i)
|
||||
days.push({
|
||||
day: d.getDate(),
|
||||
date: this.formatDate(d),
|
||||
otherMonth: d.getMonth() !== base.getMonth(),
|
||||
isToday: this.formatDate(d) === this.formatDate(new Date())
|
||||
})
|
||||
}
|
||||
}
|
||||
this.renderDays = days
|
||||
},
|
||||
|
||||
// =================== 事件 ===================
|
||||
toggleMode() {
|
||||
if (!this.allowWeekSwitch) return
|
||||
this.mode = this.mode === 'month' ? 'week' : 'month'
|
||||
this.anchorDate = this.mode === 'month' ? this.startOfMonth(this.safeDate(this.selected)) : this.startOfWeek(this.safeDate(this.selected))
|
||||
this.generateRenderDays()
|
||||
},
|
||||
selectDate(item) {
|
||||
if (!item.day) return
|
||||
this.selected = item.date
|
||||
this.$emit('dateChange', item.date)
|
||||
},
|
||||
isSelected(item) {
|
||||
return item.date === this.selected && !item.otherMonth
|
||||
},
|
||||
|
||||
// =================== 滑动 ===================
|
||||
onTouchStart(e) {
|
||||
const t = e.changedTouches[0]
|
||||
this.touchStartX = t.clientX
|
||||
this.touchStartY = t.clientY
|
||||
},
|
||||
onTouchMove(e) {
|
||||
const t = e.changedTouches[0]
|
||||
this.touchMoveX = t.clientX
|
||||
this.touchMoveY = t.clientY
|
||||
},
|
||||
onTouchEnd(e) {
|
||||
const t = e.changedTouches[0]
|
||||
const endX = t.clientX
|
||||
const endY = t.clientY
|
||||
const deltaX = endX - this.touchStartX
|
||||
const deltaY = endY - this.touchStartY
|
||||
const threshold = 40
|
||||
|
||||
// 垂直滑动切换周/月
|
||||
if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > threshold) {
|
||||
if (!this.allowWeekSwitch) return
|
||||
|
||||
if (deltaY < 0 && this.mode !== 'week') {
|
||||
this.mode = 'week'
|
||||
this.anchorDate = this.startOfWeek(this.safeDate(this.selected))
|
||||
this.generateRenderDays()
|
||||
} else if (deltaY > 0 && this.mode !== 'month') {
|
||||
this.mode = 'month'
|
||||
this.anchorDate = this.startOfMonth(this.safeDate(this.selected))
|
||||
this.generateRenderDays()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 水平滑动翻页
|
||||
if (Math.abs(deltaX) > threshold && Math.abs(deltaX) >= Math.abs(deltaY)) {
|
||||
if (deltaX < 0) this.slide(1)
|
||||
else this.slide(-1)
|
||||
}
|
||||
},
|
||||
slide(direction) {
|
||||
if (this.mode === 'month') {
|
||||
const y = this.anchorDate.getFullYear()
|
||||
let m = this.anchorDate.getMonth() + 1 + direction
|
||||
let year = y
|
||||
if (m > 12) { m = 1; year++ }
|
||||
if (m < 1) { m = 12; year-- }
|
||||
this.anchorDate = new Date(year, m - 1, 1)
|
||||
} else {
|
||||
const d = this.safeDate(this.selected)
|
||||
d.setDate(d.getDate() + direction * 7)
|
||||
this.selected = this.formatDate(d)
|
||||
this.anchorDate = this.startOfWeek(d)
|
||||
}
|
||||
this.generateRenderDays()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.calendar-container {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 2rpx 16rpx #eee;
|
||||
padding: 12rpx;
|
||||
}
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.calendar-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
.calendar-week {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
color: #999;
|
||||
font-size: 24rpx;
|
||||
padding: 8rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
.calendar-days {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-auto-rows: 80rpx; /* 每个格子固定高度 */
|
||||
}
|
||||
.calendar-day {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60rpx; /* 固定宽度 */
|
||||
height: 60rpx; /* 固定高度,和宽度一致 */
|
||||
margin: auto; /* 居中对齐,避免被挤压 */
|
||||
font-size: 30rpx;
|
||||
border-radius: 50%;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.today {
|
||||
color: #007aff;
|
||||
font-weight: bold;
|
||||
border: 1rpx solid #007aff;
|
||||
}
|
||||
.selected {
|
||||
background: #007aff;
|
||||
color: #fff;
|
||||
}
|
||||
.other-month {
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
30
pages.json
30
pages.json
@@ -373,7 +373,7 @@
|
||||
{
|
||||
"path": "pages/sys/user/myRecord/myRecord",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
"navigationBarTitleText": "我的考勤"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -479,6 +479,34 @@
|
||||
{
|
||||
"navigationBarTitleText" : "巡检任务"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/sys/workbench/inspection/inspectionOpt",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "巡检详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/sys/workbench/inspection/inspectionDetail",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "巡检详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/sys/workbench/book/book",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : " 通讯录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path" : "pages/sys/workbench/leave/leave",
|
||||
"style" :
|
||||
{
|
||||
"navigationBarTitleText" : "请假"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
|
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<view class="my-record-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<image class="back-btn" src="/static/ic_back.png" @click="goBack" />
|
||||
<text class="page-title">我的考勤</text>
|
||||
</view>
|
||||
|
||||
<!-- 月份标题和切换 -->
|
||||
<view class="month-header">
|
||||
@@ -66,8 +61,11 @@
|
||||
<view v-if="date.hasRecord" class="record-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 展开/收缩按钮 -->
|
||||
<view class="calendar-toggle" @click="toggleCalendar">
|
||||
<text class="toggle-text">{{ calendarExpanded ? '收起' : '展开' }}</text>
|
||||
<image v-if="calendarExpanded" class='image_zk' src="/static/ic_exp.png"></image>
|
||||
<image v-else class='image_sq' src="/static/ic_sq.png"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -100,77 +98,43 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 我的考勤页面
|
||||
* @author lyc
|
||||
* @description 显示用户考勤记录,包含可展开收缩的日历
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 星期标题
|
||||
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
|
||||
// 日历是否展开
|
||||
calendarExpanded: false,
|
||||
// 当前选中的日期
|
||||
calendarExpanded: true,
|
||||
selectedDate: 8,
|
||||
// 当前年份
|
||||
currentYear: 2025,
|
||||
// 当前月份
|
||||
currentMonth: 7,
|
||||
// 完整月份日期数据
|
||||
allDates: []
|
||||
allDates: [],
|
||||
prevMonthDates: [],
|
||||
nextMonthDates: [],
|
||||
swiperCurrent: 1,
|
||||
touchStartX: 0,
|
||||
touchStartY: 0
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.generateCalendarDates();
|
||||
},
|
||||
computed: {
|
||||
// 当前周的日期(收缩状态显示)
|
||||
currentWeekDates() {
|
||||
// 找到选中日期所在的周
|
||||
const selectedIndex = this.allDates.findIndex(date => date.selected);
|
||||
if (selectedIndex === -1) return this.allDates.slice(0, 7);
|
||||
const startOfWeek = Math.floor(selectedIndex / 7) * 7;
|
||||
return this.allDates.slice(startOfWeek, startOfWeek + 7);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
// 初始化日历数据
|
||||
this.generateCalendarDates();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 返回上一页
|
||||
*/
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换日历展开/收缩状态
|
||||
*/
|
||||
toggleCalendar() {
|
||||
this.calendarExpanded = !this.calendarExpanded;
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择日期
|
||||
* @param {Object} date 日期对象
|
||||
*/
|
||||
selectDate(date) {
|
||||
if (!date.value) return;
|
||||
|
||||
// 清除之前选中的日期
|
||||
this.allDates.forEach(d => d.selected = false);
|
||||
// 设置新选中的日期
|
||||
date.selected = true;
|
||||
this.selectedDate = date.value;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取日期样式类名
|
||||
* @param {Object} date 日期对象
|
||||
* @returns {Object} 样式类名对象
|
||||
*/
|
||||
getDateClass(date) {
|
||||
return {
|
||||
'date-item': true,
|
||||
@@ -179,10 +143,6 @@ export default {
|
||||
'empty': !date.value
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换到上一个月
|
||||
*/
|
||||
prevMonth() {
|
||||
if (this.currentMonth === 1) {
|
||||
this.currentYear--;
|
||||
@@ -192,10 +152,6 @@ export default {
|
||||
}
|
||||
this.generateCalendarDates();
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换到下一个月
|
||||
*/
|
||||
nextMonth() {
|
||||
if (this.currentMonth === 12) {
|
||||
this.currentYear++;
|
||||
@@ -205,49 +161,57 @@ export default {
|
||||
}
|
||||
this.generateCalendarDates();
|
||||
},
|
||||
|
||||
/**
|
||||
* 生成日历数据
|
||||
*/
|
||||
generateCalendarDates() {
|
||||
const year = this.currentYear;
|
||||
const month = this.currentMonth;
|
||||
|
||||
// 获取当月第一天是星期几
|
||||
this.allDates = this.buildMonthDates(this.currentYear, this.currentMonth);
|
||||
|
||||
let prevY = this.currentYear, prevM = this.currentMonth - 1;
|
||||
if (prevM === 0) { prevM = 12; prevY--; }
|
||||
this.prevMonthDates = this.buildMonthDates(prevY, prevM);
|
||||
|
||||
let nextY = this.currentYear, nextM = this.currentMonth + 1;
|
||||
if (nextM === 13) { nextM = 1; nextY++; }
|
||||
this.nextMonthDates = this.buildMonthDates(nextY, nextM);
|
||||
},
|
||||
buildMonthDates(year, month) {
|
||||
const firstDay = new Date(year, month - 1, 1).getDay();
|
||||
|
||||
// 获取当月总天数
|
||||
const daysInMonth = new Date(year, month, 0).getDate();
|
||||
|
||||
// 清空日历数据
|
||||
this.allDates = [];
|
||||
|
||||
// 添加上月的空白日期
|
||||
for (let i = 0; i < firstDay; i++) {
|
||||
this.allDates.push({ value: null });
|
||||
}
|
||||
|
||||
// 添加当月日期
|
||||
let dates = [];
|
||||
for (let i = 0; i < firstDay; i++) dates.push({ value: null });
|
||||
for (let i = 1; i <= daysInMonth; i++) {
|
||||
// 模拟考勤记录数据,这里假设前20天有考勤记录
|
||||
const hasRecord = i <= 20;
|
||||
// 默认选中当月8号
|
||||
const selected = i === 8 && month === 7 && year === 2025;
|
||||
|
||||
this.allDates.push({
|
||||
value: i,
|
||||
hasRecord,
|
||||
selected
|
||||
});
|
||||
dates.push({ value: i, hasRecord: i <= 20, selected: i === this.selectedDate });
|
||||
}
|
||||
|
||||
// 计算需要添加的下月空白日期数量,使总数为7的倍数
|
||||
const remainingDays = 7 - (this.allDates.length % 7);
|
||||
if (remainingDays < 7) {
|
||||
for (let i = 0; i < remainingDays; i++) {
|
||||
this.allDates.push({ value: null });
|
||||
}
|
||||
const remaining = 7 - (dates.length % 7);
|
||||
if (remaining < 7) for (let i = 0; i < remaining; i++) dates.push({ value: null });
|
||||
return dates;
|
||||
},
|
||||
isInCurrentWeek(date) {
|
||||
if (!date.value) return false;
|
||||
const index = this.allDates.findIndex(d => d.value === this.selectedDate);
|
||||
if (index === -1) return false;
|
||||
const start = Math.floor(index / 7) * 7;
|
||||
const week = this.allDates.slice(start, start + 7);
|
||||
return week.includes(date);
|
||||
},
|
||||
onTouchStart(e) {
|
||||
this.touchStartX = e.changedTouches[0].clientX;
|
||||
this.touchStartY = e.changedTouches[0].clientY;
|
||||
},
|
||||
onTouchEnd(e) {
|
||||
const endX = e.changedTouches[0].clientX;
|
||||
const endY = e.changedTouches[0].clientY;
|
||||
const deltaX = endX - this.touchStartX;
|
||||
const deltaY = endY - this.touchStartY;
|
||||
|
||||
// 上下滑切换月/周视图
|
||||
if (Math.abs(deltaY) > Math.abs(deltaX)) {
|
||||
if (deltaY < -50 && this.calendarExpanded) this.calendarExpanded = false;
|
||||
else if (deltaY > 50 && !this.calendarExpanded) this.calendarExpanded = true;
|
||||
}
|
||||
},
|
||||
onSwiperChange(e) {
|
||||
const current = e.detail.current;
|
||||
if (current === 0) { this.prevMonth(); this.swiperCurrent = 1; }
|
||||
else if (current === 2) { this.nextMonth(); this.swiperCurrent = 1; }
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -256,7 +220,7 @@ export default {
|
||||
<style scoped>
|
||||
.my-record-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
@@ -286,6 +250,8 @@ export default {
|
||||
/* 月份标题和导航 */
|
||||
.month-header {
|
||||
padding: 20rpx 30rpx;
|
||||
margin-left: 180rpx;
|
||||
margin-right: 180rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@@ -296,8 +262,8 @@ export default {
|
||||
}
|
||||
|
||||
.month-arrow {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -321,9 +287,9 @@ export default {
|
||||
justify-content: space-around;
|
||||
padding: 40rpx 30rpx;
|
||||
margin: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx dashed #e0e0e0;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 10rpx;
|
||||
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
@@ -348,96 +314,27 @@ export default {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 日历 */
|
||||
.calendar {
|
||||
margin: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx dashed #e0e0e0;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 日历样式 */
|
||||
.calendar { overflow: hidden; margin-left: 20rpx; margin-right: 20rpx}
|
||||
.weekdays { display: flex; padding: 20rpx 0; }
|
||||
.weekday { flex: 1; text-align: center; font-size: 28rpx; color: #666; }
|
||||
.dates { display: flex; flex-wrap: wrap; padding: 20rpx 0; }
|
||||
.date-item { width: 14.28%; height: 70rpx; display: flex; align-items: center; justify-content: center; position: relative; margin-bottom: 20rpx; }
|
||||
.date-item.empty { color: #ccc; }
|
||||
.date-item.selected { background-color: #007aff; border-radius: 50%; width: 60rpx; height: 60rpx; margin: 10rpx auto; }
|
||||
.date-item.selected .date-text { color: #fff; }
|
||||
.date-text { font-size: 28rpx; color: #333; }
|
||||
.record-dot { position: absolute; bottom: 8rpx; left: 50%; transform: translateX(-50%); width: 8rpx; height: 8rpx; background-color: #007aff; border-radius: 50%; }
|
||||
.date-item.selected .record-dot { background-color: #fff; }
|
||||
|
||||
.weekdays {
|
||||
display: flex;
|
||||
background-color: #f8f8f8;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.weekday {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.dates {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.date-item {
|
||||
width: 14.28%;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.date-item.empty {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.date-item.selected {
|
||||
background-color: #007aff;
|
||||
border-radius: 50%;
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
margin: 10rpx auto;
|
||||
}
|
||||
|
||||
.date-item.selected .date-text {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.date-text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.record-dot {
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
background-color: #007aff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.date-item.selected .record-dot {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.calendar-toggle {
|
||||
padding: 20rpx;
|
||||
text-align: center;
|
||||
border-top: 1rpx solid #e0e0e0;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.toggle-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
.calendar-toggle { text-align: center; background-color: #fff; margin-top: -30rpx;}
|
||||
.image_sq { width: 37rpx; height: 6rpx; }
|
||||
.image_zk { width: 52rpx; height: 19rpx; }
|
||||
|
||||
/* 固定班次 */
|
||||
.fixed-shifts {
|
||||
margin: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
@@ -108,7 +108,9 @@
|
||||
this.realSubmit();
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
const result = await uploadFiles({
|
||||
files: images,
|
||||
url: this.vuex_config.baseUrl + '/resource/oss/upload',
|
||||
@@ -120,7 +122,8 @@
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
uni.hideLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -133,11 +136,17 @@
|
||||
|
||||
async realSubmit() {
|
||||
let res = await this.$u.api.addOrder2(this.repairInfo);
|
||||
if (res.code == '200') {
|
||||
if (res.code == '200') {
|
||||
uni.hideLoading();
|
||||
// 关闭页面前发送事件通知前页面刷新
|
||||
uni.$emit('refreshData', '');
|
||||
// 返回上一页
|
||||
uni.navigateBack();
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: res.msg,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
@@ -106,10 +106,14 @@
|
||||
this.onRefresh()
|
||||
},
|
||||
onShow() {
|
||||
uni.$once('refreshData', () => {
|
||||
uni.$on('refreshData', () => {
|
||||
this.onRefresh()
|
||||
});
|
||||
},
|
||||
// 页面卸载时移除事件监听器
|
||||
onUnload() {
|
||||
uni.$off('refreshData');
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
|
@@ -91,9 +91,13 @@
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
uni.$once('selectPlate', plate => {
|
||||
uni.$on('selectPlate', plate => {
|
||||
this.form.licensePlate = plate;
|
||||
});
|
||||
},
|
||||
// 页面卸载时移除事件监听器
|
||||
onUnload() {
|
||||
uni.$off('selectPlate');
|
||||
},
|
||||
methods: {
|
||||
// 新增:处理图片上传
|
||||
|
212
pages/sys/workbench/book/book.vue
Normal file
212
pages/sys/workbench/book/book.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<input type="text" class="search-input" placeholder="部门、岗位、姓名" v-model="keyword"/>
|
||||
</view>
|
||||
|
||||
<!-- 联系人列表 -->
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="contact-list"
|
||||
:scroll-into-view="currentView"
|
||||
scroll-with-animation
|
||||
>
|
||||
<block v-for="(group, gIndex) in contacts" :key="gIndex">
|
||||
<view class="group-title" :id="'group-' + group.letter">{{ group.letter }}</view>
|
||||
<view class="contact-item" v-for="(item, index) in group.list" :key="index">
|
||||
<image class="avatar" :src="item.avatar"></image>
|
||||
<view class="contact-info">
|
||||
<text class="name" :class="{'highlight': item.isHighlight}">{{ item.name }}</text>
|
||||
<text class="desc">{{ item.job }} {{ item.phone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
|
||||
|
||||
<!-- 字母索引栏 -->
|
||||
<view
|
||||
class="index-bar"
|
||||
@touchstart="onTouch"
|
||||
@touchmove.stop.prevent="onTouch"
|
||||
@touchend="onTouchEnd"
|
||||
@touchcancel="onTouchEnd"
|
||||
>
|
||||
<view
|
||||
v-for="(letter, i) in indexList"
|
||||
:key="i"
|
||||
class="index-item"
|
||||
:style="{height: indexItemHeight + 'rpx', lineHeight: indexItemHeight + 'rpx'}"
|
||||
>
|
||||
{{ letter }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 居中大字母提示 -->
|
||||
<view v-if="showLetter" class="letter-toast">
|
||||
{{ showLetter }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
keyword: "",
|
||||
currentView: "",
|
||||
showLetter: "",
|
||||
indexList: "ABCDEFGHIJKLMNOPQRSTUVWXYZ#".split(""),
|
||||
indexItemHeight: 30, // 索引单个高度 (rpx),可调整
|
||||
contacts: [
|
||||
{ letter: "A", list: [{ name: "张晓明", phone: "17895698899", job: "保洁", avatar: "https://img.yzcdn.cn/vant/cat.jpeg", isHighlight: true }] },
|
||||
{ letter: "B", list: [
|
||||
{ name: "阿俊", phone: "17895698899", job: "保洁", avatar: "https://img.yzcdn.cn/vant/dog.jpeg" },
|
||||
{ name: "阿俊", phone: "17895698899", job: "保洁", avatar: "https://img.yzcdn.cn/vant/cat.jpeg" },
|
||||
{ name: "阿俊", phone: "17895698899", job: "保洁", avatar: "https://img.yzcdn.cn/vant/elephant.jpeg" }
|
||||
]},
|
||||
{ letter: "C", list: [
|
||||
{ name: "阿俊", phone: "17895698899", job: "保洁", avatar: "https://img.yzcdn.cn/vant/horse.jpeg" },
|
||||
{ name: "阿俊", phone: "17895698899", job: "保洁", avatar: "https://img.yzcdn.cn/vant/lion.jpeg" }
|
||||
]}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 开始/移动
|
||||
onTouch(e) {
|
||||
const touchY = e.touches[0].clientY
|
||||
this.calcIndexByY(touchY)
|
||||
},
|
||||
// 松手/取消时隐藏
|
||||
onTouchEnd() {
|
||||
setTimeout(() => {
|
||||
this.showLetter = ""
|
||||
}, 300) // 0.3 秒后消失
|
||||
},
|
||||
|
||||
// 计算位置 → 滚动 & 显示大字母
|
||||
calcIndexByY(y) {
|
||||
const query = uni.createSelectorQuery().in(this)
|
||||
query.select('.index-bar').boundingClientRect(rect => {
|
||||
if (!rect) return
|
||||
const top = rect.top
|
||||
const index = Math.floor((y - top) / (rect.height / this.indexList.length))
|
||||
if (index >= 0 && index < this.indexList.length) {
|
||||
const letter = this.indexList[index]
|
||||
this.scrollTo(letter)
|
||||
}
|
||||
}).exec()
|
||||
},
|
||||
scrollTo(letter) {
|
||||
const exists = this.contacts.find(item => item.letter === letter)
|
||||
if (exists) {
|
||||
this.currentView = "group-" + letter
|
||||
}
|
||||
this.showLetter = letter
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 90rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
.back {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
.title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.search-box {
|
||||
padding: 20rpx;
|
||||
}
|
||||
.search-input {
|
||||
width: auto;
|
||||
height: 63rpx;
|
||||
background: #F2F3F5;
|
||||
border-radius: 25rpx;
|
||||
padding-left: 20rpx;
|
||||
font-size: 28rpx;
|
||||
margin-left: 18rpx;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
.contact-list {
|
||||
flex: 1;
|
||||
}
|
||||
.group-title {
|
||||
padding: 10rpx 20rpx;
|
||||
background: #f5f5f5;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
.avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.contact-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.name {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.name.highlight {
|
||||
color: orange;
|
||||
}
|
||||
.desc {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
.index-bar {
|
||||
position: fixed;
|
||||
right: 10rpx;
|
||||
top: 200rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
user-select: none;
|
||||
}
|
||||
.index-item {
|
||||
font-size: 22rpx;
|
||||
color: #3a6ea5;
|
||||
}
|
||||
.letter-toast {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
font-size: 80rpx;
|
||||
font-weight: bold;
|
||||
padding: 40rpx 60rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
</style>
|
@@ -86,10 +86,15 @@
|
||||
this.loadAllTabsData()
|
||||
},
|
||||
onShow() {
|
||||
uni.$once('refreshData', () => {
|
||||
// 使用$on替代$once,确保每次都能监听到事件
|
||||
uni.$on('refreshData', () => {
|
||||
this.loadAllTabsData()
|
||||
});
|
||||
},
|
||||
// 页面卸载时移除事件监听器
|
||||
onUnload() {
|
||||
uni.$off('refreshData');
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
@@ -141,16 +146,16 @@
|
||||
this.tabLoaded[idx] = true;
|
||||
this.loading = false;
|
||||
},
|
||||
goDetail2(item) {
|
||||
const itemStr = encodeURIComponent(JSON.stringify(item));
|
||||
if([20, 30, 31, 32].includes(item.state)){
|
||||
uni.navigateTo({
|
||||
url: "/pages/sys/workbench/earlyWarning/warnDetail?item=" + itemStr,
|
||||
});
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url: "/pages/sys/workbench/earlyWarning/warnDetail?item=" + itemStr + "&pageType=detail",
|
||||
});
|
||||
goDetail2(item) {
|
||||
const itemStr = encodeURIComponent(JSON.stringify(item));
|
||||
if([20, 30, 31, 32].includes(item.state)){
|
||||
uni.navigateTo({
|
||||
url: "/pages/sys/workbench/earlyWarning/warnDetail?item=" + itemStr,
|
||||
});
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url: "/pages/sys/workbench/earlyWarning/warnDetail?item=" + itemStr + "&pageType=detail",
|
||||
});
|
||||
}
|
||||
},
|
||||
goStatistics() {
|
||||
@@ -161,6 +166,11 @@
|
||||
|
||||
// 添加预加载所有标签页数据的方法
|
||||
async loadAllTabsData() {
|
||||
// 重置状态
|
||||
this.pageNum = [1, 1];
|
||||
this.noMore = [false, false];
|
||||
this.tabData = [[], []];
|
||||
|
||||
// 并行加载所有标签页数据,提高加载速度
|
||||
const loadPromises = [0, 1].map((index) => {
|
||||
return this.loadTabData(index);
|
||||
|
@@ -8,24 +8,45 @@
|
||||
<view v-if="idx === activeTab" class="tab-underline"></view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 列表区 -->
|
||||
<view class="ins-list">
|
||||
<view v-for="(item, idx) in list" :key="idx" class="ins-card" @click="goProcess(item)">
|
||||
<view class="ins-row">
|
||||
<view class="ins-no">保洁部日常巡检 {{ item.createTime.substring(0,11) }}</view>
|
||||
<view class="ins-status" :class="getStatusColor(item.status)">
|
||||
{{ getStatusLabel(item.status) }}
|
||||
</view>
|
||||
</view>
|
||||
<image class="ins-line-image" src="/static/ic_my_repair_03.png" />
|
||||
<view class="ins-info">巡检人:{{ item.createTime }}</view>
|
||||
<view class="ins-info">计划完成时间:{{ item.typeName }}</view>
|
||||
<view class="ins-info">实际完成时间:{{ item.location }}</view>
|
||||
<view class="ins-info">巡检进度:{{ item.location }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 为每个标签页创建独立的scroll-view -->
|
||||
<view class="ins-list-container">
|
||||
<scroll-view
|
||||
v-for="(tab, idx) in tabs"
|
||||
:key="idx"
|
||||
v-show="idx === activeTab"
|
||||
class="ins-list"
|
||||
scroll-y
|
||||
refresher-enabled
|
||||
:refresher-triggered="refresherTriggered[idx]"
|
||||
refresher-background="#f7f7f7"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="loadMore"
|
||||
:scroll-with-animation="true"
|
||||
>
|
||||
<view v-for="(item, index) in tabData[idx].list" :key="index" class="ins-card" @click="goProcess(item)">
|
||||
<view class="ins-row">
|
||||
<view class="ins-no">{{item.planName || ''}} {{ item.createTime ? item.createTime.substring(0,11) : '' }}</view>
|
||||
<view class="ins-status" :class="getStatusColor(item.status)">
|
||||
{{ getStatusLabel(item.status) }}
|
||||
</view>
|
||||
</view>
|
||||
<image class="ins-line-image" src="/static/ic_my_repair_03.png" />
|
||||
<view class="ins-info">巡检人:{{ item.actUserName || '' }}</view>
|
||||
<view class="ins-info">计划完成时间:{{ item.planInsTime || '' }}</view>
|
||||
<view class="ins-info">实际完成时间:{{ item.location || '' }}</view>
|
||||
<view class="ins-info">巡检进度:{{ item.inspectionProgress || '' }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多提示 -->
|
||||
<view v-if="loading[idx]" style="text-align:center;color:#999;font-size:26rpx;padding:20rpx;">
|
||||
加载中...
|
||||
</view>
|
||||
<view v-if="noMore[idx] && tabData[idx].list.length > 0" style="text-align:center;color:#999;font-size:26rpx;padding:20rpx;">
|
||||
没有更多数据了
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -33,65 +54,122 @@
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tabs: ['待进行', '处理中', '已完成'],
|
||||
tabs: ['待进行', '处理中', '已完成', '已超时'],
|
||||
activeTab: 0,
|
||||
tabData: [
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
], // 每个tab的数据
|
||||
tabLoaded: [false, false, false], // 每个tab是否已加载
|
||||
loading: false
|
||||
{ list: [], pageNum: 1, pageSize: 10 },
|
||||
{ list: [], pageNum: 1, pageSize: 10 },
|
||||
{ list: [], pageNum: 1, pageSize: 10 },
|
||||
{ list: [], pageNum: 1, pageSize: 10 }
|
||||
],
|
||||
pageNum: [1, 1, 1, 1], // 每个 tab 当前页码
|
||||
pageSize: 10,
|
||||
noMore: [false, false, false, false], // 每个 tab 是否没有更多数据
|
||||
tabLoaded: [false, false, false, false], // tab 是否加载过
|
||||
loading: [false, false, false, false], // 每个 tab 的加载状态
|
||||
refresherTriggered: [false, false, false, false] // 每个 tab 的下拉刷新状态
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
list() {
|
||||
return this.tabData[this.activeTab];
|
||||
return this.tabData[this.activeTab].list;
|
||||
},
|
||||
hasMore() {
|
||||
return !this.noMore[this.activeTab];
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadTabData(this.activeTab); // 初始化加载当前tab数据
|
||||
this.loadAllTabsData();
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
// 加载所有tab数据
|
||||
async loadAllTabsData() {
|
||||
for (let idx = 0; idx < this.tabs.length; idx++) {
|
||||
if (!this.tabLoaded[idx]) {
|
||||
await this.loadTabData(idx);
|
||||
}
|
||||
}
|
||||
},
|
||||
// 切换 tab
|
||||
async changeTab(idx) {
|
||||
this.activeTab = idx;
|
||||
if (!this.tabLoaded[idx]) {
|
||||
await this.loadTabData(idx);
|
||||
await this.onRefresh();
|
||||
}
|
||||
},
|
||||
// 下拉刷新
|
||||
async onRefresh() {
|
||||
this.refresherTriggered[this.activeTab] = true;
|
||||
this.pageNum[this.activeTab] = 1;
|
||||
this.noMore[this.activeTab] = false;
|
||||
this.tabData[this.activeTab].list = [];
|
||||
await this.loadTabData(this.activeTab);
|
||||
this.refresherTriggered[this.activeTab] = false;
|
||||
},
|
||||
// 滚动加载更多
|
||||
async loadMore() {
|
||||
if (this.loading[this.activeTab] || this.noMore[this.activeTab]) return;
|
||||
this.pageNum[this.activeTab]++;
|
||||
this.loading[this.activeTab] = true;
|
||||
await this.loadTabData(this.activeTab);
|
||||
this.loading[this.activeTab] = false;
|
||||
},
|
||||
async loadTabData(idx) {
|
||||
this.loading = true;
|
||||
// 模拟接口请求,不同tab返回不同mock数据
|
||||
let params = {}
|
||||
|
||||
|
||||
let data = [];
|
||||
this.loading[idx] = true;
|
||||
let params = {
|
||||
pageNum: this.pageNum[idx],
|
||||
pageSize: this.pageSize
|
||||
};
|
||||
|
||||
// 根据tab索引设置不同的状态参数
|
||||
switch(idx) {
|
||||
case 0: // 待进行
|
||||
params.status = '0';
|
||||
break;
|
||||
case 1: // 处理中
|
||||
params.status = '1';
|
||||
break;
|
||||
case 2: // 已完成
|
||||
params.status = '2';
|
||||
break;
|
||||
case 3: // 已超时
|
||||
params.status = '3';
|
||||
break;
|
||||
}
|
||||
|
||||
let res = await this.$u.api.getInspection(params);
|
||||
if (res.code == '200') {
|
||||
data = res.rows
|
||||
let rows = res.rows || [];
|
||||
if (rows.length < this.pageSize) {
|
||||
this.noMore[idx] = true;
|
||||
}
|
||||
if (this.pageNum[idx] === 1) {
|
||||
// 刷新时重置数据
|
||||
this.tabData[idx].list = rows;
|
||||
} else {
|
||||
// 加载更多时追加数据
|
||||
this.tabData[idx].list = [...this.tabData[idx].list, ...rows];
|
||||
}
|
||||
}
|
||||
|
||||
this.$set(this.tabData, idx, data);
|
||||
this.$set(this.tabLoaded, idx, true);
|
||||
this.loading = false;
|
||||
this.loading[idx] = false;
|
||||
},
|
||||
goProcess(item) {
|
||||
const detailItemStr = encodeURIComponent(JSON.stringify(item));
|
||||
uni.navigateTo({
|
||||
url: `/pages/sys/workbench/inspection/inspectionProcess?detailItem=${item}`
|
||||
uni.navigateTo({
|
||||
url: `/pages/sys/workbench/inspection/inspectionProcess?item=${detailItemStr}`
|
||||
});
|
||||
},
|
||||
|
||||
getStatusLabel(status) {
|
||||
const statusMap = {
|
||||
0: '待确认',
|
||||
1: '已确认',
|
||||
2: '已取消',
|
||||
3: '已完成'
|
||||
0: '待进行',
|
||||
1: '处理中',
|
||||
2: '已完成',
|
||||
3: '已超时'
|
||||
};
|
||||
return statusMap[status] || '';
|
||||
},
|
||||
@@ -100,7 +178,8 @@
|
||||
0: '待确认',
|
||||
1: 'orange',
|
||||
2: '已取消',
|
||||
3: '已完成'
|
||||
3: 'done',
|
||||
4: 'done'
|
||||
};
|
||||
return statusMap[status] || '';
|
||||
}
|
||||
@@ -153,15 +232,15 @@
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.ins-list {
|
||||
margin: 25rpx 0 0 0;
|
||||
padding: 0 35rpx;
|
||||
.ins-list-container {
|
||||
flex: 1;
|
||||
/* 占据所有剩余空间 */
|
||||
overflow-y: auto;
|
||||
/* 内容超出时,开启垂直滚动 */
|
||||
padding-bottom: 200rpx;
|
||||
/* 为底部按钮留出空间 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ins-list {
|
||||
height: 100%;
|
||||
padding: 25rpx 35rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ins-card {
|
||||
@@ -180,43 +259,47 @@
|
||||
margin-top: 25rpx;
|
||||
margin-left: 19rpx;
|
||||
margin-right: 50rpx;
|
||||
}
|
||||
|
||||
.ins-no {
|
||||
font-size: 24rpx;
|
||||
color: #0B0B0B;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ins-status {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ins-line-image {
|
||||
margin: left 29rpx;
|
||||
margin-right: 39rpx;
|
||||
height: 2rpx;
|
||||
margin-bottom: 29rpx;
|
||||
}
|
||||
|
||||
.ins-status.orange {
|
||||
color: #F3AB44;
|
||||
}
|
||||
|
||||
.ins-status.doing {
|
||||
color: #00C9AA;
|
||||
}
|
||||
|
||||
.ins-status.done {
|
||||
color: #8A8A8A;
|
||||
}
|
||||
|
||||
.ins-info {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-bottom: 30rpx;
|
||||
margin-left: 47rpx;
|
||||
}
|
||||
|
||||
.ins-no {
|
||||
font-size: 24rpx;
|
||||
color: #0B0B0B;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ins-status {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ins-line-image {
|
||||
margin: left 29rpx;
|
||||
margin-right: 39rpx;
|
||||
height: 2rpx;
|
||||
margin-bottom: 29rpx;
|
||||
}
|
||||
|
||||
.ins-status.orange {
|
||||
color: #F3AB44;
|
||||
}
|
||||
|
||||
.ins-status.doing {
|
||||
color: #00C9AA;
|
||||
}
|
||||
|
||||
.ins-status.done {
|
||||
color: #8A8A8A;
|
||||
}
|
||||
|
||||
.ins-status.overdue {
|
||||
color: #FF4D4D;
|
||||
}
|
||||
|
||||
.ins-info {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
margin-bottom: 30rpx;
|
||||
margin-left: 47rpx;
|
||||
}
|
||||
|
||||
</style>
|
452
pages/sys/workbench/inspection/inspectionDetail.vue
Normal file
452
pages/sys/workbench/inspection/inspectionDetail.vue
Normal file
@@ -0,0 +1,452 @@
|
||||
<template>
|
||||
<view class="inspection-opt-container">
|
||||
<view class="inspection-opt-lable"> 巡检人
|
||||
<view class="inspection-opt-round" />
|
||||
<text class="text">{{info.planInspectionPerson}}</text>
|
||||
</view>
|
||||
<view class="inspection-opt-lable"> 巡检位置
|
||||
<text class="required">*</text>
|
||||
</view>
|
||||
<view class="inspection-opt-row">
|
||||
<text class="text2">{{info.inspectionLocation}}</text>
|
||||
</view>
|
||||
<view class="inspection-opt-lable"> 巡检结果
|
||||
<text class="required">*</text>
|
||||
<view :class="info.inspectionResults == 1?'inspection-opt-round4':'inspection-opt-round2'"/>
|
||||
<text class="text">正常</text>
|
||||
<view :class="info.inspectionResults != 1?'inspection-opt-round3':'inspection-opt-round2'" />
|
||||
<text class="text"> 异常</text>
|
||||
</view>
|
||||
<view class="inspection-opt-lable"> 巡检描述
|
||||
<text class="required">*</text>
|
||||
</view>
|
||||
<text class="input" >{{ info.remark }}</text>
|
||||
|
||||
<!-- 上传照片 -->
|
||||
<view class="page">
|
||||
<!-- 已选图片预览 -->
|
||||
<view class="preview-item">
|
||||
<image :src="selectedImage" mode="aspectFill" @click="previewImage"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
info: {
|
||||
inspectionResults: 1 // 默认选中"正常"
|
||||
},
|
||||
selectedImage: '',
|
||||
isSign: false,
|
||||
showDialog: false // 控制弹窗显示
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.item) {
|
||||
try {
|
||||
// 首先尝试解析原始字符串(可能未编码)
|
||||
const item = JSON.parse(options.item);
|
||||
this.info = item;
|
||||
} catch (e) {
|
||||
// 如果直接解析失败,再尝试解码后解析
|
||||
try {
|
||||
const item = JSON.parse(decodeURIComponent(options.item));
|
||||
this.info = item;
|
||||
} catch (e2) {
|
||||
this.info = options.item;
|
||||
}
|
||||
}
|
||||
this.isSign = this.info.actualSignState == 1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
|
||||
// 预览图片
|
||||
previewImage() {
|
||||
uni.previewImage({
|
||||
current: this.selectedImage,
|
||||
urls: [this.selectedImage]
|
||||
})
|
||||
},
|
||||
|
||||
// 切换巡检结果状态
|
||||
toggleInspectionResult(result) {
|
||||
// 使用$set确保响应式更新
|
||||
this.$set(this.info, 'inspectionResults', result);
|
||||
},
|
||||
beforeSubmit() {
|
||||
if (this.info.inspectionResults == 1) {
|
||||
// 直接提交
|
||||
this.submit();
|
||||
} else {
|
||||
// 弹窗显示,生成工单提交
|
||||
this.showDialog = true;
|
||||
}
|
||||
},
|
||||
closeDialog() {
|
||||
this.showDialog = false;
|
||||
},
|
||||
confirmDialog() {
|
||||
this.showDialog = false;
|
||||
// 生成工单提交
|
||||
this.submit();
|
||||
},
|
||||
|
||||
async taskSignIn() {
|
||||
|
||||
uni.scanCode({
|
||||
success: async (scanRes) => {
|
||||
// 使用扫码结果调用接口
|
||||
let params = {
|
||||
taskId: this.info.id,
|
||||
qrCode: scanRes.result // 将扫码结果作为参数传递
|
||||
};
|
||||
if (scanRes.result == this.info.pointId) {
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
this.info.signType = 3
|
||||
let res = await this.$u.api.taskSignIn(this.info);
|
||||
|
||||
// 隐藏loading
|
||||
uni.hideLoading();
|
||||
|
||||
if (res.code == 200) {
|
||||
this.isSign = true
|
||||
uni.showToast({
|
||||
title: '签到成功',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '签到失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}else{
|
||||
uni.showToast({
|
||||
title: '当前签到巡检点不是目标点',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
// 扫码失败处理
|
||||
uni.showToast({
|
||||
title: '扫码失败,请重试',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
async submit() {
|
||||
// 显示loading
|
||||
uni.showLoading({
|
||||
title: '提交中...'
|
||||
});
|
||||
|
||||
if (!this.selectedImage) {
|
||||
this.realSubmit()
|
||||
return
|
||||
}
|
||||
const images = [];
|
||||
images.push(this.selectedImage?.path?.replace('file://', '') || this.selectedImage);
|
||||
if (images.length === 0) {
|
||||
this.realSubmit();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await uploadFiles({
|
||||
files: images,
|
||||
url: this.vuex_config.baseUrl + '/resource/oss/upload',
|
||||
name: 'file',
|
||||
vm: this // 关键:用于注入 token 等
|
||||
});
|
||||
const allSuccess = result.every(item => item.code == 200);
|
||||
if (!allSuccess) {
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
// 隐藏loading
|
||||
uni.hideLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历result获取data.url加上,分割
|
||||
const urls = result.map(item => item.data?.url || '').filter(url => url !== '');
|
||||
this.info.inspectionImage = urls.join(',');
|
||||
this.realSubmit()
|
||||
|
||||
},
|
||||
|
||||
async realSubmit() {
|
||||
let res = await this.$u.api.taskSubmit(this.info);
|
||||
if (res.code == '200') {
|
||||
// 关闭页面前发送事件通知前页面刷新
|
||||
uni.$emit('refreshData', '');
|
||||
// 返回上一页
|
||||
uni.navigateBack();
|
||||
}
|
||||
// 隐藏loading
|
||||
uni.hideLoading();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inspection-opt-container {
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.inspection-opt-lable {
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 40rpx;
|
||||
margin-top: 45rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
margin-top: 34rpx;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #DC9100;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: #000000;
|
||||
font-size: 24rpx;
|
||||
margin-left: 10rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text2 {
|
||||
height: 73rpx;
|
||||
flex: 1;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.text3 {
|
||||
width: 88rpx;
|
||||
height: 73rpx;
|
||||
background: #296AEF;
|
||||
border-radius: 10rpx;
|
||||
color: #F7F7F7;
|
||||
text-align: center;
|
||||
margin-left: 18rpx;
|
||||
line-height: 73rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 200rpx;
|
||||
width: auto;
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
margin-top: 40rpx;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #000000;
|
||||
padding: 15rpx;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 55rpx;
|
||||
height: 42rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-round {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #3370FF;
|
||||
margin-left: 35rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-round2 {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #fff;
|
||||
margin-left: 24rpx;
|
||||
border: 1rpx solid #3370FF;
|
||||
}
|
||||
|
||||
.inspection-opt-round3 {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #F27A0F;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-round4 {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #3370FF;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.custom-upload-btn {
|
||||
width: auto;
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
margin-top: 24rpx;
|
||||
height: 244rpx;
|
||||
background: #f6f6f6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
position: relative;
|
||||
width: auto;
|
||||
/* 宽度自动 */
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
height: 244rpx;
|
||||
margin-top: 24rpx;
|
||||
/* 固定高度 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
/* 水平居中 */
|
||||
align-items: center;
|
||||
/* 垂直居中 */
|
||||
overflow: hidden;
|
||||
/* 裁剪超出部分 */
|
||||
border-radius: 10rpx;
|
||||
/* 可选:圆角 */
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 隐藏自带按钮 */
|
||||
::v-deep .u-upload__wrap__add {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.inspection-opt-submit-btn {
|
||||
width: 60vw;
|
||||
height: 73rpx;
|
||||
background: linear-gradient(90deg, #005DE9 0%, #4B9BFF 100%);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
border-radius: 40rpx;
|
||||
margin: 100rpx auto 0 auto;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
/* 弹窗遮罩 */
|
||||
.custom-dialog {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* 弹窗背景图 */
|
||||
.dialog-bg {
|
||||
position: absolute;
|
||||
width: 656rpx;
|
||||
height: 560rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
/* 弹窗内容覆盖在图片上 */
|
||||
.dialog-content {
|
||||
position: relative;
|
||||
width: 80vw;
|
||||
margin-top: 220rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 42rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.dialog-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 100rpx;
|
||||
}
|
||||
|
||||
.dialog-btns {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-confirm {
|
||||
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
color: #FF6B00;
|
||||
}
|
||||
</style>
|
573
pages/sys/workbench/inspection/inspectionOpt.vue
Normal file
573
pages/sys/workbench/inspection/inspectionOpt.vue
Normal file
@@ -0,0 +1,573 @@
|
||||
<template>
|
||||
<view class="inspection-opt-container">
|
||||
<view class="inspection-opt-lable"> 巡检人
|
||||
<view class="inspection-opt-round" />
|
||||
<text class="text">{{info.planInspectionPerson}}</text>
|
||||
</view>
|
||||
<view class="inspection-opt-lable"> 巡检位置
|
||||
<text class="required">*</text>
|
||||
</view>
|
||||
<view class="inspection-opt-row">
|
||||
<text class="text2">{{info.inspectionLocation}}</text>
|
||||
<text v-if="!isSign" class="text3" @click="taskSignIn"> 签到</text>
|
||||
</view>
|
||||
<view class="inspection-opt-lable"> 巡检结果
|
||||
<text class="required">*</text>
|
||||
<view :class="info.inspectionResults == 1?'inspection-opt-round4':'inspection-opt-round2'"
|
||||
@click="toggleInspectionResult(1)" />
|
||||
<text class="text" @click="toggleInspectionResult(1)">正常</text>
|
||||
<view :class="info.inspectionResults != 1?'inspection-opt-round3':'inspection-opt-round2'"
|
||||
@click="toggleInspectionResult(0)" />
|
||||
<text class="text" @click="toggleInspectionResult(0)"> 异常</text>
|
||||
</view>
|
||||
<view class="inspection-opt-lable"> 巡检描述
|
||||
<text class="required">*</text>
|
||||
</view>
|
||||
<textarea type="text" v-model="info.remark" placeholder="请输入" class="input" />
|
||||
|
||||
<!-- 上传照片 -->
|
||||
<view class="page">
|
||||
<!-- 如果没有图片,显示上传按钮 -->
|
||||
<view class="custom-upload-btn" @click="chooseImage" v-if="!selectedImage">
|
||||
<image class="image" src="/static/ic_camera.png"></image>
|
||||
<text class="text">上传图片</text>
|
||||
</view>
|
||||
|
||||
<!-- 已选图片预览 -->
|
||||
<view class="preview-item" v-else>
|
||||
<image class="preview-img" :src="selectedImage" mode="aspectFill" @click="previewImage"></image>
|
||||
<view class="delete-btn" @click.stop="deletePic">✕</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="info.inspectionResults != 1" class="inspection-opt-lable">报事报修</view>
|
||||
<view v-if="info.inspectionResults != 1" class="repair-type" @click="chooseType">
|
||||
<text class="text-type">{{ selectedType.orderTypeName }}</text>
|
||||
<image class="right-arrow" src="/static/ic_right_arrow_g.png" />
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button class="inspection-opt-submit-btn"
|
||||
@click="beforeSubmit">{{ info.inspectionResults == 1 ? '提交' : '生成工单提交' }}</button>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<view class="custom-dialog" v-if="showDialog">
|
||||
<image class="dialog-bg" src="/static/ic_dialog_01.png"></image>
|
||||
<view class="dialog-content">
|
||||
<text class="dialog-title">是否确定</text>
|
||||
<text class="dialog-desc">转送确定生成工单编号</text>
|
||||
|
||||
<view class="dialog-btns">
|
||||
<view class="btn-cancel" @click="closeDialog">下次再说</view>
|
||||
<view class="btn-confirm" @click="confirmDialog">确认生成</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 导入MediaSelector和MediaType
|
||||
import MediaSelector, {
|
||||
MediaType
|
||||
} from '@/utils/mediaSelector';
|
||||
import {
|
||||
uploadFiles
|
||||
} from '@/common/upload.js';
|
||||
import toast from '../../../../uview-ui/libs/function/toast';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
info: {
|
||||
inspectionResults: 1 // 默认选中"正常"
|
||||
},
|
||||
repairTypes: [],
|
||||
selectedType: {},
|
||||
selectedImage: '',
|
||||
isSign: false,
|
||||
showDialog: false // 控制弹窗显示
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.item) {
|
||||
try {
|
||||
// 首先尝试解析原始字符串(可能未编码)
|
||||
const item = JSON.parse(options.item);
|
||||
this.info = item;
|
||||
} catch (e) {
|
||||
// 如果直接解析失败,再尝试解码后解析
|
||||
try {
|
||||
const item = JSON.parse(decodeURIComponent(options.item));
|
||||
this.info = item;
|
||||
} catch (e2) {
|
||||
this.info = options.item;
|
||||
}
|
||||
}
|
||||
this.isSign = this.info.actualSignState == 1
|
||||
}
|
||||
this.loadFilterData()
|
||||
},
|
||||
methods: {
|
||||
async loadFilterData() {
|
||||
let resType = await this.$u.api.getOrdersType();
|
||||
if (resType.code === 200) {
|
||||
this.repairTypes = [...this.repairTypes, ...resType.rows];
|
||||
this.selectedType = this.repairTypes[0]
|
||||
}
|
||||
},
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
this.selectedImage = res.tempFilePaths[0]
|
||||
}
|
||||
})
|
||||
},
|
||||
// 预览图片
|
||||
previewImage() {
|
||||
uni.previewImage({
|
||||
current: this.selectedImage,
|
||||
urls: [this.selectedImage]
|
||||
})
|
||||
},
|
||||
// 删除图片
|
||||
deletePic(event) {
|
||||
this.selectedImage = ''
|
||||
},
|
||||
// 切换巡检结果状态
|
||||
toggleInspectionResult(result) {
|
||||
// 使用$set确保响应式更新
|
||||
this.$set(this.info, 'inspectionResults', result);
|
||||
},
|
||||
// 添加chooseType方法实现
|
||||
chooseType() {
|
||||
uni.showActionSheet({
|
||||
itemList: this.repairTypes.map(item => item.orderTypeName),
|
||||
success: (res) => {
|
||||
this.selectedType = this.repairTypes[res.tapIndex];
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('用户取消选择或出错', err);
|
||||
}
|
||||
});
|
||||
},
|
||||
beforeSubmit() {
|
||||
// if(!this.isSign){
|
||||
// uni.showToast({
|
||||
// title: '请先签到',
|
||||
// icon: 'none'
|
||||
// });
|
||||
// return
|
||||
// }
|
||||
if (this.info.inspectionResults == 1) {
|
||||
// 直接提交
|
||||
this.submit();
|
||||
} else {
|
||||
// 弹窗显示,生成工单提交
|
||||
this.showDialog = true;
|
||||
}
|
||||
},
|
||||
closeDialog() {
|
||||
this.showDialog = false;
|
||||
},
|
||||
confirmDialog() {
|
||||
this.showDialog = false;
|
||||
// 生成工单提交
|
||||
this.orderSubmig();
|
||||
},
|
||||
|
||||
async taskSignIn() {
|
||||
|
||||
uni.scanCode({
|
||||
success: async (scanRes) => {
|
||||
// 使用扫码结果调用接口
|
||||
let params = {
|
||||
taskId: this.info.id,
|
||||
qrCode: scanRes.result // 将扫码结果作为参数传递
|
||||
};
|
||||
if (scanRes.result == this.info.pointId) {
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
this.info.signType = 3
|
||||
let res = await this.$u.api.taskSignIn(this.info);
|
||||
|
||||
// 隐藏loading
|
||||
uni.hideLoading();
|
||||
|
||||
if (res.code == 200) {
|
||||
this.isSign = true
|
||||
uni.showToast({
|
||||
title: '签到成功',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '签到失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '当前签到巡检点不是目标点',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
// 扫码失败处理
|
||||
// uni.showToast({
|
||||
// title: '扫码失败,请重试',
|
||||
// icon: 'none'
|
||||
// });
|
||||
}
|
||||
});
|
||||
},
|
||||
async orderSubmig(){
|
||||
this.info.orderTypeId = this.selectedType.id
|
||||
uni.showLoading({
|
||||
title: '工单提交中...'
|
||||
});
|
||||
let res = await this.$u.api.taskOrderSubmit(this.info);
|
||||
uni.hideLoading();
|
||||
if (res.code == 200) {
|
||||
this.submit()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.msg || '工单提交失败',
|
||||
icon: 'none'
|
||||
});
|
||||
this.info.orderTypeId = ''
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
// 显示loading
|
||||
uni.showLoading({
|
||||
title: '提交中...'
|
||||
});
|
||||
|
||||
if (!this.selectedImage) {
|
||||
this.realSubmit()
|
||||
return
|
||||
}
|
||||
const images = [];
|
||||
images.push(this.selectedImage?.path?.replace('file://', '') || this.selectedImage);
|
||||
if (images.length === 0) {
|
||||
this.realSubmit();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await uploadFiles({
|
||||
files: images,
|
||||
url: this.vuex_config.baseUrl + '/resource/oss/upload',
|
||||
name: 'file',
|
||||
vm: this // 关键:用于注入 token 等
|
||||
});
|
||||
const allSuccess = result.every(item => item.code == 200);
|
||||
if (!allSuccess) {
|
||||
uni.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'none'
|
||||
});
|
||||
// 隐藏loading
|
||||
uni.hideLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历result获取data.url加上,分割
|
||||
const urls = result.map(item => item.data?.url || '').filter(url => url !== '');
|
||||
this.info.inspectionImage = urls.join(',');
|
||||
this.realSubmit()
|
||||
|
||||
},
|
||||
|
||||
async realSubmit() {
|
||||
let res = await this.$u.api.taskSubmit(this.info);
|
||||
if (res.code == '200') {
|
||||
// 关闭页面前发送事件通知前页面刷新
|
||||
uni.$emit('refreshData', '');
|
||||
// 返回上一页
|
||||
uni.navigateBack();
|
||||
}
|
||||
// 隐藏loading
|
||||
uni.hideLoading();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.inspection-opt-container {
|
||||
height: 100vh;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.inspection-opt-lable {
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 40rpx;
|
||||
margin-top: 45rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
margin-top: 34rpx;
|
||||
}
|
||||
|
||||
.required {
|
||||
color: #DC9100;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: #000000;
|
||||
font-size: 24rpx;
|
||||
margin-left: 10rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text2 {
|
||||
height: 73rpx;
|
||||
flex: 1;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 20rpx;
|
||||
}
|
||||
|
||||
.text3 {
|
||||
width: 88rpx;
|
||||
height: 73rpx;
|
||||
background: #296AEF;
|
||||
border-radius: 10rpx;
|
||||
color: #F7F7F7;
|
||||
text-align: center;
|
||||
margin-left: 18rpx;
|
||||
line-height: 73rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 200rpx;
|
||||
width: auto;
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
margin-top: 40rpx;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #000000;
|
||||
padding: 15rpx;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 55rpx;
|
||||
height: 42rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-round {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #3370FF;
|
||||
margin-left: 35rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-round2 {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #fff;
|
||||
margin-left: 24rpx;
|
||||
border: 1rpx solid #3370FF;
|
||||
}
|
||||
|
||||
.inspection-opt-round3 {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #F27A0F;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.inspection-opt-round4 {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #3370FF;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.custom-upload-btn {
|
||||
width: auto;
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
margin-top: 24rpx;
|
||||
height: 244rpx;
|
||||
background: #f6f6f6;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.preview-item {
|
||||
position: relative;
|
||||
width: auto;
|
||||
/* 宽度自动 */
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
height: 244rpx;
|
||||
margin-top: 24rpx;
|
||||
/* 固定高度 */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
/* 水平居中 */
|
||||
align-items: center;
|
||||
/* 垂直居中 */
|
||||
overflow: hidden;
|
||||
/* 裁剪超出部分 */
|
||||
border-radius: 10rpx;
|
||||
/* 可选:圆角 */
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 隐藏自带按钮 */
|
||||
::v-deep .u-upload__wrap__add {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.inspection-opt-submit-btn {
|
||||
width: 60vw;
|
||||
height: 73rpx;
|
||||
background: linear-gradient(90deg, #005DE9 0%, #4B9BFF 100%);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
border-radius: 40rpx;
|
||||
margin: 100rpx auto 0 auto;
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
|
||||
/* 弹窗遮罩 */
|
||||
.custom-dialog {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* 弹窗背景图 */
|
||||
.dialog-bg {
|
||||
position: absolute;
|
||||
width: 656rpx;
|
||||
height: 560rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
/* 弹窗内容覆盖在图片上 */
|
||||
.dialog-content {
|
||||
position: relative;
|
||||
width: 80vw;
|
||||
margin-top: 220rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 42rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.dialog-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 100rpx;
|
||||
}
|
||||
|
||||
.dialog-btns {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.btn-cancel,
|
||||
.btn-confirm {
|
||||
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-confirm {
|
||||
color: #FF6B00;
|
||||
}
|
||||
|
||||
.repair-type {
|
||||
height: 98rpx;
|
||||
background: #F7F8FA;
|
||||
border-radius: 10rpx;
|
||||
padding: 0 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
box-sizing: border-box;
|
||||
margin-left: 40rpx;
|
||||
margin-right: 40rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.text-type {
|
||||
font-size: 24rpx;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.right-arrow {
|
||||
width: 11rpx;
|
||||
height: 21rpx;
|
||||
}
|
||||
</style>
|
@@ -1,274 +1,293 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
|
||||
<view class="section-title">巡检点</view>
|
||||
|
||||
<!-- 时间轴列表 -->
|
||||
<view class="timeline">
|
||||
<view v-for="(item, idx) in taskList" :key="idx" class="node"
|
||||
:class="[{ 'is-last': idx === taskList.length - 1 }]">
|
||||
<!-- 左侧:点 + 竖线 -->
|
||||
<view class="rail">
|
||||
<view class="dot" :class="{
|
||||
'dot-circle': item.dotShape === 'circle',
|
||||
'dot-square': item.dotShape === 'square',
|
||||
'dot-active': item.dotColor === 'blue'
|
||||
}"></view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<view class="card">
|
||||
<!-- 标题块 + 操作区(打包在一起,方便宽度同步) -->
|
||||
<view class="title-ops-wrapper">
|
||||
<!-- 标题块 -->
|
||||
<view v-if="item.headerStyle === 'solid'" class="title-solid">
|
||||
{{ item.pointName }}({{ item.date }} {{ item.time }})
|
||||
</view>
|
||||
<view v-else class="title-dashed">
|
||||
{{ item.pointName }}({{ item.date }} {{ item.time }})
|
||||
</view>
|
||||
|
||||
<!-- 操作区:宽度跟随标题块,内部居中 -->
|
||||
<view class="ops" v-if="item.status === '待巡检'">
|
||||
<view class="btn-outline" @click="startTask(item)">立即巡检</view>
|
||||
</view>
|
||||
|
||||
<view class="ops" v-else>
|
||||
<view class="btn-disabled">完成巡检</view>
|
||||
<view class="badge" :class="item.result === '正常' ? 'badge-success' : 'badge-warn'">
|
||||
{{ item.result }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer-placeholder">巡检提醒</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
taskList: []
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.getTaskListMock()
|
||||
},
|
||||
methods: {
|
||||
// ---- 模拟接口数据(可替换成 uni.request)----
|
||||
getTaskListMock() {
|
||||
this.taskList = [
|
||||
{
|
||||
pointName: 'A区花园',
|
||||
date: '2025-07-16',
|
||||
time: '09:00—10:00',
|
||||
status: '待巡检',
|
||||
headerStyle: 'solid',
|
||||
result: '',
|
||||
dotShape: 'circle',
|
||||
dotColor: 'gray'
|
||||
},
|
||||
{
|
||||
pointName: 'A区花园',
|
||||
date: '2025-07-16',
|
||||
time: '09:00—10:00',
|
||||
status: '已完成',
|
||||
headerStyle: 'dashed',
|
||||
result: '正常',
|
||||
dotShape: 'circle',
|
||||
dotColor: 'blue'
|
||||
},
|
||||
{
|
||||
pointName: 'A区花园',
|
||||
date: '2025-07-16',
|
||||
time: '09:00—10:00',
|
||||
status: '待巡检',
|
||||
headerStyle: 'solid',
|
||||
result: '',
|
||||
dotShape: 'square',
|
||||
dotColor: 'gray'
|
||||
},
|
||||
{
|
||||
pointName: 'A区花园',
|
||||
date: '2025-07-16',
|
||||
time: '09:00—10:00',
|
||||
status: '已完成',
|
||||
headerStyle: 'dashed',
|
||||
result: '异常',
|
||||
dotShape: 'square',
|
||||
dotColor: 'blue'
|
||||
}
|
||||
]
|
||||
<template>
|
||||
<view class="page">
|
||||
|
||||
<view class="section-title">巡检点</view>
|
||||
|
||||
<!-- 时间轴列表 -->
|
||||
<view class="timeline">
|
||||
<view v-for="(item, idx) in taskList" :key="idx" class="node"
|
||||
:class="[{ 'is-last': idx === taskList.length - 1 }]">
|
||||
<!-- 左侧:点 + 竖线 -->
|
||||
<view class="rail">
|
||||
<view class="dot" :class="{
|
||||
'dot-active': item.inspectionState == 1,
|
||||
|
||||
}"></view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<view class="card">
|
||||
<!-- 标题块 + 操作区(打包在一起,方便宽度同步) -->
|
||||
<view class="title-ops-wrapper">
|
||||
<!-- 标题块 -->
|
||||
<view v-if="item.inspectionState ==0 " class="title-solid">
|
||||
{{ item.pointName }}
|
||||
<view>
|
||||
{{ item.pointStartTime.substring(0,16) }} - {{ item.pointEndTime.substring(0,16) }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="title-dashed">
|
||||
{{ item.pointName }}
|
||||
<view>
|
||||
{{ item.pointStartTime.substring(0,16) }} - {{ item.pointEndTime.substring(0,16) }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作区:宽度跟随标题块,内部居中 -->
|
||||
<view class="ops" v-if="item.inspectionState ==0">
|
||||
<view class="btn-outline" @click="startTask(item)">立即巡检</view>
|
||||
</view>
|
||||
|
||||
<view class="status" v-else @click="goDetail(item)">
|
||||
<view>完成巡检</view>
|
||||
<view class="badge" :class="item.inspectionResults ==1 ? 'badge-success' : 'badge-warn'">
|
||||
{{ item.inspectionResults == 1 ? '正常' : '异常' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
taskInfo: {},
|
||||
taskList: []
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.item) {
|
||||
console.log('options.item:', options.item);
|
||||
try {
|
||||
// 首先尝试解析原始字符串(可能未编码)
|
||||
const item = JSON.parse(options.item);
|
||||
console.log('parsed item:', item);
|
||||
this.taskInfo = item;
|
||||
} catch (e) {
|
||||
// 如果直接解析失败,再尝试解码后解析
|
||||
try {
|
||||
const item = JSON.parse(decodeURIComponent(options.item));
|
||||
console.log('parsed decoded item:', item);
|
||||
this.taskInfo = item;
|
||||
} catch (e2) {
|
||||
// 如果两种方式都失败,记录错误并使用原始数据
|
||||
console.error('解析item失败:', e2);
|
||||
this.taskInfo = options.item;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.getTaskListMock();
|
||||
},
|
||||
onShow() {
|
||||
uni.$on('refreshData', () => {
|
||||
this.getTaskListMock();
|
||||
});
|
||||
},
|
||||
// 页面卸载时移除事件监听器
|
||||
onUnload() {
|
||||
uni.$off('refreshData');
|
||||
},
|
||||
methods: {
|
||||
async getTaskListMock() {
|
||||
let params = {
|
||||
taskId: this.taskInfo.id,
|
||||
};
|
||||
let res = await this.$u.api.getTaskList(params);
|
||||
if (res.code == 200 && res.rows) {
|
||||
// 提取res.data数组中每个对象的url字段
|
||||
this.taskList = res.rows
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
startTask(item) {
|
||||
const detailItemStr = encodeURIComponent(JSON.stringify(item));
|
||||
uni.navigateTo({
|
||||
url: `/pages/sys/workbench/inspection/inspectionOpt?item=${detailItemStr}`
|
||||
});
|
||||
},
|
||||
startTask(item) {
|
||||
uni.showToast({
|
||||
title: `开始巡检:${item.pointName}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面基础 */
|
||||
.page {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin: 24rpx 24rpx 8rpx
|
||||
}
|
||||
|
||||
/* 时间轴容器 */
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 16rpx 24rpx 40rpx 24rpx
|
||||
}
|
||||
|
||||
/* 每个节点 */
|
||||
.node {
|
||||
position: relative;
|
||||
padding-left: 72rpx;
|
||||
margin-bottom: 32rpx
|
||||
}
|
||||
|
||||
.node:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
/* 左侧导轨与连线 */
|
||||
.node::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 37rpx;
|
||||
top: 35rpx;
|
||||
bottom: -32rpx;
|
||||
width: 2rpx;
|
||||
background: #e8e9ee
|
||||
}
|
||||
|
||||
.node.is-last::after {
|
||||
display: none
|
||||
}
|
||||
|
||||
/* 左栏(点的容器) */
|
||||
.rail {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 72rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
background: #cfd3dc
|
||||
}
|
||||
|
||||
.dot-circle {
|
||||
border-radius: 50%
|
||||
}
|
||||
|
||||
.dot-square {
|
||||
border-radius: 4rpx
|
||||
}
|
||||
|
||||
.dot-active {
|
||||
background: #2f6aff
|
||||
}
|
||||
|
||||
/* 右侧卡片 */
|
||||
.card {
|
||||
padding: 16rpx 20rpx;
|
||||
}
|
||||
|
||||
/* 标题 + 操作区包裹(宽度由标题决定) */
|
||||
.title-ops-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* 标题两种样式 */
|
||||
.title-solid {
|
||||
background: #2f6aff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 12rpx 18rpx;
|
||||
display: inline-block;
|
||||
font-size: 26rpx
|
||||
}
|
||||
|
||||
.title-dashed {
|
||||
border-radius: 12rpx;
|
||||
background: #fff;
|
||||
padding: 12rpx 18rpx;
|
||||
display: inline-block;
|
||||
font-size: 26rpx;
|
||||
color: #333
|
||||
}
|
||||
|
||||
/* 操作区:宽度继承标题块,内部居中 */
|
||||
.ops {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
border: 2rpx solid #2f6aff;
|
||||
color: #2f6aff;
|
||||
background: #fff;
|
||||
padding: 12rpx 28rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx
|
||||
}
|
||||
|
||||
.btn-disabled {
|
||||
background: #f2f3f5;
|
||||
color: #a0a0a0;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx
|
||||
}
|
||||
|
||||
/* 结果徽标 */
|
||||
.badge {
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 22rpx;
|
||||
font-size: 24rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
color: #16a34a;
|
||||
border: 2rpx solid #16a34a;
|
||||
background: #fff
|
||||
}
|
||||
|
||||
.badge-warn {
|
||||
color: #f59e0b;
|
||||
border: 2rpx solid #f59e0b;
|
||||
background: #fff
|
||||
}
|
||||
|
||||
/* 底部占位文字 */
|
||||
.footer-placeholder {
|
||||
text-align: center;
|
||||
color: #e5e6eb;
|
||||
font-size: 28rpx;
|
||||
margin-top: 80rpx;
|
||||
letter-spacing: 2rpx
|
||||
}
|
||||
</style>
|
||||
|
||||
goDetail(item) {
|
||||
// const detailItemStr = encodeURIComponent(JSON.stringify(item));
|
||||
// uni.navigateTo({
|
||||
// url: `/pages/sys/workbench/inspection/inspectionDetail?item=${detailItemStr}`
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面基础 */
|
||||
.page {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #0B0B0B;
|
||||
font-weight: bold;
|
||||
padding-top: 24rpx;
|
||||
padding-left: 40rpx;
|
||||
}
|
||||
|
||||
/* 时间轴容器 */
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 16rpx 24rpx 40rpx 24rpx
|
||||
}
|
||||
|
||||
/* 每个节点 */
|
||||
.node {
|
||||
position: relative;
|
||||
padding-left: 72rpx;
|
||||
margin-bottom: 32rpx
|
||||
}
|
||||
|
||||
.node:last-child {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
/* 左侧导轨与连线 */
|
||||
.node::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 37rpx;
|
||||
top: 35rpx;
|
||||
bottom: -32rpx;
|
||||
width: 2rpx;
|
||||
background: #e8e9ee
|
||||
}
|
||||
|
||||
.node.is-last::after {
|
||||
display: none
|
||||
}
|
||||
|
||||
/* 左栏(点的容器) */
|
||||
.rail {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 72rpx;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
background: #cfd3dc;
|
||||
border-radius: 50%
|
||||
}
|
||||
|
||||
.dot-active {
|
||||
background: #2f6aff
|
||||
}
|
||||
|
||||
/* 右侧卡片 */
|
||||
.card {}
|
||||
|
||||
/* 标题 + 操作区包裹(宽度由标题决定) */
|
||||
.title-ops-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 标题两种样式 */
|
||||
.title-solid {
|
||||
background: #2f6aff;
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
padding: 12rpx 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title-dashed {
|
||||
border-radius: 12rpx;
|
||||
background: #fff;
|
||||
padding: 12rpx 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #000;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 操作区:宽度继承标题块,内部居中 */
|
||||
.ops {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.status {
|
||||
width: 280rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #BFBFBF;
|
||||
margin-top: 20rpx;
|
||||
color: #BFBFBF;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
border: 2rpx solid #2f6aff;
|
||||
color: #2f6aff;
|
||||
background: #fff;
|
||||
padding: 12rpx 28rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx
|
||||
}
|
||||
|
||||
.btn-disabled {
|
||||
background: #f2f3f5;
|
||||
color: #a0a0a0;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 26rpx
|
||||
}
|
||||
|
||||
/* 结果徽标 */
|
||||
.badge {
|
||||
font-size: 24rpx;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.badge-warn {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
/* 底部占位文字 */
|
||||
.footer-placeholder {
|
||||
text-align: center;
|
||||
color: #e5e6eb;
|
||||
font-size: 28rpx;
|
||||
margin-top: 80rpx;
|
||||
letter-spacing: 2rpx
|
||||
}
|
||||
</style>
|
583
pages/sys/workbench/leave/leave.vue
Normal file
583
pages/sys/workbench/leave/leave.vue
Normal file
@@ -0,0 +1,583 @@
|
||||
<template>
|
||||
<view class="leave-container">
|
||||
<!-- tab栏 -->
|
||||
<view class="leave-tabs">
|
||||
<view v-for="(tab, idx) in tabs" :key="idx" :class="['leave-tab', {active: idx === activeTab}]"
|
||||
@click="changeTab(idx)">
|
||||
{{ tab }}
|
||||
<view v-if="idx === activeTab" class="tab-underline"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 发起申请 -->
|
||||
<scroll-view v-show="activeTab === 0" class="leave-form" scroll-y>
|
||||
<!-- 提交人 -->
|
||||
<view class="form-row2">
|
||||
<text class="label">提交人</text>
|
||||
<view class="leave-round"></view>
|
||||
<text class="name">马花花</text>
|
||||
</view>
|
||||
|
||||
<!-- 请假类型 -->
|
||||
<view class="form-row">
|
||||
<text class="label required">请假类型</text>
|
||||
<view class="picker" @click="openLeaveTypePopup">
|
||||
<text
|
||||
:class="leaveTypeIndex === 0 ? 'placeholder' : ''">{{ leaveTypes[leaveTypeIndex] || '请选择' }}</text>
|
||||
<image class="right-arrow" src="/static/ic_right_arrow_g.png" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开始时间 -->
|
||||
<view class="form-row">
|
||||
<text class="label required">开始时间</text>
|
||||
<picker mode="date" @change="startDateChange">
|
||||
<view class="picker">
|
||||
<text :class="!startDate ? 'placeholder' : ''">{{ startDate || '请选择' }}</text>
|
||||
<image class="right-arrow" src="/static/ic_right_arrow_g.png" />
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 结束时间 -->
|
||||
<view class="form-row">
|
||||
<text class="label required">结束时间</text>
|
||||
<picker mode="date" @change="endDateChange">
|
||||
<view class="picker">
|
||||
<text :class="!endDate ? 'placeholder' : ''">{{ endDate || '请选择' }}</text>
|
||||
<image class="right-arrow" src="/static/ic_right_arrow_g.png" />
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<!-- 时间(小时) -->
|
||||
<view class="form-row">
|
||||
<text class="label required">时间(小时)</text>
|
||||
<input type="number" v-model="hours" placeholder="请输入时间(小时)" />
|
||||
</view>
|
||||
|
||||
<!-- 请假事由 -->
|
||||
<view class="form-row">
|
||||
<text class="label required">请假事由</text>
|
||||
<textarea v-model="reason" placeholder="请输入请假事由..." />
|
||||
</view>
|
||||
|
||||
<!-- 上传图片 -->
|
||||
<u-upload class="upload-style" :fileList="selectedImages" @on-list-change="onListChange" @delete="deletePic" name="upload"
|
||||
multiple maxCount="3" width="180" height="180" :autoUpload="false"></u-upload>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<button class="submit-btn" @click="submitLeave">提交</button>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 请假记录 -->
|
||||
<scroll-view v-show="activeTab === 1" class="leave-list-scroll" scroll-y :refresher-enabled="true"
|
||||
:refresher-triggered="refreshing" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
|
||||
<view v-for="(item, index) in leaveRecords" :key="index" class="oa-card" @click="goToDetail(item)">
|
||||
<view class="card-row">
|
||||
<view class="card-type">{{ item.type }}</view>
|
||||
<view class="card-status-tag" :class="item.statusClass">{{ item.status }}</view>
|
||||
</view>
|
||||
<view class="card-info">
|
||||
<view class="card-leave-type">请假类型:<text class="card-leave-type">{{ item.leaveType }}</text></view>
|
||||
<view class="card-leave-type">
|
||||
<text>开始时间:{{ item.startTime }}</text>
|
||||
</view>
|
||||
<view class="card-leave-type">
|
||||
<text>结束时间:{{ item.endTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-footer">
|
||||
<view class="card-user">
|
||||
<view class="user-avatar" :style="{backgroundColor: item.avatarColor}">{{ item.avatarText }}
|
||||
</view>
|
||||
<text class="user-name">{{ item.userName }}</text>
|
||||
</view>
|
||||
<text class="card-datetime">{{ item.dateTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 加载更多提示 -->
|
||||
<view class="loading-more">
|
||||
<view v-if="loading && !refreshing" class="loading-text">加载中...</view>
|
||||
<view v-else-if="!hasMore && leaveRecords.length > 0" class="loading-text">没有更多数据了</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 已完成 -->
|
||||
<view v-show="activeTab === 2" class="completed-tab">
|
||||
<!-- 暂时留空 -->
|
||||
</view>
|
||||
|
||||
<!-- 弹出层 -->
|
||||
<u-popup v-model="showLeaveTypePopup" mode="bottom" border-radius="16">
|
||||
<view class="popup-content">
|
||||
<view class="popup-title">请假类型</view>
|
||||
<view class="popup-options">
|
||||
<view
|
||||
v-for="(type, index) in leaveTypes"
|
||||
:key="index"
|
||||
class="popup-option"
|
||||
:class="{ 'selected': leaveTypeIndex === index }"
|
||||
@click="selectLeaveType(index)"
|
||||
>
|
||||
{{ type }}
|
||||
</view>
|
||||
</view>
|
||||
<button class="popup-submit-btn" @click="confirmLeaveType">提交</button>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tabs: ['发起申请', '请假记录', '已完成'],
|
||||
activeTab: 0,
|
||||
leaveTypes: ['事假', '调休', '病假', '年假', '产假', '陪产假', '婚假', '例假', '丧假', '哺乳假', '其他'],
|
||||
leaveTypeIndex: -1, // 初始化为-1表示未选择
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
hours: '',
|
||||
reason: '',
|
||||
selectedImages: [],
|
||||
leaveRecords: [],
|
||||
loading: false,
|
||||
hasMore: true,
|
||||
refreshing: false,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
showLeaveTypePopup: false,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTab(idx) {
|
||||
this.activeTab = idx;
|
||||
},
|
||||
bindPickerChange(e) {
|
||||
this.leaveTypeIndex = e.detail.value;
|
||||
},
|
||||
startDateChange(e) {
|
||||
this.startDate = e.detail.value;
|
||||
},
|
||||
endDateChange(e) {
|
||||
this.endDate = e.detail.value;
|
||||
},
|
||||
submitLeave() {
|
||||
// 提交请假逻辑
|
||||
},
|
||||
deletePic(event) {
|
||||
this.selectedImages.splice(event.index, 1);
|
||||
},
|
||||
onListChange(list) {
|
||||
this.selectedImages = list;
|
||||
},
|
||||
goToDetail(item) {
|
||||
// 跳转到详情页逻辑
|
||||
},
|
||||
async onRefresh() {
|
||||
this.refreshing = true;
|
||||
this.pageNum = 1;
|
||||
await this.loadLeaveRecords();
|
||||
this.refreshing = false;
|
||||
},
|
||||
async onLoadMore() {
|
||||
if (!this.hasMore || this.loading) return;
|
||||
this.pageNum++;
|
||||
await this.loadLeaveRecords();
|
||||
},
|
||||
async loadLeaveRecords() {
|
||||
this.loading = true;
|
||||
let data = [
|
||||
{
|
||||
type: '请假',
|
||||
leaveType: '病假',
|
||||
status: '已通过',
|
||||
statusClass: 'green',
|
||||
startTime: '2025-07-13',
|
||||
endTime: '2025-07-15',
|
||||
userName: '余永乐',
|
||||
avatarText: '余',
|
||||
avatarColor: '#4B7BF5',
|
||||
dateTime: '07-12 18:28:22'
|
||||
},
|
||||
{
|
||||
type: '请假',
|
||||
leaveType: '病假',
|
||||
status: '待审核',
|
||||
statusClass: 'orange',
|
||||
startTime: '2025-07-13',
|
||||
endTime: '2025-07-15',
|
||||
userName: '余永乐',
|
||||
avatarText: '余',
|
||||
avatarColor: '#4B7BF5',
|
||||
dateTime: '07-12 18:28:22'
|
||||
}
|
||||
];
|
||||
const start = (this.pageNum - 1) * this.pageSize;
|
||||
const end = start + this.pageSize;
|
||||
const pageData = data.slice(start, end);
|
||||
await new Promise(res => setTimeout(res, 300));
|
||||
if (this.pageNum === 1) {
|
||||
this.leaveRecords = pageData;
|
||||
} else {
|
||||
this.leaveRecords = [...this.leaveRecords, ...pageData];
|
||||
}
|
||||
this.hasMore = end < data.length;
|
||||
this.loading = false;
|
||||
},
|
||||
openLeaveTypePopup() {
|
||||
this.showLeaveTypePopup = true;
|
||||
},
|
||||
selectLeaveType(index) {
|
||||
this.leaveTypeIndex = index;
|
||||
},
|
||||
confirmLeaveType() {
|
||||
if (this.leaveTypeIndex !== -1) {
|
||||
this.showLeaveTypePopup = false;
|
||||
// 更新表单中的请假类型
|
||||
this.leaveType = this.leaveTypes[this.leaveTypeIndex];
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '请选择请假类型',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.leave-container {
|
||||
height: 100vh;
|
||||
background: #f7f7f7;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.leave-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
background: #fff;
|
||||
height: 80rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.leave-tab {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
color: #737373;
|
||||
position: relative;
|
||||
padding: 0 0 10rpx 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.leave-tab.active {
|
||||
color: #007CFF;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-underline {
|
||||
width: 60rpx;
|
||||
height: 6rpx;
|
||||
background: #2186FF;
|
||||
border-radius: 3rpx;
|
||||
margin: 0 auto;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* 让 leave-form 可滚动 */
|
||||
.leave-form {
|
||||
flex: 1;
|
||||
padding: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 10rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
margin-bottom: 34rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 20rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.form-row2 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 34rpx;
|
||||
margin-left: 20rpx;
|
||||
margin-right: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 32rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.required::after {
|
||||
content: "*";
|
||||
color: #DC9100;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
height: 73rpx;
|
||||
width: auto;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20rpx;
|
||||
margin-top: 20rpx;
|
||||
margin-right: 40rpx;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.right-arrow {
|
||||
width: 11rpx;
|
||||
height: 21rpx;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 73rpx;
|
||||
width: auto;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 200rpx;
|
||||
width: auto;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #000000;
|
||||
padding: 15rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.leave-round {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-radius: 17rpx;
|
||||
background: #3370FF;
|
||||
margin-left: 18rpx;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 24rpx;
|
||||
color: #000000;
|
||||
margin-left: 10rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 80vw;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(90deg, #005DE9 0%, #4B9BFF 100%);
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
margin-top: 20rpx;
|
||||
margin-bottom: 90rpx;
|
||||
}
|
||||
|
||||
.leave-list-scroll {
|
||||
flex: 1;
|
||||
padding: 0 35rpx;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.oa-card {
|
||||
background: #fff;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 26rpx;
|
||||
padding: 20rpx 20rpx 20rpx 20rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 46rpx;
|
||||
}
|
||||
|
||||
.card-type {
|
||||
width: 126rpx;
|
||||
height: 46rpx;
|
||||
font-weight: 600;
|
||||
font-size: 32rpx;
|
||||
padding-left: 10rpx;
|
||||
padding-top: 7rpx;
|
||||
border-radius: 15rpx;
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, #007CFF 0%, #FFFFFF 100%);
|
||||
}
|
||||
|
||||
.card-status-tag {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.card-status-tag.orange {
|
||||
color: #F27A0F;
|
||||
}
|
||||
|
||||
.card-status-tag.green {
|
||||
color: #0AC88F;
|
||||
}
|
||||
|
||||
.card-status-tag.gray {
|
||||
color: #8F8F8F;
|
||||
background-color: rgba(143, 143, 143, 0.1);
|
||||
border: 1px solid #8F8F8F;
|
||||
}
|
||||
|
||||
.card-info {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 42rpx;
|
||||
}
|
||||
|
||||
.card-leave-type {
|
||||
color: #626262;
|
||||
font-size: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
border-radius: 27rpx;
|
||||
background-color: #688CFF;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
margin-right: 18rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 24rpx;
|
||||
color: #626262;
|
||||
}
|
||||
|
||||
.card-datetime {
|
||||
font-size: 24rpx;
|
||||
color: #626262;
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
.upload-style{
|
||||
margin-left: 15rpx;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.custom-leave-type-btn {
|
||||
width: 100%;
|
||||
height: 73rpx;
|
||||
background: #F7F7F7;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20rpx;
|
||||
margin-top: 20rpx;
|
||||
font-size: 32rpx;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding: 40rpx;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 36rpx;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.popup-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.popup-option {
|
||||
width: 30%;
|
||||
height: 80rpx;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.popup-option.selected {
|
||||
background-color: #3370FF;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popup-submit-btn {
|
||||
width: 80vw;
|
||||
height: 88rpx;
|
||||
background: linear-gradient(90deg, #005DE9 0%, #4B9BFF 100%);
|
||||
border-radius: 44rpx;
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
</style>
|
@@ -23,7 +23,12 @@
|
||||
v-show="idx === activeTab"
|
||||
class="oa-list-scroll"
|
||||
scroll-y
|
||||
:refresher-enabled="false">
|
||||
:scroll-top="scrollTop[idx]"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="refreshing[idx]"
|
||||
@refresherrefresh="onRefresh"
|
||||
@scrolltolower="onLoadMore"
|
||||
@scroll="onScroll($event, idx)">
|
||||
<view v-for="(item, index) in tabData[idx]" :key="index" class="oa-card" @click="goToDetail(item)">
|
||||
<view class="card-row">
|
||||
<view class="card-type">{{ item.type }}</view>
|
||||
@@ -46,6 +51,11 @@
|
||||
<text class="card-datetime">{{ item.dateTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 加载更多提示 -->
|
||||
<view class="loading-more">
|
||||
<view v-if="loading && !refreshing[idx]" class="loading-text">加载中...</view>
|
||||
<view v-else-if="!hasMore[idx] && tabData[idx].length > 0" class="loading-text">没有更多数据了</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -63,7 +73,12 @@
|
||||
[] // 已发起
|
||||
],
|
||||
tabLoaded: [false, false, false], // 每个tab是否已加载
|
||||
loading: false
|
||||
loading: false,
|
||||
pageNum: [1, 1, 1], // 每个tab的页码
|
||||
pageSize: 10, // 每页条数
|
||||
hasMore: [true, true, true], // 每个tab是否还有更多数据
|
||||
refreshing: [false, false, false], // 每个tab是否正在刷新
|
||||
scrollTop: [0, 0, 0] // 每个tab的滚动位置
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -93,6 +108,32 @@
|
||||
}
|
||||
*/
|
||||
},
|
||||
// 监听滚动事件,记录每个tab的滚动位置
|
||||
onScroll(e, idx) {
|
||||
this.scrollTop[idx] = e.detail.scrollTop;
|
||||
},
|
||||
// 下拉刷新
|
||||
async onRefresh() {
|
||||
// 设置当前tab为刷新状态
|
||||
this.$set(this.refreshing, this.activeTab, true);
|
||||
// 重置页码
|
||||
this.$set(this.pageNum, this.activeTab, 1);
|
||||
// 重新加载数据
|
||||
await this.loadTabData(this.activeTab);
|
||||
// 取消刷新状态
|
||||
this.$set(this.refreshing, this.activeTab, false);
|
||||
},
|
||||
// 上拉加载更多
|
||||
async onLoadMore() {
|
||||
// 如果没有更多数据或正在加载,则不处理
|
||||
if (!this.hasMore[this.activeTab] || this.loading) {
|
||||
return;
|
||||
}
|
||||
// 页码加1
|
||||
this.$set(this.pageNum, this.activeTab, this.pageNum[this.activeTab] + 1);
|
||||
// 加载更多数据
|
||||
await this.loadTabData(this.activeTab);
|
||||
},
|
||||
async loadTabData(idx) {
|
||||
this.loading = true;
|
||||
// 模拟接口请求,不同tab返回不同mock数据
|
||||
@@ -227,9 +268,25 @@
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// 模拟分页效果
|
||||
const start = (this.pageNum[idx] - 1) * this.pageSize;
|
||||
const end = start + this.pageSize;
|
||||
const pageData = data.slice(start, end);
|
||||
|
||||
// 模拟网络延迟
|
||||
await new Promise(res => setTimeout(res, 300));
|
||||
this.$set(this.tabData, idx, data);
|
||||
|
||||
if (this.pageNum[idx] === 1) {
|
||||
// 刷新操作,替换数据
|
||||
this.$set(this.tabData, idx, pageData);
|
||||
} else {
|
||||
// 加载更多,追加数据
|
||||
this.tabData[idx] = [...this.tabData[idx], ...pageData];
|
||||
}
|
||||
|
||||
// 判断是否还有更多数据
|
||||
this.$set(this.hasMore, idx, end < data.length);
|
||||
this.$set(this.tabLoaded, idx, true);
|
||||
this.loading = false;
|
||||
},
|
||||
@@ -334,6 +391,7 @@
|
||||
.oa-list-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.oa-list-scroll {
|
||||
@@ -344,8 +402,6 @@
|
||||
bottom: 0;
|
||||
padding: 0 35rpx;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 50rpx;
|
||||
height: calc(100vh - 80rpx - 32rpx - 150rpx); /* 减去顶部区域高度 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -354,7 +410,6 @@
|
||||
padding: 0 35rpx;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.oa-card {
|
||||
@@ -448,6 +503,16 @@
|
||||
font-size: 24rpx;
|
||||
color: #626262;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.loading-more {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
@@ -148,9 +148,13 @@ export default {
|
||||
this.isManager = this.vuex_user.roles[0].roleId == 1
|
||||
},
|
||||
onShow() {
|
||||
uni.$once('refreshData', () => {
|
||||
uni.$on('refreshData', () => {
|
||||
this.loadAllTabsData();
|
||||
});
|
||||
},
|
||||
// 页面卸载时移除事件监听器
|
||||
onUnload() {
|
||||
uni.$off('refreshData');
|
||||
},
|
||||
methods: {
|
||||
async changeTab(idx) {
|
||||
|
@@ -48,17 +48,7 @@
|
||||
data() {
|
||||
return {
|
||||
commonApps: [
|
||||
// {
|
||||
// icon: 'https://picsum.photos/80/80?random=3',
|
||||
// text: '工作巡检',
|
||||
// url:'/pages/sys/workbench/inspection/inspection'
|
||||
// },
|
||||
// {
|
||||
// icon: 'https://picsum.photos/80/80?random=3',
|
||||
// text: '假勤',
|
||||
// url:'/pages/sys/workbench/camera'
|
||||
|
||||
// },
|
||||
|
||||
{
|
||||
icon: '/static/aaaa_gd.png',
|
||||
text: '工单',
|
||||
@@ -83,11 +73,26 @@
|
||||
icon: '/static/aaaa_bsbx.png',
|
||||
text: '报事报修',
|
||||
url:'/pages/sys/user/myRepair/myRepair'
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'https://picsum.photos/80/80?random=3',
|
||||
text: '工作巡检',
|
||||
url:'/pages/sys/workbench/inspection/inspection'
|
||||
},
|
||||
// {
|
||||
// icon: 'https://picsum.photos/80/80?random=3',
|
||||
// text: '通讯录',
|
||||
// url:'/pages/sys/workbench/book/book'
|
||||
// },
|
||||
// {
|
||||
// icon: 'https://picsum.photos/80/80?random=3',
|
||||
// text: '请假',
|
||||
// url:'/pages/sys/workbench/leave/leave'
|
||||
// },
|
||||
// {
|
||||
// icon: 'https://picsum.photos/80/80?random=3',
|
||||
// text: '会议',
|
||||
// url:'/pages/sys/workbench/meet/meet'
|
||||
// text: 'oa',
|
||||
// url:'/pages/sys/workbench/oa/oa'
|
||||
// },
|
||||
// {
|
||||
// icon: 'https://picsum.photos/80/80?random=3',
|
||||
|
BIN
static/ic_camera.png
Normal file
BIN
static/ic_camera.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
static/ic_dialog_01.png
Normal file
BIN
static/ic_dialog_01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 312 KiB |
BIN
static/ic_exp.png
Normal file
BIN
static/ic_exp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
static/ic_sq.png
Normal file
BIN
static/ic_sq.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 326 B |
Reference in New Issue
Block a user