123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- <template>
- <view class="photo-container" :class="{'loaded': isPageLoaded}">
- <!-- 标题 -->
- <view class="photo-header">
- <text class="photo-title">拍摄面部照片</text>
- <text class="photo-subtitle">我们将用于身份核验,请正对摄像头</text>
- </view>
-
- <!-- 模式选择 -->
- <!-- <view class="mode-selector">
- <view class="mode-option" :class="{'active': mode === 'photo'}" @click="switchMode('photo')">拍照</view>
- <view class="mode-option" :class="{'active': mode === 'video'}" @click="switchMode('video')">录制视频</view>
- </view> -->
-
- <!-- 照片/视频预览区域 -->
- <view class="photo-preview">
- <!-- 使用camera组件替代静态图片 -->
- <camera v-if="!mediaSource" device-position="front" flash="auto" class="camera"
- :mode="mode" @error="handleCameraError"></camera>
- <image v-else-if="mode === 'photo'" class="preview-image" :src="mediaSource" mode="aspectFit"></image>
- <video v-else-if="mode === 'video'" class="preview-video" :src="mediaSource"
- controls autoplay></video>
- <view class="face-outline"></view>
-
- <!-- 视频录制时间显示 -->
- <view v-if="mode === 'video' && isRecording" class="recording-indicator">
- <view class="recording-dot"></view>
- <text class="recording-time">{{formatTime(recordingTime)}}</text>
- </view>
- </view>
-
- <!-- 操作按钮 -->
- <view v-if="!mediaSource" class="capture-btn-container">
- <button v-if="mode === 'photo'" class="capture-btn" @click="takePhoto">拍照</button>
- <button v-else-if="mode === 'video' && !isRecording" class="capture-btn" @click="startRecording">开始录制</button>
- <button v-else-if="mode === 'video' && isRecording" class="stop-btn" @click="stopRecording">停止录制</button>
- </view>
- <view v-else class="btn-group">
- <button class="retry-btn" @click="retakeMedia">重新{{mode === 'photo' ? '拍照' : '录制'}}</button>
- <button class="start-btn" @click="continueProcess">完成</button>
- </view>
- </view>
- </template>
- <script>
- import { uploadPhoto,fillUserInfo,getUserInfo } from '@/api/user';
- import { apiBaseUrl } from '@/common/config.js';
- export default {
- data() {
- return {
- mediaSource: '', // 拍摄的照片或视频路径
- isCameraReady: false,
- cameraContext: null,
- isPageLoaded: false, // 添加页面加载状态标记
- mode: 'photo', // 默认为拍照模式,可选值:photo, video
- isRecording: false, // 是否正在录制视频
- recordingTime: 0, // 录制时间(秒)
- recordingTimer: null, // 录制计时器
- photoUrl: '', // 存储上传后返回的图片URL
- }
- },
- onReady() {
- // 相机组件初始化完成后创建相机上下文
- this.cameraContext = uni.createCameraContext();
- // 设置页面已加载完成
- setTimeout(() => {
- this.isPageLoaded = true;
- }, 100);
- },
- methods: {
- // 切换拍照/录制模式
- switchMode(newMode) {
- if (this.mediaSource) {
- // 如果已经有拍摄内容,需要先清除
- this.retakeMedia();
- }
- this.mode = newMode;
- },
-
- // 处理相机错误
- handleCameraError(e) {
- console.error('相机错误:', e);
- uni.showToast({
- title: '相机初始化失败,请检查权限设置',
- icon: 'none'
- });
- },
-
- // 拍照方法
- takePhoto() {
- if (!this.cameraContext) {
- uni.showToast({
- title: '相机未就绪',
- icon: 'none'
- });
- return;
- }
-
- uni.showLoading({
- title: '拍照中...'
- });
-
- this.cameraContext.takePhoto({
- quality: 'high',
- success: (res) => {
- this.mediaSource = res.tempImagePath;
- uni.hideLoading();
- },
- fail: (err) => {
- console.error('拍照失败:', err);
- uni.hideLoading();
- uni.showToast({
- title: '拍照失败,请重试',
- icon: 'none'
- });
- }
- });
- },
-
- // 开始录制视频
- startRecording() {
- if (!this.cameraContext) {
- uni.showToast({
- title: '相机未就绪',
- icon: 'none'
- });
- return;
- }
-
- this.isRecording = true;
- this.recordingTime = 0;
-
- // 开始计时
- this.recordingTimer = setInterval(() => {
- this.recordingTime++;
-
- // 限制最长录制时间为60秒
- if (this.recordingTime >= 60) {
- this.stopRecording();
- }
- }, 1000);
-
- this.cameraContext.startRecord({
- success: () => {
- console.log('开始录制视频');
- },
- fail: (err) => {
- console.error('开始录制失败:', err);
- this.isRecording = false;
- clearInterval(this.recordingTimer);
- uni.showToast({
- title: '录制失败,请重试',
- icon: 'none'
- });
- }
- });
- },
-
- // 停止录制视频
- stopRecording() {
- if (!this.isRecording) return;
-
- clearInterval(this.recordingTimer);
- this.isRecording = false;
-
- uni.showLoading({
- title: '处理中...'
- });
-
- this.cameraContext.stopRecord({
- success: (res) => {
- this.mediaSource = res.tempVideoPath;
- console.log(res);
- uni.hideLoading();
- },
- fail: (err) => {
- console.error('停止录制失败:', err);
- uni.hideLoading();
- uni.showToast({
- title: '视频保存失败,请重试',
- icon: 'none'
- });
- }
- });
- },
-
- // 格式化时间显示 (秒 -> MM:SS)
- formatTime(seconds) {
- const minutes = Math.floor(seconds / 60);
- const remainingSeconds = seconds % 60;
- return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
- },
-
- // 重新拍照或录制
- retakeMedia() {
- this.mediaSource = '';
- this.recordingTime = 0;
- },
-
- // 继续流程
- continueProcess() {
- if (!this.mediaSource) {
- uni.showToast({
- title: '请先完成拍照',
- icon: 'none'
- });
- return;
- }
-
- uni.showLoading({
- title: '上传中...'
- });
-
- // 直接上传照片
- this.submitPhotoUrl();
- },
-
- // 上传媒体文件方法 - 不再使用
- uploadMedia(callback) {
- // 获取openid和tenant_id,可以从缓存或全局状态获取
- const openid = JSON.parse(uni.getStorageSync('userInfo')).openid || '';
- const tenant_id =JSON.parse(uni.getStorageSync('userInfo')).tenant_id || 1;
-
- if (!openid || !tenant_id) {
- uni.hideLoading();
- uni.showToast({
- title: '用户信息不完整,请重新登录',
- icon: 'none'
- });
- return;
- }
-
- // 使用uni.uploadFile方式上传图片
- uni.uploadFile({
- url: `${apiBaseUrl}/api/upload/`,
- filePath: this.mediaSource,
- name: 'file',
- formData: {
- openid: openid,
- tenant_id: tenant_id
- },
- success: (uploadRes) => {
- // 解析返回的JSON字符串
- const res = JSON.parse(uploadRes.data);
- if (res.code === 2000) { // 根据实际API响应调整
- // 获取返回的图片URL
- const photoUrl = res.data.url || res.data.photoUrl || '';
- if (callback && typeof callback === 'function') {
- callback(photoUrl);
- }
- } else {
- uni.hideLoading();
- uni.showToast({
- title: res.msg || '照片上传失败',
- icon: 'none'
- });
- }
- },
- fail: (err) => {
- uni.hideLoading();
- console.error('上传失败:', err);
- uni.showToast({
- title: '网络错误,请重试',
- icon: 'none'
- });
- }
- });
- },
-
- // 提交照片URL到指定接口 - 修改为直接上传文件
- submitPhotoUrl() {
- const openid = JSON.parse(uni.getStorageSync('userInfo')).openid || '';
- const tenant_id =JSON.parse(uni.getStorageSync('userInfo')).tenant_id || 1;
-
- if (!this.mediaSource) {
- uni.hideLoading();
- uni.showToast({
- title: '照片信息不完整,请重试',
- icon: 'none'
- });
- return;
- }
- console.log(this.mediaSource);
- // 使用uni.uploadFile直接上传文件,类似interview页面的实现
- uni.uploadFile({
- url: `${apiBaseUrl}/job/upload_posture_photo`,
- filePath: this.mediaSource,
- name: 'photo', // 确保文件参数名称正确
- formData: { // 使用formData传递参数
- 'application_id': uni.getStorageSync('appId'),
- 'tenant_id': tenant_id,
- 'openid': openid,
- 'description': '面部照片'
- },
- success: (uploadRes) => {
- console.log('照片上传成功:', uploadRes);
-
- // 解析返回的JSON字符串
- let result;
- try {
- if (typeof uploadRes.data === 'string') {
- result = JSON.parse(uploadRes.data);
- } else {
- result = uploadRes.data;
- }
- console.log('解析后的上传结果:', result);
-
- // 保存返回的URL
- if (result.data && result.data.url) {
- this.photoUrl = result.data.url;
- }
-
- // 更新本地用户信息
- // this.updateLocalUserInfo();
-
- uni.showToast({
- title: '照片上传成功',
- icon: 'success'
- });
-
- // 上传成功后跳转到下一页
- setTimeout(() => {
- uni.navigateTo({
- url: '/pages/identity-verify/identity-verify',
- fail: (err) => {
- console.error('页面跳转失败:', err);
- uni.showToast({
- title: '页面跳转失败',
- icon: 'none'
- });
- }
- });
- }, 1500);
- } catch (e) {
- console.error('解析上传结果失败:', e);
- uni.showToast({
- title: '处理响应失败',
- icon: 'none'
- });
- }
- },
- fail: (err) => {
- console.error('照片上传失败:', err);
- uni.showToast({
- title: '照片上传失败,请重试',
- icon: 'none'
- });
- },
- complete: () => {
- uni.hideLoading();
- }
- });
- },
- updateLocalUserInfo() {
- getUserInfo()
- .then(res => {
- if (res.code === 200 && res.data) {
- let userInfo = {};
- try {
- userInfo = JSON.parse(uni.getStorageSync('userInfo') || '{}');
- } catch (e) {
- console.error('解析本地存储用户信息失败:', e);
- userInfo = {};
- }
- const updatedUserInfo = {
- ...userInfo,
- ...res.data
- };
- uni.setStorageSync('userInfo', JSON.stringify(updatedUserInfo));
- }
- })
- .catch(err => {
- console.error('更新本地用户信息失败:', err);
- });
- }
- }
- }
- </script>
- <style>
- .photo-container {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
- background-color: #f5f7fa;
- padding: 30rpx;
- opacity: 0; /* 初始设置为不可见 */
- transition: opacity 0.3s ease; /* 添加过渡效果 */
- }
- .photo-container.loaded {
- opacity: 1; /* 加载完成后显示 */
- }
- .photo-header {
- display: flex;
- flex-direction: column;
- align-items: center;
- margin-bottom: 80rpx;
- }
- .photo-title {
- font-size: 36rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 10rpx;
- }
- .photo-subtitle {
- font-size: 28rpx;
- color: #666;
- }
- /* 模式选择器样式 */
- .mode-selector {
- display: flex;
- justify-content: center;
- margin-bottom: 30rpx;
- background-color: #eaeaea;
- border-radius: 40rpx;
- padding: 6rpx;
- width: 80%;
- align-self: center;
- }
- .mode-option {
- flex: 1;
- text-align: center;
- padding: 16rpx 0;
- font-size: 28rpx;
- color: #666;
- border-radius: 36rpx;
- transition: all 0.3s;
- }
- .mode-option.active {
- background-color: #0039b3;
- color: #fff;
- }
- .photo-preview {
- position: relative;
- width: 100%;
- height: 900rpx;
- background-color: #000;
- border-radius: 20rpx;
- overflow: hidden;
- margin-bottom: 40rpx;
- display: flex;
- justify-content: center;
- align-items: center;
- }
- .preview-image, .preview-video {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .face-outline {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 500rpx;
- height: 600rpx;
- border: 4rpx dashed #fff;
- border-radius: 200rpx;
- pointer-events: none;
- }
- .camera {
- width: 100%;
- height: 100%;
- }
- /* 录制指示器 */
- .recording-indicator {
- position: absolute;
- top: 30rpx;
- right: 30rpx;
- display: flex;
- align-items: center;
- background-color: rgba(0, 0, 0, 0.5);
- padding: 10rpx 20rpx;
- border-radius: 30rpx;
- }
- .recording-dot {
- width: 20rpx;
- height: 20rpx;
- background-color: #ff0000;
- border-radius: 50%;
- margin-right: 10rpx;
- animation: blink 1s infinite;
- }
- .recording-time {
- color: #fff;
- font-size: 28rpx;
- }
- @keyframes blink {
- 0% { opacity: 1; }
- 50% { opacity: 0.3; }
- 100% { opacity: 1; }
- }
- .capture-btn-container {
- width: 100%;
- display: flex;
- justify-content: center;
- margin-top: 60rpx;
- }
- .capture-btn {
- width: 100%;
- height: 90rpx;
- line-height: 90rpx;
- background-color: #0039b3;
- color: #fff;
- border-radius: 45rpx;
- font-size: 32rpx;
- }
- .stop-btn {
- width: 100%;
- height: 90rpx;
- line-height: 90rpx;
- background-color: #e74c3c;
- color: #fff;
- border-radius: 45rpx;
- font-size: 32rpx;
- }
- .btn-group {
- display: flex;
- justify-content: space-between;
- width: 100%;
- margin-top: 60rpx;
- }
- .retry-btn {
- width: 45%;
- height: 90rpx;
- line-height: 90rpx;
- background-color: #fff;
- color: #0039b3;
- border: 1px solid #0039b3;
- border-radius: 45rpx;
- font-size: 32rpx;
- }
- .start-btn {
- width: 45%;
- height: 90rpx;
- line-height: 90rpx;
- background-color: #0039b3;
- color: #fff;
- border-radius: 45rpx;
- font-size: 32rpx;
- }
- /* 添加手部轮廓样式 */
- .face-outline.hand-outline {
- width: 500rpx;
- height: 300rpx;
- border-radius: 30rpx;
- }
- </style>
|