Files
guoju0808/ry-h5/pages/signin/index.uvue
T
郭庆泰andClaude 904e28710f feat: 生产部署 + 会议全链路 (执行方分配/费用结算/签到脱敏/H5相机)
- 生产 nginx 三路径: ry-vue3 /hg/, ry-h5 /camera/, API /hg-api, .env 分环境
- 签约链接/摄像头 base-url 走 yml, 不再硬编码 /hg 与 localhost
- 新增 biz_project_executor_assign (镜像 sponsor_assign) 执行方项目级分配
- 会议费用后台汇总 FeeCalcScheduler + 结算回写项目金额 + 状态流转调度
- 签到表拍照高斯模糊脱敏 extra_oss_url, 新增 PosterService/StageDeriver
- 清理 biz_support_intent 旧表 (6 Java/XML 删除)
- ry-h5 相机黑屏修复: 显式 video.play() + 动态 apiBase 上传
- 资源: simhei 字体 / logo.png / qrcode_1.png / favicon.ico

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 01:00:09 +08:00

300 lines
5.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="camera-page">
<!-- 顶部导航 -->
<view class="topbar">
<view class="back-btn" @click="goBack">
<text class="back-icon"></text>
</view>
<text class="topbar-title">签到表取景 (A4)</text>
<view class="placeholder" />
</view>
<!-- 视频 + overlay -->
<view class="viewport">
<video
id="signin-video"
class="video"
autoplay
muted
playsinline
/>
<image
class="frame signin-frame"
src="/static/overlay/signin.svg"
mode="widthFix"
/>
<view v-if="errorMsg" class="error-layer">
<text class="error-text">{{ errorMsg }}</text>
<button class="retry-btn" @click="startCamera">重试</button>
</view>
</view>
<!-- 底部控制条 -->
<view class="controls">
<button class="ctrl-btn" @click="flipCamera">
<text class="ctrl-icon">⟲</text>
<text class="ctrl-label">翻转</text>
</button>
<view class="shutter" @click="takePhoto">
<view class="shutter-inner" />
</view>
<view class="ctrl-btn-placeholder" />
</view>
<!-- 拍照预览层 -->
<view v-if="captured" class="preview">
<image
class="preview-img"
:src="captured"
mode="aspectFit"
/>
<view class="preview-actions">
<button class="preview-btn preview-btn-cancel" @click="retake">
<text>重拍</text>
</button>
<button class="preview-btn preview-btn-confirm" @click="confirm">
<text>使用</text>
</button>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { useCamera } from '@/composables/useCamera'
const videoId = 'signin-video'
// A4 纸 20%~50% 区间 (签到人手机号/身份证号所在的姓名栏) 做高斯模糊脱敏
const { captured, errorMsg, startCamera, flipCamera, takePhoto, retake, confirm } =
useCamera(videoId, { start: 0.2, end: 0.5, radius: 20 })
function goBack() {
uni.navigateBack()
}
</script>
<style>
.camera-page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
display: flex;
flex-direction: column;
overflow: hidden;
z-index: 3;
}
/* ========== 顶部导航 ========== */
.topbar {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 20rpx 30rpx;
padding-top: calc(20rpx + env(safe-area-inset-top));
background: rgba(0, 0, 0, 0.7);
}
.back-btn {
width: 80rpx;
height: 80rpx;
display: flex;
justify-content: center;
align-items: center;
}
.back-icon {
color: #fff;
font-size: 60rpx;
font-weight: bold;
}
.topbar-title {
color: #fff;
font-size: 32rpx;
font-weight: bold;
}
.placeholder {
width: 80rpx;
}
/* ========== 视频取景区 ========== */
.viewport {
position: relative;
flex: 1;
overflow: hidden;
background: transparent;
display: flex;
justify-content: center;
align-items: center;
}
.video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 0;
}
.frame {
position: relative;
z-index: 1;
pointer-events: none;
}
/* A4 框: 宽度 90% 视口,SVG 内置 1:√2 比例 */
.signin-frame {
width: 90%;
height: auto;
}
/* ========== 错误层 ========== */
.error-layer {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx;
background: rgba(0, 0, 0, 0.75);
border-radius: 16rpx;
max-width: 80%;
z-index: 2;
}
.error-text {
color: #fff;
font-size: 28rpx;
text-align: center;
margin-bottom: 24rpx;
line-height: 1.5;
}
.retry-btn {
padding: 12rpx 48rpx;
background: #FFD600;
color: #000;
border-radius: 32rpx;
font-size: 26rpx;
font-weight: bold;
}
/* ========== 底部控制 ========== */
.controls {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
padding: 40rpx 60rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
background: rgba(0, 0, 0, 0.7);
}
.ctrl-btn {
display: flex;
flex-direction: column;
align-items: center;
background: transparent;
min-width: 120rpx;
}
.ctrl-icon {
color: #fff;
font-size: 40rpx;
line-height: 1;
margin-bottom: 8rpx;
}
.ctrl-label {
color: #ccc;
font-size: 22rpx;
}
.ctrl-btn-placeholder {
min-width: 120rpx;
}
.shutter {
width: 144rpx;
height: 144rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.25);
border: 6rpx solid #fff;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 0 0 4rpx rgba(0, 0, 0, 0.3);
}
.shutter:active {
background: rgba(255, 255, 255, 0.4);
transform: scale(0.95);
}
.shutter-inner {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
background: #fff;
}
/* ========== 预览层 ========== */
.preview {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
}
.preview-img {
width: 100%;
flex: 1;
}
.preview-actions {
display: flex;
flex-direction: row;
justify-content: space-around;
width: 100%;
padding: 40rpx 60rpx;
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
}
.preview-btn {
flex: 1;
margin: 0 20rpx;
padding: 24rpx 0;
border-radius: 48rpx;
font-size: 30rpx;
font-weight: bold;
}
.preview-btn-cancel {
background: rgba(255, 255, 255, 0.2);
color: #fff;
}
.preview-btn-confirm {
background: #FFD600;
color: #000;
}
</style>