Преглед на файлове

feat(mine/checkIn):新增我的考勤页面
refactor(mine/personal_message):优化个人信息页面

HMY преди 1 година
родител
ревизия
4f498f67f9

+ 1 - 1
manifest.json

@@ -50,7 +50,7 @@
     /* 快应用特有相关 */
     "mp-weixin" : {
         /* 小程序特有相关 */
-        "appid" : "",
+        "appid" : "wxdd50400f2edf156f",
         "setting" : {
             "urlCheck" : false
         },

+ 16 - 0
pages.json

@@ -1,5 +1,6 @@
 {
 	"pages": [
+		
 		{
 			"path": "pages/message/index",
 			"style": {
@@ -90,6 +91,20 @@
 				"navigationBarTitleText": "待办事项",
 				"enablePullDownRefresh": false
 			}
+		},
+		{
+			"path" : "pages/mine/checkIn/checkIn",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
+		},
+		{
+			"path" : "pages/mine/test/test",
+			"style" : 
+			{
+				"navigationBarTitleText" : ""
+			}
 		}
 	],
 	"tabBar": {
@@ -137,4 +152,5 @@
 		"backgroundColor": "#F8F8F8"
 	},
 	"uniIdRouter": {}
+	
 }

+ 406 - 0
pages/mine/checkIn/checkIn.vue

@@ -0,0 +1,406 @@
+<template>
+	<view class="attendance-page">
+		<view class="header">
+			<text class="title">我的考勤记录</text>
+			<picker mode="selector" :range="timeRanges" @change="onTimeRangeChange">
+				<view class="picker">
+					{{ selectedTimeRange }}
+				</view>
+			</picker>
+		</view>
+
+		<view class="statistics" :class="{'show': chartShow}">
+			<view class="statistic-card">
+				<view class="statistic-title">考勤统计</view>
+				<view class="statistic-item">
+					<text class="label">出勤:</text>
+					<text class="value">{{ attendanceCount }}</text>
+				</view>
+				<view class="statistic-item">
+					<text class="label">请假:</text>
+					<text class="value">{{ leaveCount }}</text>
+				</view>
+				<view class="statistic-item">
+					<text class="label">出差:</text>
+					<text class="value">{{ businessTripCount }}</text>
+				</view>
+				<view class="statistic-item">
+					<text class="label">打卡:</text>
+					<text class="value">{{ clockInCount }}</text>
+				</view>
+				<view class="statistic-item">
+					<text class="label">补卡:</text>
+					<text class="value">{{ makeUpCount }}</text>
+				</view>
+			</view>
+		</view>
+
+		<view class="todayCheckIn" :class="{'show': shouldShow}">
+			<view class="check-in-container">
+				<text class="title2">今日签到信息</text>
+				<view class="info-row">
+					<text class="label">日期:</text>
+					<text class="value">{{todayData.day}}</text>
+				</view>
+				<view class="info-row">
+					<text class="label">晨签时间:</text>
+					<text class="value">{{todayData.startTime || '未打卡'}}  </text>
+				</view>
+				<view class="info-row">
+					<text class="label">晚签时间:</text>
+					<text class="value">{{todayData.endTime || '未打卡'}} </text>
+				</view>
+				<view class="info-row">
+					<text class="label">状态:</text>
+					<text class="value">{{todayData.status}}</text>
+				</view>
+			</view>
+		</view>
+		<view class="chart-container" :class="{'show': chartShow}">
+			<view class="">
+				<text class="chart-title">考勤趋势图</text>
+				<view class="charts-box">
+					<qiun-data-charts :type="chartsType" :opts="opts" :chartData="chartData" />
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		onMounted,
+		reactive
+	} from 'vue';
+
+	const todayData = reactive({
+		day: '2024-11-11',
+		startTime: '08:59:59',
+		endTime: '',
+		status: '待补卡'
+	})
+	const shouldShow = ref(true)
+	const chartShow = ref(false)
+	//图表类型
+	const chartsType = ref('pie')
+	const chartData = ref({})
+	const opts = ref({
+		color: ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"],
+		padding: [15, 15, 0, 5],
+		enableScroll: false,
+		legend: {},
+		xAxis: {
+			disableGrid: true
+		},
+		yAxis: {
+			data: [{
+				min: 0
+			}]
+		},
+		extra: {
+			column: {
+				type: "group",
+				width: 30,
+				activeBgColor: "#000000",
+				activeBgOpacity: 0.08
+			}
+		}
+	});
+	const timeRanges = ref(['日', '周', '月']);
+	const selectedTimeRange = ref('日');
+
+	//出勤...补卡统计
+	const attendanceCount = ref(0);
+	const leaveCount = ref(0);
+	const businessTripCount = ref(0);
+	const clockInCount = ref(0);
+	const makeUpCount = ref(0);
+
+	const currentData = ref({
+		attendance: 0,
+		leave: 0,
+		businessTrip: 0,
+		clockIn: 0,
+		makeUp: 0,
+		res: {},
+	});
+
+
+
+	//日周月切换
+	const onTimeRangeChange = (event) => {
+
+		const selectedIndex = event.detail.value;
+		console.log('selectedIndex', selectedIndex)
+		selectedTimeRange.value = timeRanges.value[selectedIndex];
+		fetchAttendanceData();
+		switch (selectedTimeRange.value) {
+			case '日':
+				shouldShow.value = true;
+				chartShow.value = false;
+				break;
+			case '周':
+				shouldShow.value = false;
+				chartShow.value = true;
+				console.log(shouldShow.value)
+				getData1();
+				break;
+			case '月':
+				shouldShow.value = false;
+				chartShow.value = true;
+				getData2();
+				break;
+
+		}
+
+	};
+
+	const fetchAttendanceData = () => {
+		const mockData = {
+			日: {
+				attendance: 5,
+				leave: 0,
+				businessTrip: 1,
+				clockIn: 5,
+				makeUp: 0,
+				res: {}
+			},
+			周: {
+				attendance: 30,
+				leave: 2,
+				businessTrip: 1,
+				clockIn: 32,
+				makeUp: 1,
+				res: {
+					series: [{
+						data: [{
+							"name": "出勤",
+							"value": 50,
+							"labelText": "出勤:50次"
+						}, {
+							"name": "请假",
+							"value": 30
+						}, {
+							"name": "出差",
+							"value": 20
+						}, {
+							"name": "打卡",
+							"value": 18,
+							"labelText": "打卡:18次"
+						}, {
+							"name": "补卡",
+							"value": 8
+						}]
+					}],
+				}
+			},
+			月: {
+				attendance: 100,
+				leave: 5,
+				businessTrip: 2,
+				clockIn: 105,
+				makeUp: 3,
+				res: {
+					categories: ["1周", "2周", "3周", "4周"],
+					series: [{
+							name: "出勤",
+							data: [35, 8, 25, 37]
+						},
+						{
+							name: "出差",
+							data: [70, 40, 65, 100]
+						},
+						{
+							name: "请假",
+							data: [100, 80, 95, 150]
+						}
+					]
+				},
+			}
+		};
+
+		const currentData1 = mockData[selectedTimeRange.value];
+		currentData.value=currentData1;
+		
+		if (currentData1) {
+			attendanceCount.value = currentData1.attendance;
+			leaveCount.value = currentData1.leave;
+			businessTripCount.value = currentData1.businessTrip;
+			clockInCount.value = currentData1.clockIn;
+			makeUpCount.value = currentData1.makeUp;
+
+		} else {
+			console.error('没有找到相关的考勤数据:', selectedTimeRange.value);
+		}
+	};
+
+
+
+	onMounted(() => {
+		//初始化数据
+		fetchAttendanceData();
+	});
+
+	function getData1() {
+		chartsType.value = 'pie';
+		opts.value.yAxis = {
+			data: [{
+				min: 0
+			}]
+		};
+		opts.value.extra = {
+			column: {
+				type: "group",
+				width: 30,
+				activeBgColor: "#000000",
+				activeBgOpacity: 0.08
+			}
+		};
+		// console.log(currentData.value.res)
+		let res =currentData.value.res;
+		chartData.value = JSON.parse(JSON.stringify(res));
+	}
+
+	function getData2() {
+		chartsType.value = 'column';
+		let res =currentData.value.res;
+		chartData.value = JSON.parse(JSON.stringify(res));
+	}
+</script>
+
+<style scoped>
+	.attendance-page {
+		padding: 20px;
+		background-color: #f9f9f9;
+	}
+
+	.header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		margin-bottom: 20px;
+	}
+
+	.title {
+		font-size: 24px;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.picker {
+		border: 1px solid #3498db;
+		border-radius: 5px;
+		padding: 10px;
+		background-color: #ecf6fc;
+		color: #3498db;
+	}
+
+	.statistics {
+		display: flex;
+		justify-content: center;
+	}
+
+	.statistic-card {
+		background: white;
+		border-radius: 10px;
+		padding: 20px;
+		box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+		/* width: 100%; */
+		max-width: 600px;
+	}
+
+	.statistic-title {
+		text-align: center;
+		font-size: 20px;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.statistic-item {
+		display: flex;
+		justify-content: space-between;
+		margin-bottom: 10px;
+		border-bottom: 1px solid #eee;
+		padding-bottom: 10px;
+	}
+
+	.label {
+		font-size: 18px;
+		color: #666;
+	}
+
+	.value {
+		font-size: 16px;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.chart-container {
+		margin-top: 20px;
+	}
+
+	.chart-title {
+		font-size: 20px;
+		font-weight: bold;
+		text-align: center;
+		margin-bottom: 10px;
+		color: #333;
+	}
+
+	.charts-box {
+		width: 100%;
+		height: 300px;
+	}
+
+
+	.todayCheckIn {
+
+		background-color: #ffffff;
+		padding: 20px;
+		box-sizing: border-box;
+	}
+
+
+
+	.check-in-container {
+		width: 100%;
+		height: 100%;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.title2 {
+		font-size: 22px;
+		font-weight: bold;
+		margin-bottom: 20px;
+	}
+
+
+	.info-row {
+		width: 100%;
+		display: flex;
+		justify-content: space-between;
+		margin-bottom: 10px;
+	}
+
+
+
+
+
+	.todayCheckIn,
+	.chart-container,
+	.statistics {
+		/* 默认隐藏 */
+		display: none;
+	}
+
+	/* 当需要显示时,可以添加一个额外的类 */
+	.todayCheckIn.show,
+	.chart-container.show,
+	.statistics.show {
+		display: block;
+	}
+</style>

+ 10 - 5
pages/mine/index.vue

@@ -19,7 +19,7 @@
 				</view>
 
 			</view>
-			<uni-icons @click="lookMsg()" type="search" size="50" style="margin-right: 30rpx;"></uni-icons>
+			<uni-icons @click="lookMsg()" type="forward" size="50" style="margin-right: 30rpx;"></uni-icons>
 
 		</view>
 
@@ -94,7 +94,10 @@
 		$tab.navigateTo('/pages/mine/edit/edit')
 	};
 	function handleButtonClick2(){
-		$tab.navigateTo('/pages/mine/waitWork/waitWork')
+		$tab.navigateTo('/pages/mine/test/test')
+	}
+	function handleButtonClick3(){
+		$tab.navigateTo('/pages/mine/checkIn/checkIn')
 	}
 
 	function handleButtonClick4() {
@@ -103,13 +106,15 @@
 </script>
 
 
-<style lang="scss" scoped>
+<style lang="scss">
 	.content {
 		display: flex;
 		flex-direction: column;
 	}
-
-
+//样式穿透
+	::v-deep .uni-select__selector-item text,.uni-select__input-text {
+		font-size: larger;
+	}
 
 	.user-info {
 		display: flex;

+ 3 - 3
pages/mine/personal_message/personal_message.vue

@@ -6,7 +6,7 @@
 		<!-- 用户名及职位 -->
 		<text class="username">{{ userInfo.name }}</text>
 		<view class="position" v-for="(item,index) in userInfo.department">
-			{{ item.positionName }}--{{item.position}}
+			{{ item.departmentName }}--{{item.position}}
 		</view>
 		
 		
@@ -55,12 +55,12 @@
 					name: "张三",
 					department: [{
 							value: 0,
-							positionName: "研发部",
+							departmentName: "研发部",
 							position: '软件开发工程师'
 						},
 						{
 							value: 1,
-							positionName: "业务部",
+							departmentName: "业务部",
 							position: '销售经理'
 						},
 					],

+ 141 - 0
pages/mine/test/test.vue

@@ -0,0 +1,141 @@
+<template>
+	<view class="container">
+		<view class="header">
+			<text class="day">{{ currentDay }}</text>
+			<text class="date">{{ currentDate }}</text>
+			<text class="status">未打卡</text>
+		</view>
+		<view class="record">
+			<view class="record-item" v-for="(item, index) in recordList" :key="index">
+				<image :src="item.icon" class="icon"></image>
+				<text class="title">{{ item.title }}</text>
+				<text class="time">{{ item.time }}</text>
+			</view>
+		</view>
+		<map id="myMap" class="map" :longitude="longitude" :latitude="latitude" show-location></map>
+		<view class="footer">
+			<button type="primary" @click="signIn">上班签到</button>
+			<button type="default" @click="signOut">下班签退</button>
+		</view>
+	</view>
+</template>
+
+
+<script setup>
+	import {
+		ref
+	} from 'vue';
+
+	const currentDate = ref('2023-04-01');
+	const currentDay = ref('星期三');
+	const recordList = ref([{
+			icon: '/static/icons/morning.svg',
+			title: '上班',
+			time: '09:00'
+		},
+		{
+			icon: '/static/icons/afternoon.svg',
+			title: '下班',
+			time: '18:00'
+		}
+	]);
+	const longitude = ref(116.407497);
+	const latitude = ref(39.90471);
+
+	const signIn = () => {
+		try {
+		    const res=uni.getLocation({ type: 'wgs84' });
+		    const lat = res.latitude;  // 获取纬度
+		    const lng = res.longitude;  // 获取经度
+		    console.log("当前位置的经纬度:", lat, lng);
+		  } catch (e) {
+		    console.error("获取位置信息失败:", e);
+		  }
+	};
+
+	const signOut = () => {
+		console.log('下班签退');
+	};
+</script>
+
+<style>
+	.container {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		padding: 20rpx;
+	}
+
+	.header {
+		width: 100%;
+		height: 200rpx;
+		background-color: #f8f8f8;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.day {
+		font-size: 36rpx;
+		font-weight: bold;
+	}
+
+	.date {
+		font-size: 28rpx;
+		color: #666;
+	}
+
+	.status {
+		font-size: 24rpx;
+		color: red;
+	}
+
+	.record {
+		width: 100%;
+		margin-top: 20rpx;
+		border-top: 1px solid #ddd;
+		border-bottom: 1px solid #ddd;
+		padding: 0 15rpx;
+	}
+
+	.record-item {
+		display: flex;
+		align-items: center;
+		height: 100rpx;
+		border-bottom: 1px solid #eee;
+	}
+
+	.icon {
+		width: 50rpx;
+		height: 50rpx;
+		margin-right: 10rpx;
+	}
+
+	.title {
+		font-size: 28rpx;
+	}
+
+	.time {
+		font-size: 24rpx;
+		color: #999;
+		margin-left: auto;
+	}
+
+	.map {
+		width: 100%;
+		height: 500rpx;
+		margin-top: 20rpx;
+	}
+
+	.footer {
+		width: 100%;
+		margin-top: 20rpx;
+		display: flex;
+		justify-content: space-around;
+	}
+
+	button {
+		width: 45%;
+	}
+</style>

+ 231 - 0
pages/mine/waitWork/waitWork.vue

@@ -0,0 +1,231 @@
+<template>
+	<view class="container">
+		<view class="stats-container">
+			<view class="stat-item" @click="goToTodos">
+
+
+				<image :src="waitWorkImg" style="width: 120rpx;height: 120rpx;"></image>
+				<text class="stat-label">
+					待办任务
+					<text class="stat-value">{{ todoCount }}</text>
+					</text>
+			</view>
+			<view class="divider"></view>
+			<view class="stat-item" @click="goToProcesses">
+				<image :src="processImg" style="width: 60px;height: 60px;"></image>
+
+				<text class="stat-label">
+					发布流程
+					<text class="stat-value">{{ processCount }}</text>
+				</text>
+			</view>
+		</view>
+		<view class="tasks-section">
+			<view class="section-header">
+				<text class="section-title">任务列表</text>
+			</view>
+			<scroll-view class="task-list" scroll-y>
+				<view class="task-item" v-for="(todo, index) in todos" :key="index" @click="showTaskDetail(todo)">
+					<view>
+						<image :src="waitWorkImg" style="width: 30rpx;height: 30rpx;margin-right: 30rpx;"></image>
+						<text class="task-content">{{ todo.content }}</text>
+					</view>
+					
+					<text class="task-detail-icon">›</text>
+				</view>
+			</scroll-view>
+		</view>
+		<view class="processes-section">
+			<view class="section-header">
+				<text class="section-title">流程列表</text>
+			</view>
+			<scroll-view class="process-list" scroll-y>
+				<view class="process-item" v-for="(process, index) in processes" :key="index"
+					@click="showProcessDetail(process)">
+					<view>
+						<image :src="processImg" style="width: 30rpx;height: 30rpx;margin-right: 30rpx;"></image>
+						<text class="process-content">{{ process.content }}</text>
+					</view>
+					
+					<text class="process-detail-icon">›</text>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script setup>
+	import {
+		ref,
+		computed,
+		reactive,
+	} from 'vue';
+
+import waitWorkImg from "/static/images/mine/waitWork2.png";
+import processImg from "/static/images/mine/process.png";
+
+	const todos = reactive([{
+			id: 1,
+			content: '完成报告1',
+			detail: '完成11月5日的今日报告'
+		},
+		{
+			id: 2,
+			content: '参加会议2',
+			detail: '参加11月5日下午5点的会议'
+		},
+		{
+			id: 3,
+			content: '回复邮件3',
+			detail: '详细描述...'
+		},
+		{
+			id: 4,
+			content: '完成报告4',
+			detail: '详细描述...'
+		},
+		{
+			id: 5,
+			content: '参加会议5',
+			detail: '详细描述...'
+		},
+		{
+			id: 6,
+			content: '回复邮件6',
+			detail: '详细描述...'
+		},
+	]);
+
+	const processes = ref([{
+			id: 1,
+			content: '审批流程',
+			detail: '来自张三的请假申请'
+		},
+		{
+			id: 2,
+			content: '财务流程',
+			detail: '详细描述...'
+		},
+	]);
+
+	const todoCount = computed(() => todos.length);
+	const processCount = computed(() => processes.value.length);
+
+	function goToTodos() {
+		// 跳转到待办任务列表页面
+	}
+
+	function goToProcesses() {
+		// 跳转到流程列表页面
+	}
+
+	function showTaskDetail(todo) {
+		// 显示待办任务详情
+		uni.showModal({
+			title: '任务详情',
+			content: todo.detail,
+			cancelText:'完成',
+			success: function (res) {
+					if (res.confirm) {
+						console.log('用户点击确定');
+					} else if (res.cancel) {
+						console.log('用户点击完成',todo);
+						// 找到要移除的对象的索引
+						let index = todos.findIndex(item => item.id === todo.id);
+						// 如果找到了,使用splice移除它
+						if (index > -1) {
+						  todos.splice(index, 1);
+						}
+						
+					}
+				}
+			
+		});
+	}
+
+	function showProcessDetail(process) {
+		// 显示流程详情
+		uni.showModal({
+			title: '流程详情',
+			content: process.detail,
+			showCancel: false
+		});
+	}
+</script>
+
+<style scoped>
+	.container {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		background-color: #f5f5f5;
+		height: 100%;
+	}
+
+
+	.stats-container {
+		display: flex;
+		justify-content: space-around;
+		width: 100%;
+		margin-top: 20px;
+		background-color: white;
+		border-radius: 10px;
+		padding: 10px 0;
+	}
+
+	.stat-item {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		width: 50%;
+	}
+
+	.stat-value {
+		font-size: 24px;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.stat-label {
+		font-size: 16px;
+		color: #666;
+	}
+
+	.divider {
+		width: 1px;
+		height: 40px;
+		background-color: #e5e5e5;
+	}
+
+	.tasks-section,
+	.processes-section {
+		width: 100%;
+		margin-top: 20px;
+	}
+
+	.section-header {
+		padding: 10px;
+		background-color: #f8f8f8;
+		border-bottom: 1px solid #e5e5e5;
+	}
+
+	.section-title {
+		font-size: 20px;
+		font-weight: bold;
+	}
+
+	.task-list,
+	.process-list {
+		height: 200px;
+		background-color: white;
+	}
+
+	.task-item,
+	.process-item {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 15px;
+
+	}
+</style>

BIN
static/images/mine/process.png


BIN
static/images/mine/waitWork2.png