..
This commit is contained in:
+28
@@ -6,19 +6,24 @@ import com.budwk.app.base.page.Pagination;
|
|||||||
import com.budwk.app.base.result.Result;
|
import com.budwk.app.base.result.Result;
|
||||||
import com.budwk.app.client.module.activity.service.ClientActivityService;
|
import com.budwk.app.client.module.activity.service.ClientActivityService;
|
||||||
import com.budwk.app.client.module.activity.vo.ClientActivitySignVO;
|
import com.budwk.app.client.module.activity.vo.ClientActivitySignVO;
|
||||||
|
import com.budwk.app.module.activity.models.ActUser;
|
||||||
import com.budwk.app.module.activity.models.Activity;
|
import com.budwk.app.module.activity.models.Activity;
|
||||||
import com.budwk.app.web.commons.auth.satoken.StpClientUtil;
|
import com.budwk.app.web.commons.auth.satoken.StpClientUtil;
|
||||||
import org.nutz.dao.Cnd;
|
import org.nutz.dao.Cnd;
|
||||||
|
import org.nutz.dao.Dao;
|
||||||
import org.nutz.dao.Sqls;
|
import org.nutz.dao.Sqls;
|
||||||
import org.nutz.dao.sql.Sql;
|
import org.nutz.dao.sql.Sql;
|
||||||
import org.nutz.ioc.loader.annotation.Inject;
|
import org.nutz.ioc.loader.annotation.Inject;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
import org.nutz.mvc.adaptor.JsonAdaptor;
|
import org.nutz.mvc.adaptor.JsonAdaptor;
|
||||||
import org.nutz.mvc.annotation.AdaptBy;
|
import org.nutz.mvc.annotation.AdaptBy;
|
||||||
import org.nutz.mvc.annotation.At;
|
import org.nutz.mvc.annotation.At;
|
||||||
import org.nutz.mvc.annotation.GET;
|
import org.nutz.mvc.annotation.GET;
|
||||||
import org.nutz.mvc.annotation.Ok;
|
import org.nutz.mvc.annotation.Ok;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@IocBean
|
@IocBean
|
||||||
@At("/client/activity")
|
@At("/client/activity")
|
||||||
@Ok("json:full")
|
@Ok("json:full")
|
||||||
@@ -26,6 +31,8 @@ public class ClientActivityController {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private ClientActivityService clientActivityService;
|
private ClientActivityService clientActivityService;
|
||||||
|
@Inject
|
||||||
|
private Dao dao;
|
||||||
|
|
||||||
@At
|
@At
|
||||||
@AdaptBy(type = JsonAdaptor.class)
|
@AdaptBy(type = JsonAdaptor.class)
|
||||||
@@ -71,4 +78,25 @@ public class ClientActivityController {
|
|||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询我报名的活动
|
||||||
|
*/
|
||||||
|
@At("/list-mine-act")
|
||||||
|
@SaCheckLogin(type = StpClientUtil.TYPE)
|
||||||
|
public Result listMineAct() {
|
||||||
|
List<NutMap> list = clientActivityService.listMineAct();
|
||||||
|
return Result.success(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询报名签到信息
|
||||||
|
*/
|
||||||
|
@At("/sign-info/?")
|
||||||
|
@SaCheckLogin(type = StpClientUtil.TYPE)
|
||||||
|
public Result signInfo(Long activityId) {
|
||||||
|
ActUser actUser = dao.fetch(ActUser.class, Cnd.where(ActUser::getActivityId, "=", activityId).and(ActUser::getUserId, "=", StpClientUtil.STP.getLoginIdAsLong()));
|
||||||
|
return Result.success(actUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -2,6 +2,15 @@ package com.budwk.app.client.module.activity.service;
|
|||||||
|
|
||||||
import com.budwk.app.base.service.BaseService;
|
import com.budwk.app.base.service.BaseService;
|
||||||
import com.budwk.app.module.activity.models.Activity;
|
import com.budwk.app.module.activity.models.Activity;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface ClientActivityService extends BaseService<Activity> {
|
public interface ClientActivityService extends BaseService<Activity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询我报名的活动
|
||||||
|
*/
|
||||||
|
List<NutMap> listMineAct();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -3,12 +3,34 @@ package com.budwk.app.client.module.activity.service.impl;
|
|||||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||||
import com.budwk.app.client.module.activity.service.ClientActivityService;
|
import com.budwk.app.client.module.activity.service.ClientActivityService;
|
||||||
import com.budwk.app.module.activity.models.Activity;
|
import com.budwk.app.module.activity.models.Activity;
|
||||||
|
import com.budwk.app.web.commons.auth.satoken.StpClientUtil;
|
||||||
import org.nutz.dao.Dao;
|
import org.nutz.dao.Dao;
|
||||||
|
import org.nutz.dao.Sqls;
|
||||||
|
import org.nutz.dao.sql.Sql;
|
||||||
import org.nutz.ioc.loader.annotation.IocBean;
|
import org.nutz.ioc.loader.annotation.IocBean;
|
||||||
|
import org.nutz.lang.util.NutMap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@IocBean(args = {"refer:dao"})
|
@IocBean(args = {"refer:dao"})
|
||||||
public class ClientActivityServiceImpl extends BaseServiceImpl<Activity> implements ClientActivityService {
|
public class ClientActivityServiceImpl extends BaseServiceImpl<Activity> implements ClientActivityService {
|
||||||
public ClientActivityServiceImpl(Dao dao) {
|
public ClientActivityServiceImpl(Dao dao) {
|
||||||
super(dao);
|
super(dao);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NutMap> listMineAct() {
|
||||||
|
Sql sql = Sqls.create("""
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
cover
|
||||||
|
FROM
|
||||||
|
activity
|
||||||
|
WHERE
|
||||||
|
id IN (SELECT activityId FROM `act_user` WHERE userId = @userId)
|
||||||
|
""");
|
||||||
|
sql.setParam("userId", StpClientUtil.STP.getLoginIdAsLong());
|
||||||
|
return listMap(sql);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8"/>
|
||||||
<script>
|
<script>
|
||||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||||
CSS.supports('top: constant(a)'))
|
CSS.supports('top: constant(a)'))
|
||||||
@@ -12,9 +12,9 @@
|
|||||||
<title></title>
|
<title></title>
|
||||||
<!--preload-links-->
|
<!--preload-links-->
|
||||||
<!--app-context-->
|
<!--app-context-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"><!--app-html--></div>
|
<div id="app"><!--app-html--></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Generated
+7
@@ -24,6 +24,7 @@
|
|||||||
"@dcloudio/uni-mp-weixin": "3.0.0-4050720250324001",
|
"@dcloudio/uni-mp-weixin": "3.0.0-4050720250324001",
|
||||||
"@dcloudio/uni-mp-xhs": "3.0.0-4050720250324001",
|
"@dcloudio/uni-mp-xhs": "3.0.0-4050720250324001",
|
||||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4050720250324001",
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4050720250324001",
|
||||||
|
"html5-qrcode": "^2.3.8",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"tdesign-mobile-vue": "^1.8.3",
|
"tdesign-mobile-vue": "^1.8.3",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
@@ -6952,6 +6953,12 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/html5-qrcode": {
|
||||||
|
"version": "2.3.8",
|
||||||
|
"resolved": "https://registry.npmmirror.com/html5-qrcode/-/html5-qrcode-2.3.8.tgz",
|
||||||
|
"integrity": "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"@dcloudio/uni-mp-weixin": "3.0.0-4050720250324001",
|
"@dcloudio/uni-mp-weixin": "3.0.0-4050720250324001",
|
||||||
"@dcloudio/uni-mp-xhs": "3.0.0-4050720250324001",
|
"@dcloudio/uni-mp-xhs": "3.0.0-4050720250324001",
|
||||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4050720250324001",
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4050720250324001",
|
||||||
|
"html5-qrcode": "^2.3.8",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"tdesign-mobile-vue": "^1.8.3",
|
"tdesign-mobile-vue": "^1.8.3",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
<template>
|
||||||
|
<!-- <div class="scanCode" v-if="isScanning">-->
|
||||||
|
<!-- <div class="btn-container">-->
|
||||||
|
<!-- <div class="left-back" @click.stop="handleBack">-->
|
||||||
|
<!-- <t-icon name="arrow-left" />-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="container">-->
|
||||||
|
<!-- <div class="qrcode">-->
|
||||||
|
<!-- <div ref="qrCodeElement" id="reader"></div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<div class="page-box" v-if="isScanning">
|
||||||
|
<div class="btn-box">
|
||||||
|
<div>
|
||||||
|
<t-icon name="arrow-left" @click="handleBack"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="qr-container">
|
||||||
|
<div class="qr-box">
|
||||||
|
<div id="reader"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {ref, defineEmits, onUnmounted, defineExpose, nextTick} from 'vue'
|
||||||
|
import {Html5Qrcode} from 'html5-qrcode'
|
||||||
|
import {Toast} from "tdesign-mobile-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['scan-success', 'back'])
|
||||||
|
const html5QrCode = ref(null)
|
||||||
|
const fileList = ref([])
|
||||||
|
const isScanning = ref(false)
|
||||||
|
const hasCameraPermission = ref(false)
|
||||||
|
const qrCodeElement = ref(null)
|
||||||
|
|
||||||
|
const startScan = async () => {
|
||||||
|
try {
|
||||||
|
const devices = await Html5Qrcode.getCameras()
|
||||||
|
if (!devices || !devices.length) {
|
||||||
|
Toast('未检测到可用摄像头')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 显示扫描界面
|
||||||
|
isScanning.value = true
|
||||||
|
|
||||||
|
// 3. 等待DOM更新完成
|
||||||
|
await nextTick()
|
||||||
|
console.log(document.getElementById('reader'))
|
||||||
|
|
||||||
|
// 5. 初始化扫码器
|
||||||
|
html5QrCode.value = new Html5Qrcode('reader')
|
||||||
|
|
||||||
|
console.log(html5QrCode.value)
|
||||||
|
|
||||||
|
// 6. 开始扫描
|
||||||
|
await html5QrCode.value.start(
|
||||||
|
{facingMode: 'environment'},
|
||||||
|
{fps: 10, qrbox: {width: 200, height: 200}},
|
||||||
|
(decodedText) => {
|
||||||
|
emit('scan-success', decodedText)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
isScanning.value = false
|
||||||
|
handleCameraError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始摄像头扫描
|
||||||
|
const startCameraScan = () => {
|
||||||
|
return html5QrCode.value.start(
|
||||||
|
{facingMode: 'environment'},
|
||||||
|
{
|
||||||
|
fps: 1,
|
||||||
|
qrbox: {width: 250, height: 250}
|
||||||
|
},
|
||||||
|
(decodedText) => {
|
||||||
|
emit('scan-success', decodedText)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止扫描
|
||||||
|
const stopScan = async () => {
|
||||||
|
alert(2)
|
||||||
|
isScanning.value = false
|
||||||
|
if (html5QrCode.value?.isScanning) {
|
||||||
|
try {
|
||||||
|
await html5QrCode.value.stop()
|
||||||
|
} catch (err) {
|
||||||
|
Toast.error('停止扫码失败')
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理摄像头错误
|
||||||
|
const handleCameraError = (err) => {
|
||||||
|
alert(JSON.stringify(err))
|
||||||
|
let message = '摄像头启动失败'
|
||||||
|
if (err.name === 'NotAllowedError') {
|
||||||
|
message = '请允许摄像头访问权限'
|
||||||
|
} else if (err.name === 'NotFoundError') {
|
||||||
|
message = '未找到可用摄像头'
|
||||||
|
}
|
||||||
|
Toast(message)
|
||||||
|
hasCameraPermission.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
stopScan()
|
||||||
|
emit('back')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件卸载时停止扫描
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopScan()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 暴露方法给父组件
|
||||||
|
defineExpose({
|
||||||
|
startScan,
|
||||||
|
stopScan
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
//.scanCode {
|
||||||
|
// //background: rgba(0, 0, 0);
|
||||||
|
// position: fixed;
|
||||||
|
// left: 0;
|
||||||
|
// right: 0;
|
||||||
|
// top: 0;
|
||||||
|
// bottom: 0;
|
||||||
|
// z-index: 999;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//.btn-container {
|
||||||
|
// position: absolute;
|
||||||
|
// top: 16px;
|
||||||
|
// left: 16px;
|
||||||
|
// z-index: 1000;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//.left-back {
|
||||||
|
// color: #fff;
|
||||||
|
// font-size: 24px;
|
||||||
|
// padding: 8px;
|
||||||
|
// background: rgba(0, 0, 0, 0.3);
|
||||||
|
// border-radius: 50%;
|
||||||
|
// display: flex;
|
||||||
|
// align-items: center;
|
||||||
|
// justify-content: center;
|
||||||
|
// width: 40px;
|
||||||
|
// height: 40px;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//.container {
|
||||||
|
// //height: 100vh;
|
||||||
|
// //position: relative;
|
||||||
|
// //width: 100%;
|
||||||
|
// position: fixed;
|
||||||
|
// left: 0;
|
||||||
|
// right: 0;
|
||||||
|
// top: 0;
|
||||||
|
// bottom: 0;
|
||||||
|
// z-index: 999;
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//.qrcode {
|
||||||
|
// height: 100%;
|
||||||
|
// position: fixed;
|
||||||
|
// left: 0;
|
||||||
|
// right: 0;
|
||||||
|
// top: 0;
|
||||||
|
// bottom: 0;
|
||||||
|
// z-index: 999;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//#reader {
|
||||||
|
// //top: 20%;
|
||||||
|
// //left: 0;
|
||||||
|
// //transform: translateY(-50%);
|
||||||
|
// //height: 100%;
|
||||||
|
// width: 500rpx;
|
||||||
|
// height: 500rpx;
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.page-box {
|
||||||
|
//display: flex;
|
||||||
|
//flex-direction: column;
|
||||||
|
//height: 100%;
|
||||||
|
background: #000;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
.qr-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.qr-box {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#reader {
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.btn-box {
|
||||||
|
//flex: 1;
|
||||||
|
//display: flex;
|
||||||
|
//justify-content: space-around;
|
||||||
|
//align-items: flex-start;
|
||||||
|
//margin-top: auto;
|
||||||
|
//padding: 12px;
|
||||||
|
//color: #fff;
|
||||||
|
//font-size: 28px;
|
||||||
|
position: a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -13,6 +13,7 @@ import moment from 'moment';
|
|||||||
import 'moment/dist/locale/zh-cn'
|
import 'moment/dist/locale/zh-cn'
|
||||||
moment.locale('zh-cn')
|
moment.locale('zh-cn')
|
||||||
|
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App);
|
const app = createSSRApp(App);
|
||||||
app.use(TDesign); // 使用TDesign组件库
|
app.use(TDesign); // 使用TDesign组件库
|
||||||
|
|||||||
@@ -1,516 +1,268 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="sign-page">
|
<view class="page-container">
|
||||||
<!-- 背景装饰 -->
|
<t-dropdown-menu>
|
||||||
<view class="bg-decoration"></view>
|
<t-dropdown-item :options="actOptions" :value="selectActId" @change="getSignInfo"/>
|
||||||
|
|
||||||
<!-- 主内容区 -->
|
|
||||||
<view class="content-container">
|
|
||||||
<!-- 页面标题 -->
|
|
||||||
<view class="title-area">
|
|
||||||
<text class="title">活动签到</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 活动选择区 -->
|
|
||||||
<view class="selector-card">
|
|
||||||
<t-dropdown-menu class="activity-dropdown">
|
|
||||||
<t-dropdown-item
|
|
||||||
v-model="selectedActivity"
|
|
||||||
label="请选择活动"
|
|
||||||
:options="activityOptions"
|
|
||||||
@change="handleActivityChange"
|
|
||||||
/>
|
|
||||||
</t-dropdown-menu>
|
</t-dropdown-menu>
|
||||||
|
<view class="content-container">
|
||||||
|
<view class="scan-area" @click="handleScan">
|
||||||
|
<view class="scan-icon-wrapper">
|
||||||
|
<t-icon name="scan" size="80" color="#f45a5a"></t-icon>
|
||||||
</view>
|
</view>
|
||||||
|
<t-button shape="round"
|
||||||
<!-- 签到区域 -->
|
theme="primary"
|
||||||
<view class="sign-card" v-if="currentActivity">
|
size="medium">扫描二维码签到
|
||||||
<view class="activity-title">{{ currentActivity.title }}</view>
|
|
||||||
|
|
||||||
<view class="qr-scan-area">
|
|
||||||
<view class="qr-frame">
|
|
||||||
<view class="corner top-left"></view>
|
|
||||||
<view class="corner top-right"></view>
|
|
||||||
<view class="corner bottom-left"></view>
|
|
||||||
<view class="corner bottom-right"></view>
|
|
||||||
<t-icon name="qrcode" size="120rpx" class="qr-icon" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<t-button size="large" class="scan-btn" @tap="handleScan" :disabled="isSignedIn">
|
|
||||||
{{ isSignedIn ? '已完成签到' : '点击扫码签到' }}
|
|
||||||
</t-button>
|
</t-button>
|
||||||
|
</view>
|
||||||
<view class="sign-status" v-if="signStatus !== 'unsigned'">
|
<view class="guide-area">
|
||||||
<t-icon name="check-circle-filled" size="48rpx" class="status-icon success" v-if="signStatus === 'signed'" />
|
<view class="guide-title">签到说明</view>
|
||||||
<t-icon name="time" size="48rpx" class="status-icon warning" v-else-if="signStatus === 'late'" />
|
<view class="guide-list">
|
||||||
<text class="status-text">{{ signStatusText }}</text>
|
<view class="guide-item">
|
||||||
<text class="sign-time" v-if="latestSignTime">{{ latestSignTime }}</text>
|
<uni-icons class="guide-icon" type="info" size="16" color="#999999"/>
|
||||||
|
<text class="guide-text">请对准活动二维码进行扫描</text>
|
||||||
|
</view>
|
||||||
|
<view class="guide-item">
|
||||||
|
<uni-icons class="guide-icon" type="info" size="16" color="#999999"/>
|
||||||
|
<text class="guide-text">确保手机相机权限已开启</text>
|
||||||
|
</view>
|
||||||
|
<view class="guide-item">
|
||||||
|
<uni-icons class="guide-icon" type="info" size="16" color="#999999"/>
|
||||||
|
<text class="guide-text">保持二维码清晰可见</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="current-status-area">
|
||||||
|
<view class="status-header">
|
||||||
|
<text class="status-title">当前签到状态</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="signInfo" class="status-content">
|
||||||
|
<template v-if="signInfo.isSignIn">
|
||||||
|
<view class="sign-success">
|
||||||
|
<t-icon name="task-checked-1" size="48" color="#07c160"></t-icon>
|
||||||
|
<text class="success-text">已完成签到</text>
|
||||||
|
<view class="sign-detail">
|
||||||
|
<view class="detail-item">
|
||||||
|
<uni-icons type="calendar" size="14" color="#666666" />
|
||||||
|
<text>签到时间:{{ signInfo.signInTime }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="detail-item">
|
||||||
|
<uni-icons type="location" size="14" color="#666666" />
|
||||||
|
<text>签到地点:{{ signInfo.location }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<view class="sign-pending">
|
||||||
|
<t-icon name="info-circle" size="48" color="#f45a5a"></t-icon>
|
||||||
|
<text class="pending-text">待签到</text>
|
||||||
|
<text class="pending-tip">请使用扫码签到完成签到</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 无活动状态 -->
|
<ScanQRCode
|
||||||
<view class="empty-state" v-if="!currentActivity">
|
ref="qrScannerRef"
|
||||||
<t-icon name="folder" size="120rpx" class="empty-icon" />
|
@scan-success="handleScanSuccess"
|
||||||
<text class="empty-text">暂无可签到的活动</text>
|
/>
|
||||||
<t-button size="small" variant="outline" class="refresh-btn" @tap="getMyActivities">刷新</t-button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import {ref, computed, onMounted} from 'vue';
|
||||||
import { useRequest } from '@/utils/request';
|
import {useRequest} from '@/utils/request';
|
||||||
import { Toast } from 'tdesign-mobile-vue';
|
import {Toast} from 'tdesign-mobile-vue';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import ScanQRCode from '@/components/scanQrCode.vue'
|
||||||
|
|
||||||
// 模拟活动数据
|
const request = useRequest()
|
||||||
const mockActivities = [
|
|
||||||
{
|
const list = ref([])
|
||||||
id: '1',
|
const signInfo = ref({})
|
||||||
title: '2023年度篮球联赛',
|
const actOptions = computed(() => {
|
||||||
cover: 'https://img01.71360.com/file/read/www/M00/4C/98/rBsAFWQUdqaABB-oAAHYfGPYE3Y799.jpg',
|
return list.value.map(item => {
|
||||||
startTime: '2023-12-10 09:00:00',
|
return {
|
||||||
endTime: '2023-12-15 18:00:00',
|
value: item.id,
|
||||||
address: '市体育中心主场馆',
|
label: item.title
|
||||||
signStartTime: '2023-11-20 00:00:00',
|
|
||||||
signEndTime: '2023-12-05 23:59:59',
|
|
||||||
signedIn: false,
|
|
||||||
signTime: null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
title: '企业文化交流会',
|
|
||||||
cover: 'https://img.zcool.cn/community/01a4d75b04a27ea801209e1f0fb01d.jpg@1280w_1l_2o_100sh.jpg',
|
|
||||||
startTime: '2023-11-25 13:30:00',
|
|
||||||
endTime: '2023-11-25 17:30:00',
|
|
||||||
address: '国际会议中心3号厅',
|
|
||||||
signStartTime: '2023-11-01 00:00:00',
|
|
||||||
signEndTime: '2023-11-24 23:59:59',
|
|
||||||
signedIn: true,
|
|
||||||
signTime: '2023-11-25 13:20:14',
|
|
||||||
status: 'signed'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
title: '技术创新大赛决赛',
|
|
||||||
cover: 'https://img.zcool.cn/community/01b1f95b04a255a8012182a7ebfe26.jpg@1280w_1l_2o_100sh.jpg',
|
|
||||||
startTime: '2023-12-05 09:00:00',
|
|
||||||
endTime: '2023-12-05 18:00:00',
|
|
||||||
address: '科技园区创新中心',
|
|
||||||
signStartTime: '2023-11-10 00:00:00',
|
|
||||||
signEndTime: '2023-12-04 23:59:59',
|
|
||||||
signedIn: true,
|
|
||||||
signTime: '2023-12-05 09:15:22',
|
|
||||||
status: 'late'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '4',
|
|
||||||
title: '2023年迎新晚会',
|
|
||||||
cover: 'https://img.zcool.cn/community/01f2b35bd123b1a801208f8bd8710a.jpg@1280w_1l_2o_100sh.jpg',
|
|
||||||
startTime: '2023-12-31 19:00:00',
|
|
||||||
endTime: '2024-01-01 00:30:00',
|
|
||||||
address: '文化广场中心舞台',
|
|
||||||
signStartTime: '2023-12-15 00:00:00',
|
|
||||||
signEndTime: '2023-12-30 23:59:59',
|
|
||||||
signedIn: false,
|
|
||||||
signTime: null
|
|
||||||
}
|
}
|
||||||
];
|
})
|
||||||
|
})
|
||||||
// 判断环境是否为开发环境
|
const selectActId = ref(null)
|
||||||
const isDev = process.env.NODE_ENV === 'development';
|
const qrScannerRef = ref()
|
||||||
|
|
||||||
// 活动列表
|
|
||||||
const myActivities = ref([]);
|
|
||||||
const activityOptions = computed(() => {
|
|
||||||
return myActivities.value.map(item => ({
|
|
||||||
label: item.title,
|
|
||||||
value: item.id
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 选中的活动ID
|
|
||||||
const selectedActivity = ref('');
|
|
||||||
const currentActivity = computed(() => {
|
|
||||||
return myActivities.value.find(item => item.id === selectedActivity.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 是否已签到
|
|
||||||
const isSignedIn = computed(() => {
|
|
||||||
return currentActivity.value?.signedIn || false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 最近签到时间
|
|
||||||
const latestSignTime = computed(() => {
|
|
||||||
if (!currentActivity.value?.signTime) return null;
|
|
||||||
return `签到时间: ${moment(currentActivity.value.signTime).format('YYYY-MM-DD HH:mm:ss')}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 签到状态
|
|
||||||
const signStatus = computed(() => {
|
|
||||||
return currentActivity.value?.status || 'unsigned';
|
|
||||||
});
|
|
||||||
|
|
||||||
const signStatusText = computed(() => {
|
|
||||||
switch(signStatus.value) {
|
|
||||||
case 'signed':
|
|
||||||
return '签到成功';
|
|
||||||
case 'late':
|
|
||||||
return '迟到签到';
|
|
||||||
default:
|
|
||||||
return '未签到';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取我的活动
|
|
||||||
const getMyActivities = async () => {
|
|
||||||
try {
|
|
||||||
Toast({ message: '加载中...', theme: 'loading' });
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
// 开发环境使用模拟数据
|
|
||||||
setTimeout(() => {
|
|
||||||
myActivities.value = mockActivities;
|
|
||||||
if (myActivities.value.length > 0) {
|
|
||||||
selectedActivity.value = myActivities.value[0].id;
|
|
||||||
}
|
|
||||||
Toast({ message: '加载成功', theme: 'success' });
|
|
||||||
}, 800);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生产环境调用接口
|
|
||||||
const response = await useRequest().get('/client/activity/myActivities');
|
|
||||||
if (response.code === 0) {
|
|
||||||
myActivities.value = response.data || [];
|
|
||||||
if (myActivities.value.length > 0) {
|
|
||||||
selectedActivity.value = myActivities.value[0].id;
|
|
||||||
}
|
|
||||||
Toast({ message: '加载成功', theme: 'success' });
|
|
||||||
} else {
|
|
||||||
Toast({ message: '获取活动失败', theme: 'error' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取活动失败', error);
|
|
||||||
Toast({ message: '网络异常,请重试', theme: 'error' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 活动切换
|
|
||||||
const handleActivityChange = (value) => {
|
|
||||||
// 切换时不需要额外操作
|
|
||||||
};
|
|
||||||
|
|
||||||
// 扫码
|
|
||||||
const handleScan = () => {
|
|
||||||
if (!currentActivity.value) {
|
|
||||||
Toast({ message: '请先选择活动', theme: 'warning' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSignedIn.value) {
|
|
||||||
Toast({ message: '已完成签到', theme: 'warning' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast({ message: '准备扫码', theme: 'loading' });
|
|
||||||
setTimeout(() => {
|
|
||||||
if (isDev) {
|
|
||||||
// 开发环境模拟扫码成功
|
|
||||||
setTimeout(() => {
|
|
||||||
Toast({ message: '签到中...', theme: 'loading' });
|
|
||||||
setTimeout(() => {
|
|
||||||
const randomSuccess = Math.random() > 0.2;
|
|
||||||
if (randomSuccess) {
|
|
||||||
const isLate = Math.random() > 0.7;
|
|
||||||
const status = isLate ? 'late' : 'signed';
|
|
||||||
const statusText = isLate ? '迟到签到' : '签到成功';
|
|
||||||
|
|
||||||
// 更新活动签到状态
|
|
||||||
const activity = myActivities.value.find(a => a.id === selectedActivity.value);
|
|
||||||
if (activity) {
|
|
||||||
activity.signedIn = true;
|
|
||||||
activity.signTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
activity.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast({ message: statusText, theme: isLate ? 'warning' : 'success' });
|
|
||||||
} else {
|
|
||||||
Toast({ message: '二维码无效', theme: 'error' });
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}, 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生产环境实际扫码
|
|
||||||
uni.scanCode({
|
|
||||||
success: async (res) => {
|
|
||||||
try {
|
|
||||||
Toast({ message: '签到中...', theme: 'loading' });
|
|
||||||
// 将扫码结果发送到后端
|
|
||||||
const response = await useRequest().post('/client/activity/sign', {
|
|
||||||
activityId: selectedActivity.value,
|
|
||||||
qrCode: res.result
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.code === 0) {
|
|
||||||
// 更新活动签到状态
|
|
||||||
const activity = myActivities.value.find(a => a.id === selectedActivity.value);
|
|
||||||
if (activity) {
|
|
||||||
activity.signedIn = true;
|
|
||||||
activity.signTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
activity.status = response.data.status || 'signed';
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusText = activity.status === 'late' ? '迟到签到' : '签到成功';
|
|
||||||
Toast({ message: statusText, theme: activity.status === 'late' ? 'warning' : 'success' });
|
|
||||||
} else {
|
|
||||||
Toast({ message: response.message || '签到失败', theme: 'error' });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('签到失败', error);
|
|
||||||
Toast({ message: '网络异常,请重试', theme: 'error' });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
Toast({ message: '扫码取消', theme: 'warning' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getMyActivities();
|
listMineAct();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 获取活动列表
|
||||||
|
async function listMineAct() {
|
||||||
|
try {
|
||||||
|
const {data, code} = await request.get('/client/activity/list-mine-act')
|
||||||
|
if (code === 0) {
|
||||||
|
list.value = data
|
||||||
|
if (list.value.length) {
|
||||||
|
selectActId.value = list.value[0].id
|
||||||
|
await getSignInfo();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.error(data.msg)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
Toast.error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取签到信息
|
||||||
|
async function getSignInfo(val){
|
||||||
|
try {
|
||||||
|
const {code, data, msg} = await request.get('/client/activity/sign-info/' + (val ? val :selectActId.value))
|
||||||
|
if (code === 0) {
|
||||||
|
signInfo.value = data
|
||||||
|
} else {
|
||||||
|
Toast.error(msg)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
Toast.error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫码签到
|
||||||
|
function handleScan() {
|
||||||
|
qrScannerRef.value.startScan()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleScanSuccess(val){
|
||||||
|
console.log(val)
|
||||||
|
Toast.success(val)
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.sign-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
padding-top: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-decoration {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 380rpx;
|
|
||||||
background: linear-gradient(to bottom, rgb(244, 90, 90, 0.8), rgb(244, 90, 90, 0.2));
|
|
||||||
border-bottom-left-radius: 40rpx;
|
|
||||||
border-bottom-right-radius: 40rpx;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-container {
|
.content-container {
|
||||||
position: relative;
|
padding: 15px;
|
||||||
z-index: 1;
|
}
|
||||||
padding: 0 30rpx;
|
|
||||||
|
.scan-area {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 64rpx 0;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-area {
|
.scan-icon-wrapper {
|
||||||
margin-bottom: 40rpx;
|
width: 160rpx;
|
||||||
width: 100%;
|
height: 160rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.guide-area {
|
||||||
font-size: 48rpx;
|
background-color: #ffffff;
|
||||||
font-weight: bold;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-card {
|
|
||||||
width: 100%;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 24rpx;
|
padding: 32rpx;
|
||||||
margin-bottom: 30rpx;
|
margin-bottom: 32rpx;
|
||||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-dropdown {
|
.guide-title {
|
||||||
width: 100%;
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.t-dropdown-menu) {
|
.guide-item {
|
||||||
background-color: transparent !important;
|
display: flex;
|
||||||
height: 80rpx;
|
align-items: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.t-dropdown-menu__item) {
|
.guide-icon {
|
||||||
font-size: 30rpx;
|
width: 32rpx;
|
||||||
color: #333 !important;
|
height: 32rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.t-dropdown-item__content) {
|
.guide-text {
|
||||||
border-radius: 12rpx !important;
|
font-size: 14px;
|
||||||
overflow: hidden;
|
color: #666666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sign-card {
|
.current-status-area {
|
||||||
width: 100%;
|
background-color: #ffffff;
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
padding: 40rpx 30rpx;
|
padding: 32rpx;
|
||||||
margin-bottom: 30rpx;
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
}
|
||||||
|
|
||||||
|
.status-header {
|
||||||
|
margin-bottom: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 48rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign-success,
|
||||||
|
.sign-pending {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
|
||||||
|
|
||||||
.activity-title {
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.qr-scan-area {
|
.success-text {
|
||||||
margin-bottom: 40rpx;
|
font-size: 20px;
|
||||||
padding: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-frame {
|
|
||||||
width: 280rpx;
|
|
||||||
height: 280rpx;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: rgba(244, 90, 90, 0.03);
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.corner {
|
|
||||||
position: absolute;
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-color: rgb(244, 90, 90);
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-left {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
border-top-width: 6rpx;
|
|
||||||
border-left-width: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-right {
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
border-top-width: 6rpx;
|
|
||||||
border-right-width: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-left {
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
border-bottom-width: 6rpx;
|
|
||||||
border-left-width: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-right {
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
border-bottom-width: 6rpx;
|
|
||||||
border-right-width: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qr-icon {
|
|
||||||
color: rgb(244, 90, 90);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-btn {
|
|
||||||
width: 70%;
|
|
||||||
height: 90rpx;
|
|
||||||
background-color: rgb(244, 90, 90);
|
|
||||||
color: #fff;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 45rpx;
|
|
||||||
box-shadow: 0 8rpx 20rpx rgba(244, 90, 90, 0.3);
|
|
||||||
transition: all 0.3s;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-btn:active {
|
|
||||||
transform: scale(0.97);
|
|
||||||
box-shadow: 0 4rpx 10rpx rgba(244, 90, 90, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-btn[disabled] {
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
color: #bbbbbb;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sign-status {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-icon {
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-icon.success {
|
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
}
|
|
||||||
|
|
||||||
.status-icon.warning {
|
|
||||||
color: #ff9f00;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #333;
|
margin: 24rpx 0 32rpx;
|
||||||
margin-bottom: 8rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sign-time {
|
.sign-detail {
|
||||||
font-size: 26rpx;
|
margin-top: 32rpx;
|
||||||
color: #999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.detail-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
margin-bottom: 16rpx;
|
||||||
padding: 60rpx 0;
|
color: #666666;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-icon {
|
.detail-item uni-icons {
|
||||||
color: #ddd;
|
margin-right: 12rpx;
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-text {
|
.pending-text {
|
||||||
font-size: 30rpx;
|
font-size: 20px;
|
||||||
color: #999;
|
color: #f45a5a;
|
||||||
margin-bottom: 30rpx;
|
font-weight: 500;
|
||||||
|
margin: 24px 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh-btn {
|
.pending-tip {
|
||||||
color: rgb(244, 90, 90);
|
font-size: 14px;
|
||||||
border-color: rgb(244, 90, 90);
|
color: #999999;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDuEcmyaW2QmhIB
|
||||||
|
6kTFYgcC9eoWHfZfgadJvl6sqLG4YKNiaBp6fXInvTecVSC4VGpggfXKfjTMlUaW
|
||||||
|
ZvtMV6i/gx+tP8LTH6lu5ht6sb9LcyO0bjJ+VZuyTeLbOPYiFGq2/Lxgl50wfr/E
|
||||||
|
BPBg3epQatxyegzbbkrWLS2ouA4sCOKrBhe+aUi8Vkp9Ny4SLAziwgGbkvL04zj3
|
||||||
|
w7omQElSCPdcjG/+gDCCee9CdMVGmvPKvOJTob0frlndfpnwQR95he8pKVlDQjkg
|
||||||
|
sjQZV2vgGQugRBcOBJxx1kENDAEctmrgXvWHE1gUbgN0aMfAdd5ZnbAShzJuWURM
|
||||||
|
qBLZOQ55AgMBAAECggEBALo206tn57mZqcgxtEUoHg/kxosyOIoP4gg+jdNDyG9N
|
||||||
|
t767ayqwgOAwkxUHSSalcOe3/8e53BbCFIqEVsAqOOZD7D0ZiIyXUXJbLtdTvSuJ
|
||||||
|
Dj6Ni5Vh5P0dCvsqnOufSLGFEtwC+4L5QYpTyqoaKrNoQrKQYQoqRQMuJ263OO6b
|
||||||
|
KmUYd3fGThTeqKc6JGbExd33l6aYNxbX88x3h3oS4hq56owGkfpbouQFkQy1XK6E
|
||||||
|
DcJoGWv1QHHP51A6QKro6Vlo/BdeVvyv//gUzvOMJ0vMzaLnPSt6/Tdj/83zJAVw
|
||||||
|
P0JBGU/l2ZnV7EwR/OyCml4yX9zTVgqbX++hq0QZ9jkCgYEA88cJuFVxrR2Hx0u/
|
||||||
|
Mv0lwaSzFyYh49ox/UAh8OhZZXfmZcwT70UIBw52RVguKrZLvUcPGBVT7UoJpslR
|
||||||
|
6ycwYiLbXZ9w+ZQPIvBp7BLl1SSFsgBy8rM93VwV20qNhVoS+qLU80OBvlhigFDc
|
||||||
|
RLE9ZXfM35jILVn8/JWlo12qcpsCgYEA+gF8XWlHKYroNrdnCwxZZm0BDfb+1DNG
|
||||||
|
/6r9mu4L+/b//n4jJ/oxZDGaOd6T8XL4pXpKeys5Q8TSbbPA44klF/ooGVd9J1jy
|
||||||
|
9gby0dYli/It0NyCU7vO3PT3FdY3KPKb53GLWXGB30B/OLL0K+R8Ff9yMtm2xoaL
|
||||||
|
jNicXVX02nsCgYEAk9TtCRMPx7qzv2EpBsPLZL37ORuPy7xjOj/tFUniBI2Mc8kp
|
||||||
|
gZ0sPBNCF9Vc0dwQ3Cy+c41jgu+2IKXPfroaNjkLLKBnA4MNFtQSd+4R4IqwZPyu
|
||||||
|
uI2IA7g3LO/uGKNCBSo/13HluUeiHExs919Aa0sWO7ZMFmyO5aHee85ucecCgYBA
|
||||||
|
43XWAOc5fxY6bWvPUEs77HrjlwIZLZ316fmc5sAxi7Bx62dQjv2W1dEDyCN5/a9P
|
||||||
|
s+f1BAj+7+z2EDu2UvhPutwBh9WyCsI03STVFdH0+sytcy5YcVyFuQcQoeL7O8z/
|
||||||
|
S1cxxeZBfcoHe1sosUsLopuPWza3XGCIFAixqQe+VQKBgQDfLLRJwirg9DVv5PlY
|
||||||
|
A545KHWOnzJpvI9ZcUvcvC+QAzPbcdO+Lv3k6XHW3Gv4BWUv/uSsTFN41Ib+ZykB
|
||||||
|
SUICqRzwF1x8O0lXyBBDooN4nXCQt5YbsX/Y9VbOWTxiJ4Bf2SBn1myWryXz3CM+
|
||||||
|
Fi9jIfi+oDYmJbmwGgTyMCCgOQ==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIETzCCAregAwIBAgIQb5YGv6dOgj3XDOtlEmSMFTANBgkqhkiG9w0BAQsFADCB
|
||||||
|
gzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMSwwKgYDVQQLDCNERVNL
|
||||||
|
VE9QLVNWSEk0MlRcanVnQERFU0tUT1AtU1ZISTQyVDEzMDEGA1UEAwwqbWtjZXJ0
|
||||||
|
IERFU0tUT1AtU1ZISTQyVFxqdWdAREVTS1RPUC1TVkhJNDJUMB4XDTI1MDQyNTA3
|
||||||
|
NDYzNVoXDTI3MDcyNTA3NDYzNVowVzEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3Bt
|
||||||
|
ZW50IGNlcnRpZmljYXRlMSwwKgYDVQQLDCNERVNLVE9QLVNWSEk0MlRcanVnQERF
|
||||||
|
U0tUT1AtU1ZISTQyVDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO4R
|
||||||
|
ybJpbZCaEgHqRMViBwL16hYd9l+Bp0m+Xqyosbhgo2JoGnp9cie9N5xVILhUamCB
|
||||||
|
9cp+NMyVRpZm+0xXqL+DH60/wtMfqW7mG3qxv0tzI7RuMn5Vm7JN4ts49iIUarb8
|
||||||
|
vGCXnTB+v8QE8GDd6lBq3HJ6DNtuStYtLai4DiwI4qsGF75pSLxWSn03LhIsDOLC
|
||||||
|
AZuS8vTjOPfDuiZASVII91yMb/6AMIJ570J0xUaa88q84lOhvR+uWd1+mfBBH3mF
|
||||||
|
7ykpWUNCOSCyNBlXa+AZC6BEFw4EnHHWQQ0MARy2auBe9YcTWBRuA3Rox8B13lmd
|
||||||
|
sBKHMm5ZREyoEtk5DnkCAwEAAaNqMGgwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM
|
||||||
|
MAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFFy6asLfUTtAaZz1nLcl16XDDcjzMCAG
|
||||||
|
A1UdEQQZMBeCCWxvY2FsaG9zdIcEfwAAAYcEwKgpdjANBgkqhkiG9w0BAQsFAAOC
|
||||||
|
AYEAHgCaKoqdwjyMqVQKCrRCUo7nw7BcM5QtUs/CtRxG4PWWk15PaMDBlDRISng6
|
||||||
|
+hEDI1ZhKnN3mpj/+xWY44Aqrj5QOgcIEkYF0Ny2k6pNFFHqd8Ma9T/9Pfdsdy5Q
|
||||||
|
29g2Rh4JW3qqna1yfEnhpudiJGmMcuc4+d4c+xQHLCtBMzURXaYYu6COptAvJf7A
|
||||||
|
JA6eUIf0IULfkyHejPM4NDnOaKd5KqhlofLAYl4bkhDD+d2b91g0rLj0PY5yF+yT
|
||||||
|
6V454hdiiXcOY334Fc1n7RWawlxhmvZOnNgFpHR+eUlfnDyhHR06YN+n2QUCZrtq
|
||||||
|
7bnKmttCi+RHNjDziB/w7Eju2PnLAstkY8i+ovS7+SzKvvKI8HvV4EpRxAntsFFt
|
||||||
|
xje0J0DTfePCZY9WsPJm4KwrV5g8uVsIdY4ayDbrvJJOyzvg+ne4VfdJjABQUgD3
|
||||||
|
J+B9W9dMc/hk9WCopAMoLlitEdGS+PXAiPYSm73aytQqhbAMaxnwkuHKc4uPdFPF
|
||||||
|
CHmn
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
// 基础配置
|
|
||||||
// const BASE_URL = "http://192.168.41.118:9090/api"
|
|
||||||
const BASE_URL = "/api"
|
|
||||||
|
|
||||||
export function useRequest() {
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref<any>(null)
|
|
||||||
|
|
||||||
// 核心请求方法
|
|
||||||
const sendRequest = <T = any>(options: UniApp.RequestOptions): Promise<T> => {
|
|
||||||
loading.value = true
|
|
||||||
error.value = null
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// 处理URL
|
|
||||||
const url = options.url.startsWith('http')
|
|
||||||
? options.url
|
|
||||||
: `${BASE_URL}/${options.url.replace(/^\//, '')}`
|
|
||||||
|
|
||||||
// 添加token
|
|
||||||
const token = uni.getStorageSync('token')
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
|
||||||
...options.header
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.request({
|
|
||||||
url,
|
|
||||||
method: options.method || 'GET',
|
|
||||||
data: options.data,
|
|
||||||
header: headers,
|
|
||||||
timeout: options.timeout || 10000,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
||||||
if(res.data.code === 0){
|
|
||||||
resolve(res.data as T)
|
|
||||||
}else if(res.data.code === 600098){
|
|
||||||
// 未登录
|
|
||||||
uni.removeStorageSync('token')
|
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/login/index'
|
|
||||||
})
|
|
||||||
}else {
|
|
||||||
const err = new Error(res.data?.msg || '请求失败')
|
|
||||||
error.value = err
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const err = new Error(res.data?.msg || '请求失败')
|
|
||||||
error.value = err
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
error.value = err
|
|
||||||
reject(err)
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET请求
|
|
||||||
const get = <T = any>(url: string, params?: any) => {
|
|
||||||
return sendRequest<T>({
|
|
||||||
url,
|
|
||||||
method: 'GET',
|
|
||||||
data: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST请求
|
|
||||||
const post = <T = any>(url: string, data?: any) => {
|
|
||||||
return sendRequest<T>({
|
|
||||||
url,
|
|
||||||
method: 'POST',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
request: sendRequest,
|
|
||||||
get,
|
|
||||||
post
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
export default {
|
||||||
|
// 初始化微信配置
|
||||||
|
initWechatConfig: function(config) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
jWeixin.config({
|
||||||
|
debug: false, // 开启调试模式
|
||||||
|
appId: config.appId,
|
||||||
|
timestamp: config.timestamp,
|
||||||
|
nonceStr: config.nonceStr,
|
||||||
|
signature: config.signature,
|
||||||
|
jsApiList: ['scanQRCode'] // 需要使用的JS接口列表
|
||||||
|
});
|
||||||
|
|
||||||
|
jWeixin.ready(() => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
jWeixin.error(err => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 调用微信扫一扫
|
||||||
|
scanQRCode: function() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
jWeixin.scanQRCode({
|
||||||
|
needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果
|
||||||
|
scanType: ["qrCode","barCode"], // 可以指定扫二维码还是一维码,默认二者都有
|
||||||
|
success: (res) => {
|
||||||
|
// 当needResult 为 1 时,扫码返回的结果
|
||||||
|
const result = res.resultStr;
|
||||||
|
resolve(result);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,9 @@ import uni from '@dcloudio/vite-plugin-uni'
|
|||||||
import AutoImport from 'unplugin-auto-import/vite';
|
import AutoImport from 'unplugin-auto-import/vite';
|
||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
import { TDesignResolver } from 'unplugin-vue-components/resolvers';
|
import { TDesignResolver } from 'unplugin-vue-components/resolvers';
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -27,9 +28,13 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
|
https: {
|
||||||
|
cert: fs.readFileSync(path.join(__dirname, 'src/ssl/localhost+2.pem')),
|
||||||
|
key: fs.readFileSync(path.join(__dirname, 'src/ssl/localhost+2-key.pem'))
|
||||||
|
},
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://192.168.0.107:9090',
|
target: 'http://192.168.41.118:9090',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '')
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user