请假 巡检
Some checks failed
Uniapp 自动化打包 CI/CD / 打包 Uniapp 项目 (push) Has been cancelled

This commit is contained in:
2025-09-12 09:24:59 +08:00
parent 9ed827a227
commit 57fe929080
14 changed files with 973 additions and 499 deletions

31
App.vue
View File

@@ -1,18 +1,33 @@
<script> <script>
/** /**
* Copyright (c) 2013-Now http://aidex.vip All rights reserved. * Copyright (c) 2013-Now http://aidex.vip All rights reserved.
*/ */
export default { export default {
onLaunch() { onLaunch() {
// 国际化,设置当前语言 // 国际化,设置当前语言
if (this.vuex_locale){ if (this.vuex_locale) {
this.$i18n.locale = this.vuex_locale; this.$i18n.locale = this.vuex_locale;
this.$u.api.lang({lang: this.vuex_locale}); this.$u.api.lang({
lang: this.vuex_locale
});
} }
//只有在基座运行的情况下才能打印看到 //只有在基座运行的情况下才能打印看到
const clientInfo = plus.push.getClientInfo() const clientInfo = plus.push.getClientInfo()
console.log(clientInfo)
this.$store.commit('$uStore', {
name: 'vuex_push_clientId',
value: clientInfo.clientid
});
// 监听系统通知栏点击事件
plus.push.addEventListener("click", function(msg) {
console.log("用户点击了推送消息:", msg);
// msg.payload 就是推送时传的自定义数据
uni.navigateTo({
url: "/pages/sys/workbench/earlyWarning/warnDetail",
});
});
// 设置底部导航栏角标 // 设置底部导航栏角标
// uni.setTabBarBadge({ // uni.setTabBarBadge({
@@ -23,12 +38,12 @@ export default {
// index: 0 // index: 0
// }); // });
} }
} }
</script> </script>
<style> <style>
@import url("~@/static/iconfont/iconfont.css"); @import url("~@/static/iconfont/iconfont.css");
</style> </style>
<style lang="scss"> <style lang="scss">
@import "uview-ui/index.scss"; @import "uview-ui/index.scss";
@import "pages/common/aidex.scss"; @import "pages/common/aidex.scss";
</style> </style>

View File

@@ -30,5 +30,5 @@ const config = {
config.baseUrl = 'http://183.230.235.66:11010/api'; config.baseUrl = 'http://183.230.235.66:11010/api';
// config.baseUrl = 'http://5b35f15d.r28.cpolar.top'; // config.baseUrl = 'http://3efb1a71.r28.cpolar.top';
export default config; export default config;

View File

@@ -26,6 +26,7 @@ const install = (Vue, vm) => {
} }
req.header["source"] = "uniapp"; req.header["source"] = "uniapp";
req.header["clientId"] = "dab457a1ea14411787c240db05bb0832" req.header["clientId"] = "dab457a1ea14411787c240db05bb0832"
req.header["pushClientId"] = vm.vuex_push_clientId
// 默认指定返回 JSON 数据 // 默认指定返回 JSON 数据
if (!req.header[ajaxHeader]){ if (!req.header[ajaxHeader]){
req.header[ajaxHeader] = 'json'; req.header[ajaxHeader] = 'json';

View File

@@ -1,12 +1,12 @@
<template> <template>
<view class="calendar-container"> <view class="calendar-container">
<!-- 展开/收起按钮可以外部隐藏 --> <!-- 展开/收起按钮可以外部隐藏 -->
<view class="calendar-header"> <!-- <view class="calendar-header">
<button size="mini" @click="toggleMode"> <button size="mini" @click="toggleMode">
{{ mode === 'month' ? '收起为周' : '展开为月' }} {{ mode === 'month' ? '收起为周' : '展开为月' }}
</button> </button>
<text>{{ displayTitle }}</text> <text>{{ displayTitle }}</text>
</view> </view> -->
<view <view
class="calendar-content" class="calendar-content"
@@ -46,7 +46,7 @@ export default {
props: { props: {
initialMode: { type: String, default: 'month' }, // 'month' or 'week' initialMode: { type: String, default: 'month' }, // 'month' or 'week'
initialDate: { type: String, default: '' }, initialDate: { type: String, default: '' },
allowWeekSwitch: { type: Boolean, default: true } // 控制是否允许上下滑切换 allowWeekSwitch: { type: Boolean, default: false } // 控制是否允许上下滑切换
}, },
data() { data() {
const today = new Date() const today = new Date()
@@ -68,8 +68,10 @@ export default {
} }
}, },
watch: { watch: {
mode() { mode(newMode, oldMode) {
this.generateRenderDays() this.generateRenderDays()
// 通知父组件mode已改变
this.$emit('modeChange', newMode);
}, },
selected() { selected() {
this.generateRenderDays() this.generateRenderDays()
@@ -231,15 +233,27 @@ export default {
let year = y let year = y
if (m > 12) { m = 1; year++ } if (m > 12) { m = 1; year++ }
if (m < 1) { m = 12; year-- } if (m < 1) { m = 12; year-- }
// 切换后的基准日期设为该月1号
this.anchorDate = new Date(year, m - 1, 1) this.anchorDate = new Date(year, m - 1, 1)
this.selected = this.formatDate(this.anchorDate) // ✅ 选中当月1号
this.$emit('dateChange', this.selected)
} else { } else {
const d = this.safeDate(this.selected) // 以当前 anchorDate 为基准移动周
const d = this.safeDate(this.anchorDate)
d.setDate(d.getDate() + direction * 7) d.setDate(d.getDate() + direction * 7)
this.selected = this.formatDate(d)
// 更新基准周
this.anchorDate = this.startOfWeek(d) this.anchorDate = this.startOfWeek(d)
// ✅ 选中该周周日startOfWeek返回的就是周日
this.selected = this.formatDate(this.anchorDate)
this.$emit('dateChange', this.selected)
} }
this.generateRenderDays() this.generateRenderDays()
} }
} }
} }
</script> </script>
@@ -249,7 +263,7 @@ export default {
width: 100%; width: 100%;
background: #fff; background: #fff;
border-radius: 12rpx; border-radius: 12rpx;
box-shadow: 0 2rpx 16rpx #eee; /* box-shadow: 0 2rpx 16rpx #eee; */
padding: 12rpx; padding: 12rpx;
} }
.calendar-header { .calendar-header {

View File

@@ -0,0 +1,140 @@
<template>
<view>
<view v-if="visible" class="mask" @click="close"></view>
<!-- 底部弹窗 -->
<view class="popup" v-if="visible">
<!-- 弹窗标题 -->
<view class="dialog-title">选择时间</view>
<!-- 日期显示区域 -->
<view class="date-display">
{{ selectedDate }}
</view>
<!-- 日历组件 -->
<view class="calendar-wrapper">
<CommonCalendar :initial-date="selectedDate" @dateChange="handleDateSelected" />
</view>
<!-- 确认按钮 -->
<view class="confirm-btn" @click="confirmSelection">
确认
</view>
</view>
</view>
</template>
<script>
import CommonCalendar from '@/components/CommonCalendar.vue'
export default {
components: {
CommonCalendar
},
name: "SelectCalendarDialog",
props: {
visible: {
type: Boolean,
default: false
}
},
data() {
return {
selectedDate: this.getCurrentDate(), // 默认选中的日期
showCalendar: true // 控制日历是否显示
};
},
methods: {
close() {
this.$emit('update:visible', false);
},
// 获取当前日期
getCurrentDate() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
},
// 处理日期选择
handleDateSelected(date) {
this.selectedDate = date;
// 不再需要手动关闭日历,因为日历会一直显示在弹窗中
},
// 确认选择
confirmSelection() {
this.$emit('confirm', this.selectedDate);
this.close()
}
}
};
</script>
<style scoped>
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
z-index: 998;
}
.popup {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
max-height: 80%;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
overflow: hidden;
display: flex;
flex-direction: column;
z-index: 999;
animation: slideUp 0.3s ease;
}
.dialog-title {
font-size: 36rpx;
color: #333;
margin-bottom: 20rpx;
margin-top: 45rpx;
text-align: center;
}
.date-display {
width: 100%;
height: 73rpx;
line-height: 73rpx;
text-align: start;
background: #EBF5FF;
border-radius: 10rpx;
font-size: 28rpx;
color: #0B0B0B;
margin-bottom: 6rpx;
margin-left: 35rpx;
margin-right: 35rpx;
padding-left: 35rpx;
}
.calendar-wrapper {
width: 100%;
margin-bottom: 89rpx;
}
.confirm-btn {
height: 88rpx;
line-height: 88rpx;
text-align: center;
background-color: #0090FF;
color: white;
border-radius: 44rpx;
font-size: 36rpx;
margin-left: 65rpx;
margin-right: 65rpx;
margin-bottom: 40rpx;
}
</style>

115
components/punchInfo.vue Normal file
View File

@@ -0,0 +1,115 @@
<template>
<!-- 上班 -->
<view class="timeline-item">
<view class="timeline-dot"></view>
<view class="timeline-content">
<text class="timeline-label">应上班 09:00</text>
<view class="clock-card late">
<text class="clock-text">已打卡 08:48:32</text>
<text class="status orange">迟到</text>
<text class="buka" @click="goBuka">申请补卡</text>
<view class="location">
<text class="icon">📍</text>
<text class="loc-text">某综合服务中心1栋</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default{
props:{
info:{}
},
methods:{
goBuka(){
uni.navigateTo({
url: '/pages/sys/user/myRecord/cardReplacement'
});
}
}
}
</script>
<style>
.timeline-item {
position: relative;
padding-left: 20rpx;
margin-left: 60rpx;
margin-right: 90rpx;
margin-bottom: 40rpx;
border-left: 2rpx solid #e0e0e0; /* 竖线 */
}
.timeline-item:last-child {
margin-bottom: 0;
}
.timeline-dot {
position: absolute;
left: -10rpx;
top: -10rpx;
width: 16rpx;
height: 16rpx;
background-color: #666;
border-radius: 50%;
}
.timeline-content {
margin-left: 20rpx;
}
.timeline-label {
font-size: 28rpx;
color: #737373;
}
/* 打卡卡片 */
.clock-card {
background: #fff;
border-radius: 12rpx;
padding: 24rpx;
margin-top: 23rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
}
.clock-text {
font-size: 34rpx;
color: #000000;
font-weight: 500;
}
.status {
margin-left: 20rpx;
font-size: 28rpx;
}
.status.orange {
color: #ff6b35;
}
.buka {
margin-left: 20rpx;
font-size: 28rpx;
color: #3370FF;
}
/* 地点 */
.location {
display: flex;
align-items: center;
margin-top: 12rpx;
}
.icon {
margin-right: 6rpx;
}
.loc-text {
font-size: 28rpx;
color: #1C9BFF;
}
</style>

View File

@@ -388,6 +388,12 @@
"navigationBarTitleText": "我的考勤" "navigationBarTitleText": "我的考勤"
} }
}, },
{
"path": "pages/sys/user/myRecord/cardReplacement",
"style": {
"navigationBarTitleText": "补卡申请"
}
},
{ {
"path": "pages/sys/workbench/oa/oa", "path": "pages/sys/workbench/oa/oa",
"style": { "style": {

View File

@@ -0,0 +1,420 @@
<template>
<view class="page-container">
<!-- 表单区域 -->
<view class="form-section">
<view class="form-item">
<text class="form-label">补卡时间</text>
<picker mode="date" :value="form.date" @change="onDateChange">
<view class="picker">{{ form.date }} {{ form.time }}</view>
</picker>
</view>
<view class="tip-text">
<text>本月已补卡{{ usedTimes }}剩余{{ remainTimes }}</text>
</view>
<view class="form-item">
<text class="form-label">补卡说明</text>
<textarea v-model="form.remark" placeholder="请输入" class="textarea" />
</view>
<view class="form-item">
<text class="form-label">附件</text>
<!-- 如果没有图片显示上传按钮 -->
<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>
<!-- 审批记录 -->
<view class="record-section">
<text class="section-title">审批记录</text>
<view class="timeline">
<view v-for="(item, index) in records" :key="index" class="timeline-item">
<!-- -->
<view class="timeline-dot"></view>
<!-- 节点内容 -->
<view class="timeline-content">
<text class="end-text">{{ item.node }}</text>
<view class="record-row" v-if="item.status !== '结束'">
<image class="avatar" :src="item.avatar" mode="aspectFill" />
<view class="name-node-container">
<text class="name">{{ item.name }}</text>
<text class="status" :class="statusClass(item.status)">{{ item.status }}</text>
</view>
<text class="time2">{{ item.time }}</text>
</view>
<text v-else class="time">{{ item.time }}</text>
</view>
</view>
</view>
</view>
<!-- 提交按钮 -->
<view class="footer">
<button class="submit-btn" type="primary" @click="submit">提交</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
usedTimes: 0,
remainTimes: 5,
selectedImage: '',
form: {
date: '2025-07-13',
time: '09:00',
remark: ''
},
records: [{
node: '提交',
name: '于永乐',
status: '已提交',
time: '07-12 18:28:22',
avatar: '/static/avatar1.png'
},
{
node: '审批',
name: '张桂花',
status: '已同意',
time: '07-12 18:30:12',
avatar: '/static/avatar2.png'
},
{
node: '审批',
name: '张桂花',
status: '已拒绝',
time: '07-12 19:12:45',
avatar: '/static/avatar2.png'
},
{
node: '结束',
name: '',
status: '结束',
time: '07-12 20:00:00',
avatar: ''
}
]
}
},
methods: {
onDateChange(e) {
this.form.date = e.detail.value
},
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 = ''
},
submit() {
uni.showToast({
title: '提交成功',
icon: 'success'
})
},
statusClass(status) {
if (status === '已提交') return 'blue'
if (status === '已同意') return 'green'
if (status === '已拒绝') return 'orange'
if (status === '结束') return 'gray'
return ''
}
}
}
</script>
<style scoped>
.page-container {
background: #f5f5f5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.form-section {
background: #fff;
margin: 20rpx;
padding: 20rpx;
border-radius: 12rpx;
}
.form-item {
margin-bottom: 20rpx;
}
.form-label {
font-size: 24rpx;
color: #737373;
margin-bottom: 10rpx;
display: block;
}
.tip-text {
height: 73rpx;
line-height: 73rpx;
background: #F7F7F7;
color: #808080;
font-size: 24rpx;
border-radius: 10rpx;
padding-left: 16rpx;
}
.textarea {
width: 100%;
height: 160rpx;
border: 2rpx solid #ddd;
border-radius: 8rpx;
padding: 12rpx;
font-size: 24rpx;
}
.upload-box {
border: 2rpx dashed #aaa;
border-radius: 12rpx;
padding: 40rpx;
text-align: center;
color: #888;
}
.upload-icon {
width: 60rpx;
height: 60rpx;
margin-bottom: 12rpx;
}
/* 审批记录 */
.record-section {
background: #fff;
border-radius: 25rpx;
margin-left: 32rpx;
margin-right: 32rpx;
padding: 35rpx 25rpx 35rpx 25rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
margin-bottom: 20rpx;
color: #333;
}
/* timeline 方法二:每个节点自己画线 */
.timeline {
position: relative;
margin-left: 12rpx;
margin-top: 45rpx;
}
.timeline-item {
position: relative;
padding-left: 40rpx;
padding-bottom: 50rpx;
}
.timeline-item::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: -2rpx;
width: 2rpx;
background: #ddd;
}
/* 第一个节点:去掉上半段 */
.timeline-item:first-child::before {
top: 20rpx;
}
/* 最后一个节点:去掉下半段 */
.timeline-item:last-child::before {
bottom: auto;
height: 20rpx;
}
/* 状态点 */
.timeline-dot {
position: absolute;
left: -12rpx;
top: 10rpx;
width: 20rpx;
height: 20rpx;
border: 1rpx solid #10AF7F;
border-radius: 50%;
background: #fff;
}
.timeline-content {
margin-left: -20rpx;
}
.record-row {
display: flex;
align-items: center;
margin-bottom: 8rpx;
}
.avatar {
width: 54rpx;
height: 54rpx;
border-radius: 50%;
margin-right: 15rpx;
background: #688CFF;
}
.name-node-container {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.name {
font-size: 28rpx;
color: #000;
}
.status {
font-size: 22rpx;
font-weight: 500;
}
.status.blue {
color: #007aff;
}
.status.green {
color: #4caf50;
}
.status.orange {
color: #ff6b35;
}
.status.gray {
color: #999;
}
.end-text {
font-size: 24rpx;
color: #000;
font-weight: bold;
}
.time {
font-size: 24rpx;
color: #626262;
float: right;
margin-right: 40rpx;
}
.time2 {
font-size: 24rpx;
color: #626262;
position: absolute;
right: 40rpx;
bottom: 70rpx;
}
.footer {
padding: 20rpx;
}
.submit-btn {
width: 100%;
height: 90rpx;
line-height: 90rpx;
border-radius: 50rpx;
background: #007aff;
color: #fff;
font-size: 32rpx;
}
.text {
color: #000000;
font-size: 24rpx;
margin-left: 10rpx;
font-weight: 400;
}
.image {
width: 55rpx;
height: 42rpx;
}
.custom-upload-btn {
width: auto;
margin-left: 0rpx;
margin-right: 0rpx;
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;
}
</style>

View File

@@ -1,18 +1,6 @@
<template> <template>
<view class="my-record-container"> <view class="my-record-container">
<text class="month-title">{{ selectedDate.substring(0,4) }}{{ selectedDate.substring(5,7) }}</text>
<!-- 月份标题和切换 -->
<view class="month-header">
<view class="month-nav">
<view class="month-arrow" @click="prevMonth">
<image class="arrow-icon" src="/static/ic_arrow_gray.webp" mode="aspectFit" style="transform: rotate(180deg)" />
</view>
<text class="month-title">{{ currentYear }}{{ currentMonth }}</text>
<view class="month-arrow" @click="nextMonth">
<image class="arrow-icon" src="/static/ic_arrow_gray.webp" mode="aspectFit" />
</view>
</view>
</view>
<!-- 考勤统计 --> <!-- 考勤统计 -->
<view class="attendance-stats"> <view class="attendance-stats">
@@ -36,35 +24,11 @@
<!-- 日历 --> <!-- 日历 -->
<view class="calendar"> <view class="calendar">
<view class="weekdays">
<text v-for="day in weekdays" :key="day" class="weekday">{{ day }}</text>
</view>
<view class="dates" v-if="calendarExpanded">
<view
v-for="(date, index) in allDates"
:key="index"
:class="getDateClass(date)"
@click="selectDate(date)"
>
<text v-if="date.value" class="date-text">{{ date.value }}</text>
<view v-if="date.hasRecord" class="record-dot"></view>
</view>
</view>
<view class="dates" v-else>
<view
v-for="(date, index) in currentWeekDates"
:key="index"
:class="getDateClass(date)"
@click="selectDate(date)"
>
<text v-if="date.value" class="date-text">{{ date.value }}</text>
<view v-if="date.hasRecord" class="record-dot"></view>
</view>
</view>
<CommonCalendar ref="calendarComponent" :initial-date="selectedDate" :allowWeekSwitch='true' :initialMode='mode' @dateChange="handleDateSelected" @modeChange="handleModeChange" />
<!-- 展开/收缩按钮 --> <!-- 展开/收缩按钮 -->
<view class="calendar-toggle" @click="toggleCalendar"> <view class="calendar-toggle" @click="toggleCalendarMode">
<image v-if="calendarExpanded" class='image_zk' src="/static/ic_exp.png"></image> <image v-if="mode == 'month'" class='image_zk' src="/static/ic_exp.png"></image>
<image v-else class='image_sq' src="/static/ic_sq.png"></image> <image v-else class='image_sq' src="/static/ic_sq.png"></image>
</view> </view>
</view> </view>
@@ -72,146 +36,54 @@
<!-- 固定班次 --> <!-- 固定班次 -->
<view class="fixed-shifts"> <view class="fixed-shifts">
<text class="shifts-title">固定班次</text> <text class="shifts-title">固定班次</text>
<PunchInfo/>
<view class="shift-item"> <PunchInfo/>
<view class="shift-time">
<view class="time-dot"></view>
<text class="time-text">应上班 09:00</text>
</view>
<view class="record leave-record">
<text class="record-text">已请假 09:0012:00</text>
</view>
</view>
<view class="shift-item">
<view class="shift-time">
<view class="time-dot"></view>
<text class="time-text">应下班 18:00</text>
</view>
<view class="record clock-record">
<text class="record-text">已打卡 18:02:18</text>
<text class="location">@某综合服务中心1栋</text>
</view>
</view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import CommonCalendar from '@/components/CommonCalendar.vue'
import PunchInfo from '@/components/punchInfo.vue'
export default { export default {
components: {
CommonCalendar,
PunchInfo
},
data() { data() {
return { return {
weekdays: ['日', '一', '二', '三', '四', '五', '六'], selectedDate: this.getCurrentDate(), // 默认选中的日期
calendarExpanded: true, mode: 'month'
selectedDate: 8,
currentYear: 2025,
currentMonth: 7,
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);
}
},
methods: { methods: {
toggleCalendar() { // 获取当前日期
this.calendarExpanded = !this.calendarExpanded; getCurrentDate() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}, },
selectDate(date) {
if (!date.value) return;
this.allDates.forEach(d => d.selected = false);
date.selected = true;
this.selectedDate = date.value;
},
getDateClass(date) {
return {
'date-item': true,
'selected': date.selected,
'has-record': date.hasRecord,
'empty': !date.value
};
},
prevMonth() {
if (this.currentMonth === 1) {
this.currentYear--;
this.currentMonth = 12;
} else {
this.currentMonth--;
}
this.generateCalendarDates();
},
nextMonth() {
if (this.currentMonth === 12) {
this.currentYear++;
this.currentMonth = 1;
} else {
this.currentMonth++;
}
this.generateCalendarDates();
},
generateCalendarDates() {
this.allDates = this.buildMonthDates(this.currentYear, this.currentMonth);
let prevY = this.currentYear, prevM = this.currentMonth - 1; // 处理日期选择
if (prevM === 0) { prevM = 12; prevY--; } handleDateSelected(date) {
this.prevMonthDates = this.buildMonthDates(prevY, prevM); this.selectedDate = date;
},
let nextY = this.currentYear, nextM = this.currentMonth + 1; // 处理日历模式变化
if (nextM === 13) { nextM = 1; nextY++; } handleModeChange(mode) {
this.nextMonthDates = this.buildMonthDates(nextY, nextM); this.mode = mode;
}, },
buildMonthDates(year, month) {
const firstDay = new Date(year, month - 1, 1).getDay();
const daysInMonth = new Date(year, month, 0).getDate();
let dates = [];
for (let i = 0; i < firstDay; i++) dates.push({ value: null });
for (let i = 1; i <= daysInMonth; i++) {
dates.push({ value: i, hasRecord: i <= 20, selected: i === this.selectedDate });
}
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)) { toggleCalendarMode() {
if (deltaY < -50 && this.calendarExpanded) this.calendarExpanded = false; // 调用日历组件的toggleMode方法
else if (deltaY > 50 && !this.calendarExpanded) this.calendarExpanded = true; if (this.$refs.calendarComponent) {
this.$refs.calendarComponent.toggleMode();
} }
},
onSwiperChange(e) {
const current = e.detail.current;
if (current === 0) { this.prevMonth(); this.swiperCurrent = 1; }
else if (current === 2) { this.nextMonth(); this.swiperCurrent = 1; }
} }
} }
}; };
@@ -219,65 +91,16 @@ export default {
<style scoped> <style scoped>
.my-record-container { .my-record-container {
display: flex;
flex-direction: column;
min-height: 100vh; min-height: 100vh;
background-color: #fff; background-color: #fff;
} }
/* 顶部导航栏 */
.header {
display: flex;
align-items: center;
padding: 20rpx 30rpx;
background-color: #fff;
position: relative;
}
.back-btn {
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
.page-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
/* 月份标题和导航 */
.month-header {
padding: 20rpx 30rpx;
margin-left: 180rpx;
margin-right: 180rpx;
background-color: #fff;
}
.month-nav {
display: flex;
align-items: center;
justify-content: space-between;
}
.month-arrow {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.arrow-icon {
width: 32rpx;
height: 32rpx;
}
.month-title { .month-title {
font-size: 32rpx; font-size: 28rpx;
font-weight: bold; font-weight: 600;
margin-left: 35rpx;
color: #333; color: #333;
} }
@@ -286,7 +109,7 @@ export default {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
padding: 40rpx 30rpx; padding: 40rpx 30rpx;
margin: 20rpx 30rpx; margin: 28rpx 30rpx 15rpx 30rpx;
background-color: #f7f7f7; background-color: #f7f7f7;
border-radius: 10rpx; border-radius: 10rpx;
@@ -315,90 +138,28 @@ export default {
} }
/* 日历样式 */ /* 日历样式 */
.calendar { overflow: hidden; margin-left: 20rpx; margin-right: 20rpx} .calendar { overflow: hidden; margin-left: 10rpx; margin-right: 10rpx}
.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; }
.calendar-toggle { text-align: center; background-color: #fff; margin-top: -30rpx;} .calendar-toggle { text-align: center; background-color: #fff;}
.image_sq { width: 37rpx; height: 6rpx; } .image_sq { width: 37rpx; height: 6rpx; }
.image_zk { width: 52rpx; height: 19rpx; } .image_zk { width: 52rpx; height: 19rpx; }
/* 固定班次 */ /* 固定班次 */
.fixed-shifts { .fixed-shifts {
margin: 20rpx 30rpx; flex: 1;
background-color: #f5f5f5; background-color: #f5f5f5;
border-radius: 16rpx;
padding: 30rpx; padding: 30rpx;
overflow: auto;
} }
.shifts-title { .shifts-title {
font-size: 32rpx; font-size: 28rpx;
font-weight: bold; color: #737373;
color: #333;
margin-bottom: 30rpx; margin-bottom: 30rpx;
} margin-top: 7rpx;
.shift-item {
margin-bottom: 40rpx;
}
.shift-item:last-child {
margin-bottom: 0;
}
.shift-time {
display: flex; display: flex;
align-items: center; justify-content: center;
margin-bottom: 20rpx;
} }
.time-dot {
width: 12rpx;
height: 12rpx;
background-color: #ff6b35;
border-radius: 50%;
margin-right: 16rpx;
}
.time-text {
font-size: 28rpx;
color: #333;
}
.record {
border: 2rpx dashed #e0e0e0;
border-radius: 12rpx;
padding: 24rpx;
margin-left: 28rpx;
}
.leave-record {
background-color: #fff7f0;
border-color: #ffb366;
}
.clock-record {
background-color: #f0f9ff;
border-color: #66b3ff;
}
.record-text {
font-size: 28rpx;
color: #333;
margin-bottom: 8rpx;
}
.location {
font-size: 24rpx;
color: #007aff;
display: block;
}
</style> </style>

View File

@@ -34,7 +34,7 @@
<image class="ins-line-image" src="/static/ic_my_repair_03.png" /> <image class="ins-line-image" src="/static/ic_my_repair_03.png" />
<view class="ins-info">巡检人{{ item.actUserName || '' }}</view> <view class="ins-info">巡检人{{ item.actUserName || '' }}</view>
<view class="ins-info">计划完成时间{{ item.planInsTime || '' }}</view> <view class="ins-info">计划完成时间{{ item.planInsTime || '' }}</view>
<view class="ins-info">实际完成时间{{ item.location || '' }}</view> <view class="ins-info">实际完成时间{{ item.compleTime || '' }}</view>
<view class="ins-info">巡检进度{{ item.inspectionProgress || '' }}</view> <view class="ins-info">巡检进度{{ item.inspectionProgress || '' }}</view>
</view> </view>

View File

@@ -56,13 +56,13 @@
</view> </view>
<!-- 操作区宽度跟随标题块内部居中 --> <!-- 操作区宽度跟随标题块内部居中 -->
<view class="ops" v-if="item.inspectionState ==0"> <view class="ops" v-if="item.inspectionState == 0">
<view class="btn-outline" @click="startTask(item)">立即巡检</view> <view class="btn-outline" @click="startTask(item)">立即巡检</view>
</view> </view>
<view class="status" v-else v-if="item.inspectionResults == 1" @click="goDetail(item)"> <view class="status" v-else-if="item.inspectionResults == 1" @click="goDetail(item)">
<view>完成巡检</view> <view>完成巡检</view>
<view class="badge" :class="item.inspectionResults ==1 ? 'badge-success' : 'badge-warn'"> <view class="badge" :class="item.inspectionResults == 1 ? 'badge-success' : 'badge-warn'">
{{ item.inspectionResults == 1 ? '正常' : '异常' }} {{ item.inspectionResults == 1 ? '正常' : '异常' }}
</view> </view>
</view> </view>

View File

@@ -31,12 +31,12 @@
<!-- 开始时间 --> <!-- 开始时间 -->
<view class="form-row"> <view class="form-row">
<text class="label required">开始时间</text> <text class="label required">开始时间</text>
<picker mode="date" @change="startDateChange"> <!-- <picker mode="date" @change="startDateChange"> -->
<view class="picker"> <view class="picker" @click="startDateSelect">
<text :class="!startDate ? 'placeholder' : ''">{{ startDate || '请选择' }}</text> <text :class="!startDate ? 'placeholder' : ''">{{ startDate || '请选择' }}</text>
<image class="right-arrow" src="/static/ic_right_arrow_g.png" /> <image class="right-arrow" src="/static/ic_right_arrow_g.png" />
</view> </view>
</picker> <!-- </picker> -->
</view> </view>
<!-- 结束时间 --> <!-- 结束时间 -->
@@ -63,8 +63,8 @@
</view> </view>
<!-- 上传图片 --> <!-- 上传图片 -->
<u-upload class="upload-style" :fileList="selectedImages" @on-list-change="onListChange" @delete="deletePic" name="upload" <u-upload class="upload-style" :fileList="selectedImages" @on-list-change="onListChange" @delete="deletePic"
multiple maxCount="3" width="180" height="180" :autoUpload="false"></u-upload> name="upload" multiple maxCount="3" width="180" height="180" :autoUpload="false"></u-upload>
<!-- 提交按钮 --> <!-- 提交按钮 -->
<button class="submit-btn" @click="submitLeave">提交</button> <button class="submit-btn" @click="submitLeave">提交</button>
@@ -113,13 +113,8 @@
<view class="popup-content"> <view class="popup-content">
<view class="popup-title">请假类型</view> <view class="popup-title">请假类型</view>
<view class="popup-options"> <view class="popup-options">
<view <view v-for="(type, index) in leaveTypes" :key="index" class="popup-option"
v-for="(type, index) in leaveTypes" :class="{ selected: leaveTypeIndex === index }" @click="selectLeaveType(index)">
:key="index"
class="popup-option"
:class="{ 'selected': leaveTypeIndex === index }"
@click="selectLeaveType(index)"
>
{{ type }} {{ type }}
</view> </view>
</view> </view>
@@ -127,11 +122,14 @@
</view> </view>
</u-popup> </u-popup>
<SelectCalendarDialog :visible.sync="showCalendarDialog" @confirm="onConfirm" />
</view> </view>
</template> </template>
<script> <script>
export default { import SelectCalendarDialog from '@/components/SelectCalendarDialog.vue'
export default {
components: { SelectCalendarDialog },
data() { data() {
return { return {
tabs: ['发起申请', '请假记录', '已完成'], tabs: ['发起申请', '请假记录', '已完成'],
@@ -150,6 +148,7 @@ export default {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
showLeaveTypePopup: false, showLeaveTypePopup: false,
showCalendarDialog: false,
} }
}, },
methods: { methods: {
@@ -159,8 +158,11 @@ export default {
bindPickerChange(e) { bindPickerChange(e) {
this.leaveTypeIndex = e.detail.value; this.leaveTypeIndex = e.detail.value;
}, },
startDateChange(e) { startDateSelect() {
this.startDate = e.detail.value; this.showCalendarDialog = true
},
onConfirm(selectedDate){
this.startDate = selectedDate
}, },
endDateChange(e) { endDateChange(e) {
this.endDate = e.detail.value; this.endDate = e.detail.value;
@@ -190,8 +192,7 @@ export default {
}, },
async loadLeaveRecords() { async loadLeaveRecords() {
this.loading = true; this.loading = true;
let data = [ let data = [{
{
type: '请假', type: '请假',
leaveType: '病假', leaveType: '病假',
status: '已通过', status: '已通过',
@@ -247,7 +248,7 @@ export default {
} }
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
@@ -513,7 +514,8 @@ export default {
font-size: 28rpx; font-size: 28rpx;
color: #999; color: #999;
} }
.upload-style{
.upload-style {
margin-left: 15rpx; margin-left: 15rpx;
margin-right: 15rpx; margin-right: 15rpx;
} }
@@ -548,22 +550,21 @@ export default {
} }
.popup-options { .popup-options {
display: flex; display: grid;
flex-wrap: wrap; grid-template-columns: repeat(3, 1fr);
justify-content: space-between; /* 三列可改成2/4列 */
gap: 20rpx;
/* 行列间距 */
padding: 20rpx;
} }
.popup-option { .popup-option {
width: 30%; text-align: center;
height: 80rpx; padding: 20rpx 0;
background-color: #f7f7f7;
border-radius: 12rpx; border-radius: 12rpx;
display: flex; background-color: #f5f5f5;
align-items: center; font-size: 28rpx;
justify-content: center;
font-size: 32rpx;
color: #333; color: #333;
margin-bottom: 20rpx;
} }
.popup-option.selected { .popup-option.selected {

View File

@@ -74,11 +74,11 @@
text: '报事报修', text: '报事报修',
url:'/pages/sys/user/myRepair/myRepair' url:'/pages/sys/user/myRepair/myRepair'
}, },
// { {
// icon: 'https://picsum.photos/80/80?random=3', icon: 'https://picsum.photos/80/80?random=3',
// text: '工作巡检', text: '工作巡检',
// url:'/pages/sys/workbench/inspection/inspection' url:'/pages/sys/workbench/inspection/inspection'
// }, },
// { // {
// icon: 'https://picsum.photos/80/80?random=3', // icon: 'https://picsum.photos/80/80?random=3',
// text: '通讯录', // text: '通讯录',

View File

@@ -16,7 +16,7 @@ try{
} }
// 需要永久存储且下次APP启动需要取出的在state中的变量名 // 需要永久存储且下次APP启动需要取出的在state中的变量名
let saveStateKeys = ['vuex_user', 'vuex_token', 'vuex_remember', 'vuex_locale','vuex_isAgent']; let saveStateKeys = ['vuex_user', 'vuex_token', 'vuex_remember', 'vuex_locale','vuex_isAgent','vuex_push_clientId'];
// 保存变量到本地存储中 // 保存变量到本地存储中
const saveLifeData = function(key, value){ const saveLifeData = function(key, value){
@@ -41,6 +41,7 @@ const store = new Vuex.Store({
vuex_remember: lifeData.vuex_remember ? lifeData.vuex_remember : '', vuex_remember: lifeData.vuex_remember ? lifeData.vuex_remember : '',
vuex_locale: lifeData.vuex_locale ? lifeData.vuex_locale : '', vuex_locale: lifeData.vuex_locale ? lifeData.vuex_locale : '',
vuex_isAgent: lifeData.vuex_isAgent ? lifeData.vuex_isAgent : '', vuex_isAgent: lifeData.vuex_isAgent ? lifeData.vuex_isAgent : '',
vuex_push_clientId: lifeData.vuex_push_clientId ? lifeData.vuex_push_clientId : '',
// 如果vuex_version无需保存到本地永久存储无需lifeData.vuex_version方式 // 如果vuex_version无需保存到本地永久存储无需lifeData.vuex_version方式
vuex_config: config, vuex_config: config,