14 changed files with 1217 additions and 58 deletions
@ -0,0 +1,797 @@ |
|||||
|
<!--合同签署表单页面--> |
||||
|
<template> |
||||
|
<view class="main_box"> |
||||
|
<!-- 自定义导航栏 --> |
||||
|
<view class="navbar_section"> |
||||
|
<view class="navbar_back" @click="goBack"> |
||||
|
<text class="back_icon">‹</text> |
||||
|
</view> |
||||
|
<view class="navbar_title">合同签署</view> |
||||
|
<view class="navbar_action"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 合同信息 --> |
||||
|
<view class="contract_info_section" v-if="contractInfo"> |
||||
|
<view class="info_header"> |
||||
|
<view class="contract_name">{{ contractInfo.contract_name }}</view> |
||||
|
<view class="contract_type">{{ contractInfo.contract_type }}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 合同内容 --> |
||||
|
<view class="contract_content_section" v-if="contractContent"> |
||||
|
<view class="content_header"> |
||||
|
<view class="content_title">合同内容</view> |
||||
|
<view class="content_expand_btn" @click="toggleContentExpand"> |
||||
|
{{ contentExpanded ? '收起' : '展开' }} |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="content_body" :class="{ expanded: contentExpanded }"> |
||||
|
<text class="content_text">{{ renderContractContent }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 表单内容 --> |
||||
|
<view class="form_section" v-if="!loading"> |
||||
|
<view class="form_title">请填写以下信息</view> |
||||
|
<view class="form_content"> |
||||
|
<view |
||||
|
v-for="(field, index) in formFields" |
||||
|
:key="index" |
||||
|
class="form_field" |
||||
|
> |
||||
|
<view class="field_label"> |
||||
|
{{ field.name }} |
||||
|
<text v-if="field.is_required" class="required_mark">*</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 手写签名字段 --> |
||||
|
<view v-if="field.data_type === 'signature'" class="field_signature"> |
||||
|
<view |
||||
|
class="signature_button" |
||||
|
@click="goToSignature(field)" |
||||
|
:class="formData[field.placeholder] ? 'signed' : ''" |
||||
|
> |
||||
|
<text class="signature_icon">✒️</text> |
||||
|
<text class="signature_text"> |
||||
|
{{ formData[field.placeholder] ? '已签名' : '手写签名' }} |
||||
|
</text> |
||||
|
</view> |
||||
|
<view v-if="formData[field.placeholder]" class="signature_preview"> |
||||
|
<image :src="formData[field.placeholder]" mode="aspectFit" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 签名图片上传字段 --> |
||||
|
<view v-else-if="field.data_type === 'sign_img'" class="field_sign_img"> |
||||
|
<view |
||||
|
class="sign_img_button" |
||||
|
@click="chooseSignImage(field)" |
||||
|
:class="formData[field.placeholder] ? 'uploaded' : ''" |
||||
|
> |
||||
|
<text class="sign_img_icon">📷</text> |
||||
|
<text class="sign_img_text"> |
||||
|
{{ formData[field.placeholder] ? '已上传' : '上传签名图片' }} |
||||
|
</text> |
||||
|
</view> |
||||
|
<view v-if="formData[field.placeholder]" class="sign_img_preview"> |
||||
|
<image :src="formData[field.placeholder]" mode="aspectFit" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文本输入框 --> |
||||
|
<view v-else-if="field.field_type === 'text'" class="field_input"> |
||||
|
<input |
||||
|
type="text" |
||||
|
:placeholder="field.placeholder || '请输入' + field.name" |
||||
|
v-model="formData[field.placeholder]" |
||||
|
:disabled="field.data_type !== 'user_input'" |
||||
|
:class="field.data_type !== 'user_input' ? 'disabled' : ''" |
||||
|
/> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 日期选择器 --> |
||||
|
<view v-else-if="field.field_type === 'date'" class="field_input"> |
||||
|
<picker |
||||
|
mode="date" |
||||
|
:value="formData[field.placeholder]" |
||||
|
@change="handleDateChange(field.placeholder, $event)" |
||||
|
:disabled="field.data_type !== 'user_input'" |
||||
|
> |
||||
|
<input |
||||
|
type="text" |
||||
|
:placeholder="field.placeholder || '请选择' + field.name" |
||||
|
:value="formData[field.placeholder]" |
||||
|
:disabled="field.data_type !== 'user_input'" |
||||
|
:class="field.data_type !== 'user_input' ? 'disabled' : ''" |
||||
|
readonly |
||||
|
/> |
||||
|
</picker> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 数字输入框 --> |
||||
|
<view v-else-if="field.field_type === 'number'" class="field_input"> |
||||
|
<input |
||||
|
type="number" |
||||
|
:placeholder="field.placeholder || '请输入' + field.name" |
||||
|
v-model="formData[field.placeholder]" |
||||
|
:disabled="field.data_type !== 'user_input'" |
||||
|
:class="field.data_type !== 'user_input' ? 'disabled' : ''" |
||||
|
/> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文本域 --> |
||||
|
<view v-else-if="field.field_type === 'textarea'" class="field_textarea"> |
||||
|
<textarea |
||||
|
:placeholder="field.placeholder || '请输入' + field.name" |
||||
|
v-model="formData[field.placeholder]" |
||||
|
:disabled="field.data_type !== 'user_input'" |
||||
|
:class="field.data_type !== 'user_input' ? 'disabled' : ''" |
||||
|
></textarea> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提示信息 --> |
||||
|
<view v-if="field.data_type !== 'user_input' && field.data_type !== 'signature' && field.data_type !== 'sign_img'" class="field_hint"> |
||||
|
{{ getFieldHint(field) }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 加载状态 --> |
||||
|
<view v-if="loading" class="loading_section"> |
||||
|
<view class="loading_spinner"></view> |
||||
|
<view class="loading_text">加载表单配置中...</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 提交按钮 --> |
||||
|
<view class="submit_section" v-if="!loading"> |
||||
|
<fui-button |
||||
|
type="warning" |
||||
|
btn-size="medium" |
||||
|
width="100%" |
||||
|
:loading="submitting" |
||||
|
@click="submitContract" |
||||
|
> |
||||
|
{{ submitting ? '提交中...' : '提交签署' }} |
||||
|
</fui-button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import apiRoute from '@/api/apiRoute.js' |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
contractId: 0, |
||||
|
studentId: 0, |
||||
|
contractName: '', |
||||
|
contractInfo: null, |
||||
|
contractContent: '', |
||||
|
contentExpanded: false, |
||||
|
formFields: [], |
||||
|
formData: {}, |
||||
|
loading: true, |
||||
|
submitting: false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
computed: { |
||||
|
// 渲染合同内容,替换占位符为实际值 |
||||
|
renderContractContent() { |
||||
|
if (!this.contractContent) return '' |
||||
|
|
||||
|
let content = this.contractContent |
||||
|
// 将表单数据替换到合同内容中的占位符 |
||||
|
Object.keys(this.formData).forEach(key => { |
||||
|
const value = this.formData[key] || '' |
||||
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, 'g') |
||||
|
content = content.replace(regex, value) |
||||
|
}) |
||||
|
|
||||
|
return content |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onLoad(options) { |
||||
|
this.contractId = parseInt(options.contract_id) || 0 |
||||
|
this.studentId = parseInt(options.student_id) || 0 |
||||
|
this.contractName = decodeURIComponent(options.contract_name || '') |
||||
|
|
||||
|
if (this.contractId && this.studentId) { |
||||
|
this.loadSignForm() |
||||
|
} else { |
||||
|
uni.showToast({ |
||||
|
title: '参数错误', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
uni.navigateBack() |
||||
|
}, 1500) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onShow() { |
||||
|
// 从签名页面返回时,检查是否有新的签名数据 |
||||
|
const pages = getCurrentPages() |
||||
|
const currentPage = pages[pages.length - 1] |
||||
|
if (currentPage.data && currentPage.data.newSignature) { |
||||
|
const { fieldPlaceholder, signatureData } = currentPage.data.newSignature |
||||
|
this.formData[fieldPlaceholder] = signatureData |
||||
|
// 清除临时数据 |
||||
|
delete currentPage.data.newSignature |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
goBack() { |
||||
|
uni.navigateBack() |
||||
|
}, |
||||
|
|
||||
|
async loadSignForm() { |
||||
|
this.loading = true |
||||
|
try { |
||||
|
console.log('加载签署表单:', { contractId: this.contractId, studentId: this.studentId }) |
||||
|
|
||||
|
// 获取签署表单配置 |
||||
|
const response = await apiRoute.getStudentContractSignForm({ |
||||
|
contract_id: this.contractId, |
||||
|
student_id: this.studentId |
||||
|
}) |
||||
|
|
||||
|
if (response.code === 1) { |
||||
|
const data = response.data |
||||
|
this.contractInfo = { |
||||
|
contract_name: data.contract_name, |
||||
|
contract_type: data.contract_type |
||||
|
} |
||||
|
this.contractContent = data.contract_content || '' |
||||
|
this.formFields = data.form_fields || [] |
||||
|
|
||||
|
// 初始化表单数据 |
||||
|
this.initFormData() |
||||
|
|
||||
|
console.log('表单配置加载成功:', this.formFields) |
||||
|
} else { |
||||
|
uni.showToast({ |
||||
|
title: response.msg || '获取表单配置失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
uni.navigateBack() |
||||
|
}, 1500) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('获取表单配置失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '获取表单配置失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
setTimeout(() => { |
||||
|
uni.navigateBack() |
||||
|
}, 1500) |
||||
|
} finally { |
||||
|
this.loading = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
initFormData() { |
||||
|
const data = {} |
||||
|
this.formFields.forEach(field => { |
||||
|
const key = field.placeholder || field.name |
||||
|
// 根据数据类型设置初始值 |
||||
|
if (field.data_type === 'database' || field.data_type === 'system') { |
||||
|
data[key] = field.default_value || '' |
||||
|
} else { |
||||
|
data[key] = field.default_value || '' |
||||
|
} |
||||
|
}) |
||||
|
this.formData = data |
||||
|
}, |
||||
|
|
||||
|
handleDateChange(fieldKey, event) { |
||||
|
this.formData[fieldKey] = event.detail.value |
||||
|
}, |
||||
|
|
||||
|
toggleContentExpand() { |
||||
|
this.contentExpanded = !this.contentExpanded |
||||
|
}, |
||||
|
|
||||
|
goToSignature(field) { |
||||
|
// 跳转到签名页面 |
||||
|
const fieldPlaceholder = field.placeholder || field.name |
||||
|
uni.navigateTo({ |
||||
|
url: `/pages-common/contract/contract_sign?field_name=${encodeURIComponent(field.name)}&field_placeholder=${encodeURIComponent(fieldPlaceholder)}&contract_id=${this.contractId}` |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
chooseSignImage(field) { |
||||
|
// 选择签名图片 |
||||
|
const fieldPlaceholder = field.placeholder || field.name |
||||
|
uni.chooseImage({ |
||||
|
count: 1, |
||||
|
sizeType: ['compressed'], |
||||
|
sourceType: ['album', 'camera'], |
||||
|
success: (res) => { |
||||
|
const tempFilePath = res.tempFilePaths[0] |
||||
|
|
||||
|
// 上传图片到服务器 |
||||
|
this.uploadSignImage(tempFilePath, fieldPlaceholder) |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
console.error('选择图片失败:', err) |
||||
|
uni.showToast({ |
||||
|
title: '选择图片失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
async uploadSignImage(tempFilePath, fieldPlaceholder) { |
||||
|
// 显示上传进度 |
||||
|
uni.showLoading({ |
||||
|
title: '上传中...', |
||||
|
mask: true |
||||
|
}) |
||||
|
|
||||
|
try { |
||||
|
// 上传图片到服务器 |
||||
|
const uploadResult = await new Promise((resolve, reject) => { |
||||
|
uni.uploadFile({ |
||||
|
url: 'http://localhost:20080/api/upload/image', |
||||
|
filePath: tempFilePath, |
||||
|
name: 'image', |
||||
|
header: { |
||||
|
'Authorization': uni.getStorageSync('token') || '' |
||||
|
}, |
||||
|
success: (uploadRes) => { |
||||
|
try { |
||||
|
const result = JSON.parse(uploadRes.data) |
||||
|
if (result.code === 1) { |
||||
|
resolve(result.data) |
||||
|
} else { |
||||
|
reject(new Error(result.msg || '上传失败')) |
||||
|
} |
||||
|
} catch (e) { |
||||
|
reject(new Error('上传响应解析失败')) |
||||
|
} |
||||
|
}, |
||||
|
fail: (err) => { |
||||
|
reject(new Error('上传请求失败')) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
|
||||
|
// 上传成功,使用服务器返回的URL |
||||
|
this.formData[fieldPlaceholder] = uploadResult.url || uploadResult.path || tempFilePath |
||||
|
|
||||
|
uni.hideLoading() |
||||
|
uni.showToast({ |
||||
|
title: '上传成功', |
||||
|
icon: 'success' |
||||
|
}) |
||||
|
|
||||
|
console.log('签名图片上传成功:', uploadResult) |
||||
|
|
||||
|
} catch (error) { |
||||
|
console.error('签名图片上传失败:', error) |
||||
|
|
||||
|
// 上传失败时使用本地临时路径(仅供预览) |
||||
|
this.formData[fieldPlaceholder] = tempFilePath |
||||
|
|
||||
|
uni.hideLoading() |
||||
|
uni.showToast({ |
||||
|
title: '上传失败,使用本地图片', |
||||
|
icon: 'none', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getFieldHint(field) { |
||||
|
switch (field.data_type) { |
||||
|
case 'database': |
||||
|
return '此信息将从系统数据库自动获取' |
||||
|
case 'system': |
||||
|
return '此信息将由系统自动生成' |
||||
|
default: |
||||
|
return '' |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
validateForm() { |
||||
|
// 验证必填字段 |
||||
|
for (const field of this.formFields) { |
||||
|
if (field.is_required) { |
||||
|
const key = field.placeholder || field.name |
||||
|
const value = this.formData[key] |
||||
|
if (!value || value.toString().trim() === '') { |
||||
|
uni.showToast({ |
||||
|
title: `请填写${field.name}`, |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
}, |
||||
|
|
||||
|
async submitContract() { |
||||
|
if (!this.validateForm()) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.submitting = true |
||||
|
try { |
||||
|
console.log('提交合同签署:', { |
||||
|
contractId: this.contractId, |
||||
|
studentId: this.studentId, |
||||
|
formData: this.formData |
||||
|
}) |
||||
|
|
||||
|
// 提交签署数据 |
||||
|
const response = await apiRoute.signStudentContract({ |
||||
|
contract_id: this.contractId, |
||||
|
student_id: this.studentId, |
||||
|
form_data: this.formData |
||||
|
}) |
||||
|
|
||||
|
if (response.code === 1) { |
||||
|
uni.showToast({ |
||||
|
title: '合同签署成功', |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
// 返回合同列表并刷新 |
||||
|
uni.navigateBack() |
||||
|
}, 2000) |
||||
|
} else { |
||||
|
uni.showToast({ |
||||
|
title: response.msg || '合同签署失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('合同签署失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '合同签署失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} finally { |
||||
|
this.submitting = false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.main_box { |
||||
|
background: #f8f9fa; |
||||
|
min-height: 100vh; |
||||
|
padding-bottom: 120rpx; |
||||
|
} |
||||
|
|
||||
|
// 自定义导航栏 |
||||
|
.navbar_section { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
background: #29D3B4; |
||||
|
padding: 40rpx 32rpx 20rpx; |
||||
|
|
||||
|
// 小程序端适配状态栏 |
||||
|
// #ifdef MP-WEIXIN |
||||
|
padding-top: 80rpx; |
||||
|
// #endif |
||||
|
|
||||
|
.navbar_back { |
||||
|
width: 60rpx; |
||||
|
|
||||
|
.back_icon { |
||||
|
color: #fff; |
||||
|
font-size: 40rpx; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.navbar_title { |
||||
|
color: #fff; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.navbar_action { |
||||
|
width: 60rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 合同信息 |
||||
|
.contract_info_section { |
||||
|
background: #fff; |
||||
|
margin: 20rpx; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 24rpx; |
||||
|
|
||||
|
.info_header { |
||||
|
.contract_name { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.contract_type { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 合同内容区域 |
||||
|
.contract_content_section { |
||||
|
background: #fff; |
||||
|
margin: 20rpx; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 24rpx; |
||||
|
|
||||
|
.content_header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16rpx; |
||||
|
|
||||
|
.content_title { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.content_expand_btn { |
||||
|
font-size: 24rpx; |
||||
|
color: #29D3B4; |
||||
|
padding: 8rpx 16rpx; |
||||
|
border: 1rpx solid #29D3B4; |
||||
|
border-radius: 8rpx; |
||||
|
background: rgba(41, 211, 180, 0.05); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.content_body { |
||||
|
max-height: 200rpx; |
||||
|
overflow: hidden; |
||||
|
transition: max-height 0.3s ease; |
||||
|
|
||||
|
&.expanded { |
||||
|
max-height: none; |
||||
|
} |
||||
|
|
||||
|
.content_text { |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.6; |
||||
|
word-break: break-all; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 表单区域 |
||||
|
.form_section { |
||||
|
margin: 20rpx; |
||||
|
|
||||
|
.form_title { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 20rpx; |
||||
|
padding: 0 8rpx; |
||||
|
} |
||||
|
|
||||
|
.form_content { |
||||
|
.form_field { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 24rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
|
||||
|
.field_label { |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
margin-bottom: 16rpx; |
||||
|
font-weight: 500; |
||||
|
|
||||
|
.required_mark { |
||||
|
color: #e74c3c; |
||||
|
margin-left: 4rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.field_input { |
||||
|
input { |
||||
|
width: 100%; |
||||
|
height: 80rpx; |
||||
|
padding: 0 20rpx; |
||||
|
border: 2rpx solid #e0e0e0; |
||||
|
border-radius: 12rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
background: #fff; |
||||
|
|
||||
|
&::placeholder { |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
&.disabled { |
||||
|
background: #f5f5f5; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.field_textarea { |
||||
|
textarea { |
||||
|
width: 100%; |
||||
|
min-height: 120rpx; |
||||
|
padding: 20rpx; |
||||
|
border: 2rpx solid #e0e0e0; |
||||
|
border-radius: 12rpx; |
||||
|
font-size: 28rpx; |
||||
|
color: #333; |
||||
|
background: #fff; |
||||
|
|
||||
|
&::placeholder { |
||||
|
color: #999; |
||||
|
} |
||||
|
|
||||
|
&.disabled { |
||||
|
background: #f5f5f5; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.field_signature { |
||||
|
.signature_button { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 80rpx; |
||||
|
border: 2rpx dashed #29D3B4; |
||||
|
border-radius: 12rpx; |
||||
|
background: rgba(41, 211, 180, 0.05); |
||||
|
|
||||
|
&.signed { |
||||
|
border-color: #27ae60; |
||||
|
background: rgba(39, 174, 96, 0.05); |
||||
|
} |
||||
|
|
||||
|
.signature_icon { |
||||
|
font-size: 32rpx; |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.signature_text { |
||||
|
font-size: 28rpx; |
||||
|
color: #29D3B4; |
||||
|
|
||||
|
.signed & { |
||||
|
color: #27ae60; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.signature_preview { |
||||
|
margin-top: 16rpx; |
||||
|
|
||||
|
image { |
||||
|
width: 100%; |
||||
|
height: 120rpx; |
||||
|
border-radius: 8rpx; |
||||
|
border: 1rpx solid #e0e0e0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.field_sign_img { |
||||
|
.sign_img_button { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 80rpx; |
||||
|
border: 2rpx dashed #409eff; |
||||
|
border-radius: 12rpx; |
||||
|
background: rgba(64, 158, 255, 0.05); |
||||
|
|
||||
|
&.uploaded { |
||||
|
border-color: #67c23a; |
||||
|
background: rgba(103, 194, 58, 0.05); |
||||
|
} |
||||
|
|
||||
|
.sign_img_icon { |
||||
|
font-size: 32rpx; |
||||
|
margin-right: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.sign_img_text { |
||||
|
font-size: 28rpx; |
||||
|
color: #409eff; |
||||
|
|
||||
|
.uploaded & { |
||||
|
color: #67c23a; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.sign_img_preview { |
||||
|
margin-top: 16rpx; |
||||
|
|
||||
|
image { |
||||
|
width: 100%; |
||||
|
height: 120rpx; |
||||
|
border-radius: 8rpx; |
||||
|
border: 1rpx solid #e0e0e0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.field_hint { |
||||
|
margin-top: 8rpx; |
||||
|
font-size: 22rpx; |
||||
|
color: #999; |
||||
|
font-style: italic; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 加载状态 |
||||
|
.loading_section { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 120rpx 32rpx; |
||||
|
|
||||
|
.loading_spinner { |
||||
|
width: 60rpx; |
||||
|
height: 60rpx; |
||||
|
border: 4rpx solid #f0f0f0; |
||||
|
border-left: 4rpx solid #29D3B4; |
||||
|
border-radius: 50%; |
||||
|
animation: spin 1s linear infinite; |
||||
|
margin-bottom: 24rpx; |
||||
|
} |
||||
|
|
||||
|
.loading_text { |
||||
|
font-size: 28rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes spin { |
||||
|
0% { transform: rotate(0deg); } |
||||
|
100% { transform: rotate(360deg); } |
||||
|
} |
||||
|
|
||||
|
// 提交按钮 |
||||
|
.submit_section { |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
background: #fff; |
||||
|
padding: 20rpx 32rpx 40rpx; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
|
||||
|
// 小程序端适配底部安全区域 |
||||
|
// #ifdef MP-WEIXIN |
||||
|
padding-bottom: calc(40rpx + env(safe-area-inset-bottom)); |
||||
|
// #endif |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue