红色打卡点,拍照打卡小程序,通过小程序完成活动任务的方法 v-slot:default=“{data, loading, error, options}

本文介绍了如何通过微信小程序实现电子签到打卡,简化活动管理并提升参与者的体验。活动发起者可以创建点位,设置签到规则,参与者只需扫描二维码即可签到,同时支持发表打卡心情、查看运动步数和排行榜。周期性打卡功能如读书节,可记录用户的连续打卡天数。电子签到适用于各类线下活动和连锁店打卡活动,通过趣味性和竞争性提高活动参与度。

<unicloud-db v-slot:default="{data, loading, error, options}" collection="table1" field="field1" :getone="true" where="id=='1'">
  <view>
    {{ data}}
  </view>
</unicloud-db>

在现实生活中,签到打卡我们可以使用手写签到,每个人在事先打印好的电子表格里,写下自己的姓名进行签到;或者可以在签到墙上进行签到,拍照打卡。

除此之外,我们还可以进行电子签到打卡,电子签到的好处是简化信息登记的手续,便于活动现场的控场、信息传递以及活动后的客户跟进、调查反馈。AR签到、刷脸签到等则是在信息收集的基础上,尽可能把活动做得更有趣,满足大众的猎奇心理,同时达到活动传播的目的。

微信是大家比较熟悉的社交软件,而依托于微信这个十亿多流量存在的小程序,则越来越多种多样,那么如何制作一个活动打卡小程序呢?线上签到打卡的玩法有哪些? 

   下面我们介绍的这个,就是通过微信小程序的方式来实现的一个电子化的打卡方式,活动发起者,可以创建活动,设置好活动信息,然后转发给参与者进行打卡活动。参与者只要微信扫一扫活动的二维码即可加入参加活动。整个系统的使用方法介绍如下:

   首先,活动发起者,可以通过这个微信小程序,创建一场活动,然后在这场活动里设置一个或者多个点位供大家打卡签到。

   每个点位可以设置点位的介绍,设置这个地点的经纬度,以及需要在这个地点周围多少米范围内才能打卡签到。也可以设置签到的时间,比如是每天的8:00—18:00签到。

   用户进入到活动以后,只要走到了活动设置的点位周围指定的范围内,就可以实现打卡签到。在打卡签到的过程中,为了实现趣味性,还可以发表打卡心情,发文字配上图片。并可以获取自己的运动步数。根据运动步数进行排名等。

   同时还可查看本次打卡活动排行榜数据,排行榜是最能让成员了解自我的功能之一。通过排行榜,成员可以查看到自己与他人的数据排名状况,在了解目前打卡数据排名的同时,还可以激发成员之间良性竞争,使圈子中的打卡活动更加活跃有趣。
如下图是活动打卡签到小程序界面 : 

根据大家打卡签到完成的时间和完成的数量进行排名。完成数量越多排在越前,打卡签到的数量一样的,就看完成的时间,谁最先完成,就谁排在最前面。

上面介绍的是单次打卡活动的玩法,同时还可以创建周期打卡。比如读书节,可以在每个月组织一次这样的活动。每天打卡签到自己读书的心得,每天分享到活动里,然后月底,系统自动统计这一个月里,所有人打卡分享的天数。谁累计坚持打卡的天数最多,进行所有的排名。从高到低依次进行排名。、

整个系统支持用户自行创建活动,生成自己活动的二维码以及活动展示页面。创建者可以分享这个二维码到朋友圈,或者转发这个活动页面到微信群,其他人扫码或者点击即可进入到这个活动,参与并完成这个活动的打卡。

整个活动可以设置一个或者多个地点,所以如果是一个地点,那么就可以适合线下会议这种模式的打卡签到。如果是设置的多个点,就适合一些线下活动,需要集齐多个活动地点,打卡完毕获得活动的名次。

所有签到打卡的用户名单都可以导出成Excel表格。方便活动发起者管理。

 应用场景:

活动打卡适用于一些大型连锁店以及公司周年庆等。消费者只要到每个指定的门店,进行打卡签到,集齐打卡徽章,达到对应的数量,就可以领取奖品,兑换奖品。

还有一些线上打卡活动,比如通过每日至少一次发表绿色生活实践照片或视频的方式打卡,宣传绿色生活,增加参与感。活动有效时长连续七天,打卡内容展示“帮助环卫工人”“做环保公益活动”“送废纸废塑料去回收”等,旨在号召人们积极参与绿色生活实践。每日坚持打卡会有香囊、水杯、充电宝等奖品,以此鼓励环保打卡行为。

很多单位都喜欢使用这种的打卡活动来举办各种不同形式的线下互动活动。比如下面的:

厦门市总工会于5月1日至15日期间举办厦门市职工“绿色行”打卡活动,持有工会会员卡的职工任意前往5个首批厦门市职工“绿色行”打卡点拍摄照片即可参与活动,前200位经审核合格的参与对象将获得由厦门市总工会提供的礼品。

本次活动要求参与者上传5张在不同打卡点拍摄的照片,照片内容须包含打卡点明显标志且有参与者本人出现,每张照片大小在1M以内,像素清晰。照片上传数量不足、参与者与工会系统用户不一致、盗图都将被取消参与活动资格。经审核合格的参与对象将接到短信通知,5月20日后可登录厦门市总工会官网公告栏查看获奖人员名单。据介绍,活动通过打卡“绿色行”打卡点,让职工切身体会厦门的山海风貌和经济特区建设丰硕成果,以调动职工工作生活积极性,增强工会组织凝聚力和向心力。

<template> <div class="app-container"> <el-row :gutter="20"> <!--部门数据--> <el-col :span="4" :xs="24"> <div class="head-container"> <el-input v-model="deptName" placeholder="请输入部门名称" clearable size="small" prefix-icon="el-icon-search" style="margin-bottom: 20px"/> </div> <div class="head-container"> <el-tree :data="deptOptions" :props="defaultProps" :expand-on-click-node="false" :filter-node-method="filterNode" ref="tree" default-expand-all highlight-current @node-click="handleNodeClick"/> </div> </el-col> <el-col :span="20" :xs="24"> <!-- 顶部搜索和筛选区域 --> <div class="search-container"> <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="80px"> <el-form-item label="用户姓名" prop="nickName"> <el-input v-model="queryParams.nickName" placeholder="请输入用户姓名" clearable style="width: 180px" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item label="打卡" prop="location"> <el-input v-model="queryParams.location" placeholder="请输入打卡" clearable style="width: 180px" @keyup.enter.native="handleQuery"/> </el-form-item> <el-form-item label="真实日期" prop="realDate"> <el-date-picker clearable v-model="queryParams.realDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择真实日期" style="width: 180px"></el-date-picker> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button> <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button> </el-form-item> </el-form> </div> <TableCard> <!-- 打卡记录类型切换 --> <div class="record-type-tabs"> <el-radio-group v-model="recordType" @change="handleRecordTypeChange"> <el-radio-button label="all">打卡记录</el-radio-button> <el-radio-button label="external">外勤打卡记录</el-radio-button> </el-radio-group> </div> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['oa:workAttendances:export']"> 导出 </el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-upload" size="mini" @click="handleImport" v-hasPermi="['oa:workAttendances:edit']"> 导入外部考勤数据 </el-button> </el-col> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> </el-row> <el-table :border="Global.tableShowBorder" v-loading="loading" :data="workAttendancesList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center"/> <el-table-column label="用户姓名" align="center" prop="nickName"/> <el-table-column label="打卡" align="center" prop="location"> <template slot-scope="scope"> <span v-if="!util.isEmpty(scope.row.location)">{{ scope.row.location }}</span> <span v-if="util.isEmpty(scope.row.location)" style="color: #e6a23c">未记录地</span> </template> </el-table-column> <el-table-column label="真实日期" align="center" prop="realDate"> <template slot-scope="scope"> <span v-if="!util.isEmpty(scope.row.realDate)">{{ parseTime(scope.row.realDate, '{y}-{m}-{d}') }}</span> <span v-if="util.isEmpty(scope.row.realDate)" style="color: #e6a23c">未有打卡记录</span> </template> </el-table-column> <el-table-column label="星期" align="center" prop="realDate" > <template slot-scope="scope"> <span v-if="!util.isEmpty(scope.row.realDate)">{{ util.getWeekDay(scope.row.realDate) }}</span> <span v-if="util.isEmpty(scope.row.realDate)" style="color: #e6a23c">未有打卡记录</span> </template> </el-table-column> <el-table-column label="年度周" align="center" prop="realDate" > <template slot-scope="scope"> <span v-if="!util.isEmpty(scope.row.realDate)">{{scope.row.realDate.split("-")[0]}}年第{{ util.getYearWeek(scope.row.realDate) }}周</span> <span v-if="util.isEmpty(scope.row.realDate)" style="color: #e6a23c">未有打卡记录</span> </template> </el-table-column> <el-table-column label="签到时间" align="center" prop="signInTime" > <template slot-scope="scope"> <span v-if="!util.isEmpty(scope.row.signInTime)">{{ scope.row.signInTime }}</span> <span v-if="util.isEmpty(scope.row.signInTime)" style="color: #f56c6c">未打卡</span> </template> </el-table-column> <el-table-column label="签到状态" align="center" prop="signInStatus" > <template slot-scope="scope"> <span v-if="scope.row.signInStatus=='0'" style="color: #67c23a">正常</span> <span v-if="scope.row.signInStatus=='1'" style="color: #f56c6c">迟到</span> <span v-if="util.isEmpty(scope.row.signInStatus)" style="color: #f56c6c">未打卡</span> </template> </el-table-column> <el-table-column label="签退时间" align="center" prop="signOutTime" > <template slot-scope="scope"> <span v-if="!util.isEmpty(scope.row.signOutTime)">{{ scope.row.signOutTime }}</span> <span v-if="util.isEmpty(scope.row.signOutTime)" style="color: #f56c6c">未打卡</span> </template> </el-table-column> <el-table-column label="签退状态" align="center" prop="signOutStatus" > <template slot-scope="scope"> <span v-if="scope.row.signOutStatus=='0'" style="color: #67c23a">正常</span> <span v-if="scope.row.signOutStatus=='1'" style="color: #f56c6c">早退</span> <span v-if="util.isEmpty(scope.row.signOutStatus)" style="color: #f56c6c">未打卡</span> </template> </el-table-column> <el-table-column label="备注" align="center" prop="nickName"/> <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right"> <template slot-scope="scope"> <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['oa:workAttendances:edit']"> 修改 </el-button> <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['oa:workAttendances:remove']"> 删除 </el-button> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList"/> </TableCard> </el-col> </el-row> <!-- 添加或修改打卡记录对话框 --> <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body> <el-form ref="form" :model="form" :rules="rules" label-width="120px"> <el-form-item label="用户姓名" prop="nickName"> <el-input v-model="form.nickName" :disabled="true"/> </el-form-item> <el-form-item label="打卡" prop="location"> <el-input v-model="form.location" placeholder="请输入打卡"/> </el-form-item> <el-form-item label="签到时间" prop="signInTime"> <el-time-picker clearable v-model="form.signInTime" :picker-options="{format:'HH:mm'}" value-format="HH:mm" placeholder="请选择签到时间"> </el-time-picker> </el-form-item> <el-form-item label="签到状态" prop="signInStatus"> <el-radio-group v-model="form.signInStatus"> <el-radio label="0">正常</el-radio> <el-radio label="1">迟到</el-radio> </el-radio-group> </el-form-item> <el-form-item label="签退时间" prop="signOutTime"> <el-time-picker clearable v-model="form.signOutTime" :picker-options="{format:'HH:mm'}" value-format="HH:mm" placeholder="请选择签退时间"> </el-time-picker> </el-form-item> <el-form-item label="签退状态" prop="signOutStatus"> <el-radio-group v-model="form.signOutStatus"> <el-radio label="0">正常</el-radio> <el-radio label="1">早退</el-radio> </el-radio-group> </el-form-item> <el-form-item label="真实日期" prop="realDate"> <el-date-picker clearable v-model="form.realDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择真实日期"> </el-date-picker> </el-form-item> <el-form-item label="考勤类型" prop="attendanceType"> <el-radio-group v-model="form.attendanceType"> <el-radio label="0">正常打卡</el-radio> <el-radio label="1">外勤打卡</el-radio> </el-radio-group> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button type="primary" @click="submitForm">确 定</el-button> <el-button @click="cancel">取 消</el-button> </div> </el-dialog> <!-- 导入外部考勤数据对话框 --> <el-dialog :title="upload.title" :visible.sync="upload.open" width="500px" append-to-body> <div class="upload-container"> <div class="upload-tip"> 将文件拖到此处,或击上传 </div> <div class="upload-format"> 仅允许导入xls、xlsx格式文件。 </div> <div class="upload-template"> <el-button type="text" @click="importTemplate">下载模板</el-button> </div> <div class="upload-action"> <el-button type="primary" @click="submitFileForm">确定</el-button> <el-button @click="upload.open = false">取消</el-button> </div> </div> </el-dialog> </div> </template> <script> import {listWorkAttendances, getWorkAttendances, delWorkAttendances, addWorkAttendances, updateWorkAttendances} from "@/api/hrm/workAttendances"; import {getUser} from "@/api/system/user" import {getToken} from "@/utils/auth"; import Treeselect from "@riophae/vue-treeselect"; import "@riophae/vue-treeselect/dist/vue-treeselect.css"; import {treeselect} from "@/api/system/dept"; export default { name: "WorkAttendances", components: {Treeselect}, data() { return { // 部门名称 deptName: undefined, // 部门树选项 deptOptions: [], defaultProps: { children: "children", label: "label" }, // 打卡记录类型 // recordType: 'all', recordType: 'normal', // 遮罩层 loading: true, // 选中数组 ids: [], // 非单个禁用 single: true, // 非多个禁用 multiple: true, // 显示搜索条件 showSearch: true, // 总条数 total: 0, // 打卡记录表格数据 workAttendancesList: [], // 弹出层标题 title: "", // 是否显示弹出层 open: false, // 查询参数 queryParams: { pageNum: 1, pageSize: 20, signUserId: null, signNickName: null, signInTime: null, signOutTime: null, realDate: null, weekDays: null, weekNo: null, location: null, deptId: null, nickName: "", attendanceType: '0' // 新增考勤类型参数 }, attendanceTypeEnum: { NORMAL: '0', // 或者后端期望的值 EXTERNAL: '1' }, // 表单参数 form: {}, // 表单校验 rules: { attendanceType: [ { required: true, message: "考勤类型不能为空", trigger: "change" } ] }, // 用户导入参数 upload: { // 是否显示弹出层(用户导入) open: false, // 弹出层标题(用户导入) title: "导入外部考勤数据", // 是否禁用上传 isUploading: false, // 是否更新已经存在的用户数据 updateSupport: 0, // 设置上传的请求头部 headers: {Authorization: "Bearer " + getToken()}, // 上传的地址 url: process.env.VUE_APP_BASE_API + "/hrm/workAttendances/importData" }, }; }, watch: { // 根据名称筛选部门树 deptName(val) { this.$refs.tree.filter(val); } }, created() { this.getTreeselect(); this.getList(); }, methods: { /** 导入按钮操作 */ handleImport() { this.upload.title = "导入外部考勤数据"; this.upload.open = true; }, /** 下载模板操作 */ importTemplate() { this.download('hrm/workAttendances/importTemplate', {}, `考勤模板_${new Date().getTime()}.xlsx`) }, // 文件上传中处理 handleFileUploadProgress(event, file, fileList) { this.upload.isUploading = true; }, // 文件上传成功处理 handleFileSuccess(response, file, fileList) { this.upload.open = false; this.upload.isUploading = false; this.$refs.upload.clearFiles(); this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", {dangerouslyUseHTMLString: true}); this.getList(); }, // 提交上传文件 submitFileForm() { this.$refs.upload.submit(); }, // 取消按钮 cancel() { this.open = false; this.reset(); }, // 表单重置 reset() { this.form = { id: null, signUserId: null, signNickName: null, signInTime: null, signOutTime: null, realDate: null, weekDays: null, weekNo: null, location: null, attendanceType: "0" // 默认正常打卡 }; this.resetForm("form"); }, /** 搜索按钮操作 */ handleQuery() { this.queryParams.pageNum = 1; this.getList(); }, /** 重置按钮操作 */ resetQuery() { this.resetForm("queryForm"); // 保存关键参数 const { deptId, attendanceType } = this.queryParams; this.queryParams = { pageNum: 1, pageSize: 20, nickName: "", location: null, realDate: null, deptId: deptId || null, attendanceType: attendanceType || '0' // 确保有默认值 }; this.getList(); }, // 多选框选中数据 handleSelectionChange(selection) { this.ids = selection.map(item => item.id) this.single = selection.length !== 1 this.multiple = !selection.length }, /** 新增按钮操作 */ handleAdd() { this.reset(); this.open = true; this.title = "添加打卡记录"; }, /** 修改按钮操作 */ handleUpdate(row) { this.reset(); if(row.id){ const id = row.id || this.ids getWorkAttendances(id).then(response => { this.form = response.data; this.open = true; this.title = "修改打卡记录"; }); }else{ getUser(row.userId).then(res=>{ let data = {}; data.signUserId = row.userId; data.nickName = res.data.nickName; this.form = data; this.open = true; this.title = "修改打卡记录"; }) } }, /** 保存按钮 */ submitForm() { this.$refs["form"].validate(valid => { if (valid) { if (this.form.id != null) { updateWorkAttendances(this.form).then(response => { this.$modal.msgSuccess("修改成功"); this.open = false; this.getList(); }); } else { addWorkAttendances(this.form).then(response => { this.$modal.msgSuccess("新增成功"); this.open = false; this.getList(); }); } } }); }, /** 删除按钮操作 */ handleDelete(row) { const ids = row.id || this.ids; this.$modal.confirm('是否确认删除打卡记录的数据项?').then(function () { return delWorkAttendances(ids); }).then(() => { this.getList(); this.$modal.msgSuccess("删除成功"); }).catch(() => { }); }, /** 导出按钮操作 */ handleExport() { this.download('hrm/workAttendances/export', { ...this.queryParams }, `workAttendances_${new Date().getTime()}.xlsx`) }, /** 查询部门下拉树结构 */ getTreeselect() { treeselect().then(response => { this.deptOptions = response.data; }); }, // 筛选节 filterNode(value, data) { if (!value) return true; return data.label.indexOf(value) !== -1; }, // 节单击事件 handleNodeClick(data) { console.log('节数据:', data); this.queryParams.deptId = data.deptId || data.id; console.log('设置 deptId:', this.queryParams.deptId); this.getList(); // 直接调用 getList 更可靠 }, // 打卡记录类型切换 handleRecordTypeChange(val) { this.recordType = val; // 根据类型设置 attendanceType 查询参数 if (val === 'external') { this.queryParams.attendanceType = '1'; // 外勤打卡 } else { this.queryParams.attendanceType = '0'; // 正常打卡 } console.log('切换打卡类型:', val, '参数值:', this.queryParams.attendanceType); // 重新获取数据 this.getList(); }, /** 查询打卡记录列表 */ getList() { // 验证参数 if (this.queryParams.attendanceType === '' || this.queryParams.attendanceType === undefined) { this.queryParams.attendanceType = '0'; // 设置默认值 } console.log('验证后的参数:', this.queryParams); this.loading = true; listWorkAttendances(this.queryParams) .then(response => { this.workAttendancesList = response.rows; this.total = response.total; this.loading = false; }) .catch(error => { console.error('API 错误:', error); this.$modal.msgError("加载失败"); this.loading = false; }); }, } }; </script> <style scoped> .search-container { background: #fff; padding: 20px; margin-bottom: 20px; border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); } .record-type-tabs { margin-top: 15px; text-align: left; } .upload-container { text-align: center; padding: 20px; } .upload-tip { font-size: 16px; color: #606266; margin-bottom: 10px; } .upload-format { font-size: 14px; color: #909399; margin-bottom: 15px; } .upload-template { margin-bottom: 20px; } .upload-action { margin-top: 20px; } </style> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['oa:workAttendances:export']"> 导出 </el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="el-icon-upload" size="mini" @click="handleImport" v-hasPermi="['oa:workAttendances:edit']"> 导入外部考勤数据 </el-button> </el-col> <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar> </el-row>怎么不显示
最新发布
11-22
<template> <view class="container"> <view class="search-container"> <view class="search-item name-picker"> <uni-data-picker v-model="queryParams.nickName" placeholder="姓名搜索" @change="handleNameChange" @clear="handleNameClear" clear-icon></uni-data-picker> </view> <view class="search-item date-picker"> <uni-datetime-picker v-model="createTime" type="daterange" @change="handleDateChange" /> </view> </view> <uni-list> <uni-list-item v-for="item in recordList" :key="item.clockinId"> <template v-slot:body> <view class="list-item-container"> <view class="left-content"> <text class="nick-name">{{item.nickName}}</text> <text class="time-info">{{getNoteContent(item)}}</text> </view> <view class="right-content" v-if="item.location && item.location !== '定位失败'"> <text class="location">{{item.location}}</text> </view> </view> </template> </uni-list-item> </uni-list> <view v-if="!loading && recordList.length === 0" class="empty"> 暂无打卡数据 </view> <view v-if="loading && recordList.length === 0" class="loading"> 加载中... </view> </view> </template> <script> import { clockInList } from '@/api/clockin/clockin.js'; export default { data() { return { recordList: [], loading: false, pageNum: 1, // 当前页码 pageSize: 10, // 每页显示条数 total: 0, // 总条数 createTime: [], queryParams: { startDate: '', // 筛选的开始日期 endDate: '', // 筛选的结束日期 nickName: '' // 筛选的姓名 } } }, onReady() { uni.setNavigationBarTitle({ title: '打卡记录' }); uni.hideLoading(); }, onShow() { this.pageNum = 1; this.recordList = []; this.fetchCheckinList().catch(error => { console.error('获取打卡列表失败:', error); uni.showToast({ title: '加载失败', icon: 'none' }); }); }, onPullDownRefresh() { this.initData(); this.fetchCheckinList() .then(() => { uni.stopPullDownRefresh(); // 停止刷新动画 }) .catch(error => { console.error('获取打卡列表失败:', error); uni.showToast({ title: '加载失败', icon: 'none' }); uni.stopPullDownRefresh(); }); }, onReachBottom() { if (this.loading || this.noData) return; if (this.recordList.length >= this.total && this.total > 0) { this.noData = true; return; } this.pageNum++; this.fetchCheckinList().catch(error => { console.error('获取打卡列表失败:', error); uni.showToast({ title: '加载失败', icon: 'none' }); }); }, methods: { initData() { this.pageNum = 1; this.recordList = []; this.noData = false; }, // 获取打卡列表 getstatus(item) { switch (item.type) { case 0: return "上班"; case 1: return "下班"; case 2: return "补卡上班"; default: return "补卡下班"; } }, getNoteContent(item) { return `${this.getstatus(item)} | ${item.createTime}`; }, gettime(item) { return item.createTime; }, fetchCheckinList() { this.loading = true; // 创建干净的请求参数(排除空值) const params = { pageNum: this.pageNum, pageSize: this.pageSize, nickName: this.queryParams.nickName || undefined, }; if (this.queryParams.startDate) params.startDate = this.queryParams.startDate; if (this.queryParams.endDate) params.endDate = this.queryParams.endDate; return clockInList(this.queryParams).then(response => { this.total = response.total || 0; const newData = response.rows || []; if (this.pageNum === 1) { this.recordList = newData; } else { this.recordList = [...this.recordList, ...newData]; } // 检查是否已加载全部数据 if (this.recordList.length >= this.total) { this.noData = true; } }).catch(error => { console.error('获取打卡列表失败:', error); uni.showToast({ title: '加载失败', icon: 'none' }); }).finally(() => { this.loading = false; }); }, handleDateChange() { // 正确设置时间范围参数 if (this.createTime && this.createTime.length === 2) { this.queryParams.beginTime = this.createTime[0]; this.queryParams.endTime = this.createTime[1]; } else { // 清空无效的时间范围 this.queryParams.beginTime = undefined; this.queryParams.endTime = undefined; } this.queryParams.pageNum = 1 this.fetchCheckinList() }, } } </script> <style> .container { padding: 0; } .search-container { padding: 15rpx; background-color: #fff; display: flex; flex-wrap: nowrap; /* 禁止换行 */ gap: 10rpx; overflow-x: auto; } /* 姓名搜索框 */ .search-item.name-picker { flex: 1.2; min-width: 200rpx; } .search-item.date-picker { flex: 2; min-width: 300rpx; } .picker { padding: 16rpx 20rpx; border: 1rpx solid #e5e5e5; border-radius: 8rpx; font-size: 14px; text-align: center; white-space: nowrap; } .watermark { color: #c0c0c0 !important; opacity: 0.8; } .list-item-container { display: flex; justify-content: space-between; align-items: flex-start; width: 100%; padding: 20rpx 30rpx; } .left-content { flex-shrink: 0; display: flex; flex-direction: column; margin-right: 20rpx; white-space: nowrap; /* 确保左侧内容不换行 */ } .right-content { flex-grow: 1; text-align: right; word-break: break-all; /* 允许地址换行 */ } .nick-name { font-size: 16px; color: #333; margin-bottom: 5px; } .time-info { font-size: 14px; color: #666; } .location { font-size: 12px; color: #999; } .loading, .empty { text-align: center; padding: 20rpx; color: #999; font-size: 14px; } </style> Duplicate keys detected: '275'. This may cause an update error. found in ---> at pages/work/checkinRecord.vue
07-25
// 查询打卡记录列表 getList() { this.loading = true // 创建干净的请求参数(排除空值) const params = { pageNum: this.queryParams.pageNum, pageSize: this.queryParams.pageSize, workCode: this.queryParams.workCode, type: this.queryParams.type || undefined }; // 仅添加有效的时间范围参数 if (this.queryParams.beginTime) params.beginTime = this.queryParams.beginTime; if (this.queryParams.endTime) params.endTime = this.queryParams.endTime; listClockIn(this.queryParams).then(response => { // 直接传递queryParams this.clockList = response.rows; this.total = response.total; this.loading = false; }).catch(() => { this.loading = false; }); },仿照该段代码对以下页面中的搜索功能进行修改 <template> <view class="container"> <view class="search-container"> <view class="search-item name-picker"> <uni-data-picker v-model="selectedName" :localdata="nameOptions" placeholder="姓名搜索" @change="handleNameChange" @clear="handleNameClear" clear-icon></uni-data-picker> </view> <view class="search-item date-picker"> <uni-datetime-picker v-model="createTime" type="daterange" @change="handleDateChange" /> </view> </view> <uni-list> <uni-list-item v-for="item in recordList" :key="item.clockinId"> <template v-slot:body> <view class="list-item-container"> <view class="left-content"> <text class="nick-name">{{item.nickName}}</text> <text class="time-info">{{getNoteContent(item)}}</text> </view> <view class="right-content" v-if="item.location && item.location !== '定位失败'"> <text class="location">{{item.location}}</text> </view> </view> </template> </uni-list-item> </uni-list> <view v-if="!loading && recordList.length === 0" class="empty"> 暂无打卡数据 </view> <view v-if="loading && recordList.length === 0" class="loading"> 加载中... </view> </view> </template> <script> import { clockInList } from '@/api/clockin/clockin.js'; export default { data() { return { recordList: [], loading: false, pageNum: 1, // 当前页码 pageSize: 10, // 每页显示条数 total: 0, // 总条数 createTime: [], selectedName: '', nameOptions: [], searchParams: { startDate: '', // 筛选的开始日期 endDate: '', // 筛选的结束日期 nickName: '' // 筛选的姓名 } } }, watch: { // 监听日期范围变化 createTime(newVal) { if (newVal && newVal.length === 2) { this.searchParams.startDate = newVal[0]; this.searchParams.endDate = newVal[1]; this.handleSearch(); // 日期变化时自动触发筛选 } else { this.searchParams.startDate = ''; this.searchParams.endDate = ''; this.handleSearch(); } }, }, onReady() { uni.setNavigationBarTitle({ title: '打卡记录' }); uni.hideLoading(); }, onShow() { this.pageNum = 1; this.recordList = []; this.fetchCheckinList(); }, onPullDownRefresh() { this.initData(); this.fetchCheckinList().finally(() => { uni.stopPullDownRefresh(); // 停止刷新动画 }); }, onReachBottom() { if (this.loading) return; this.pageNum++; this.fetchCheckinList(); }, methods: { async loadNameOptions() { try { const cachedNames = uni.getStorageSync('nameOptions'); if (cachedNames) { this.nameOptions = cachedNames; return; } // 请求数据获取姓名列表 const response = await clockInList({ pageNum: 1, pageSize: 1000, // 获取足够多的记录以提取姓名 checkin: {} }); const nameSet = new Set(); response.rows.forEach(item => { if (item.nickName) { nameSet.add(item.nickName); } }); // 转换为uni-data-picker需要的格式 this.nameOptions = Array.from(nameSet).map(name => ({ value: name, text: name })); uni.setStorageSync('nameOptions', this.nameOptions); } catch (error) { console.error('加载姓名选项失败:', error); } }, initData() { this.pageNum = 1; }, handleSearch() { this.pageNum = 1; this.recordList = []; this.fetchCheckinList(); }, handleNameChange(e) { this.filterData(); }, handleNameClear() { this.selectedName = ''; this.filterData(); }, handleDateChange(e) { if (e && e.length === 2) { this.searchParams.startDate = e[0]; this.searchParams.endDate = e[1]; } else { this.searchParams.startDate = ''; this.searchParams.endDate = ''; } this.handleSearch(); }, // 获取打卡列表 getstatus(item) { switch (item.type) { case 0: return "上班"; case 1: return "下班"; case 2: return "补卡上班"; default: return "补卡下班"; } }, getNoteContent(item) { return `${this.getstatus(item)} | ${item.createTime}`; }, gettime(item) { return item.createTime; }, filterData() { if (!this.searchParams.nickName) { this.filteredList = [...this.recordList]; return; } const keyword = this.searchParams.nickName.toLowerCase(); this.filteredList = this.recordList.filter(item => item.nickName && item.nickName.toLowerCase().includes(keyword) ); }, fetchCheckinList() { this.loading = true; const checkinData = { pageNum: this.pageNum, pageSize: this.pageSize, checkin: { nickName: '', clockinId: 0, type: 0, createTime: '', location: '', startDate: this.searchParams.startDate, endDate: this.searchParams.endDate, } }; return clockInList(checkinData).then(response => { this.total = response.total || 0; const newData = response.rows || []; // 添加唯一标识符生成逻辑 const processedData = newData.map((item, index) => { return { ...item, // 生成唯一key:原始ID + 页码 + 索引(防止后端返回重复ID) uniqueKey: `${item.clockinId || ''}_${this.pageNum}_${index}` }; }); if (this.pageNum === 1) { this.recordList = processedData; } else { // 合并时去重 const existingIds = new Set(this.recordList.map(item => item.clockinId)); const filteredData = processedData.filter( item => !existingIds.has(item.clockinId) ); this.recordList = [...this.recordList, ...filteredData]; } this.filterData(); }).catch(error => { console.error('获取打卡列表失败:', error); uni.showToast({ title: '加载失败', icon: 'none' }); }).finally(() => { this.loading = false; }); } } } </script> <style> .container { padding: 0; } .search-container { padding: 15rpx; background-color: #fff; display: flex; flex-wrap: nowrap; /* 禁止换行 */ gap: 10rpx; overflow-x: auto; } /* 姓名搜索框 */ .search-item.name-picker { flex: 1.2; min-width: 200rpx; } .search-item.date-picker { flex: 2; min-width: 300rpx; } .picker { padding: 16rpx 20rpx; border: 1rpx solid #e5e5e5; border-radius: 8rpx; font-size: 14px; text-align: center; white-space: nowrap; } .watermark { color: #c0c0c0 !important; opacity: 0.8; } .list-item-container { display: flex; justify-content: space-between; align-items: flex-start; width: 100%; padding: 20rpx 30rpx; } .left-content { flex-shrink: 0; display: flex; flex-direction: column; margin-right: 20rpx; white-space: nowrap; /* 确保左侧内容不换行 */ } .right-content { flex-grow: 1; text-align: right; word-break: break-all; /* 允许地址换行 */ } .nick-name { font-size: 16px; color: #333; margin-bottom: 5px; } .time-info { font-size: 14px; color: #666; } .location { font-size: 12px; color: #999; } .loading, .empty { text-align: center; padding: 20rpx; color: #999; font-size: 14px; } </style>
07-25
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值