13 changed files with 1331 additions and 162 deletions
@ -0,0 +1,194 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\api\controller\student; |
||||
|
|
||||
|
use core\base\BaseApiController; |
||||
|
use app\service\api\student\ContractService; |
||||
|
|
||||
|
/** |
||||
|
* 学员合同管理控制器 |
||||
|
*/ |
||||
|
class StudentContract extends BaseApiController |
||||
|
{ |
||||
|
/** |
||||
|
* 获取学员合同列表 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function lists() |
||||
|
{ |
||||
|
$data = $this->request->params([ |
||||
|
['student_id', 0], |
||||
|
['status', ''], |
||||
|
['page', 1], |
||||
|
['limit', 10] |
||||
|
]); |
||||
|
|
||||
|
if (empty($data['student_id'])) { |
||||
|
return fail('学员ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
return success((new ContractService())->getContractList($data)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取合同详情 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function info() |
||||
|
{ |
||||
|
$data = $this->request->params([ |
||||
|
['contract_id', 0], |
||||
|
['student_id', 0] |
||||
|
]); |
||||
|
|
||||
|
if (empty($data['contract_id']) || empty($data['student_id'])) { |
||||
|
return fail('参数错误'); |
||||
|
} |
||||
|
|
||||
|
return success((new ContractService())->getContractDetail($data['contract_id'], $data['student_id'])); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取合同签署表单配置 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function getSignForm() |
||||
|
{ |
||||
|
$data = $this->request->params([ |
||||
|
['contract_id', 0], |
||||
|
['student_id', 0] |
||||
|
]); |
||||
|
|
||||
|
if (empty($data['contract_id']) || empty($data['student_id'])) { |
||||
|
return fail('参数错误'); |
||||
|
} |
||||
|
|
||||
|
return success((new ContractService())->getSignForm($data['contract_id'], $data['student_id'])); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 提交合同签署 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function signContract() |
||||
|
{ |
||||
|
$data = $this->request->params([ |
||||
|
['contract_id', 0], |
||||
|
['student_id', 0], |
||||
|
['form_data', []], |
||||
|
['signature_image', ''] |
||||
|
]); |
||||
|
|
||||
|
if (empty($data['contract_id']) || empty($data['student_id'])) { |
||||
|
return fail('参数错误'); |
||||
|
} |
||||
|
|
||||
|
if (empty($data['form_data']) && empty($data['signature_image'])) { |
||||
|
return fail('请填写必要信息或提供签名'); |
||||
|
} |
||||
|
|
||||
|
return success((new ContractService())->signContract($data)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下载合同文件 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function downloadContract() |
||||
|
{ |
||||
|
$data = $this->request->params([ |
||||
|
['contract_id', 0], |
||||
|
['student_id', 0] |
||||
|
]); |
||||
|
|
||||
|
if (empty($data['contract_id']) || empty($data['student_id'])) { |
||||
|
return fail('参数错误'); |
||||
|
} |
||||
|
|
||||
|
return success((new ContractService())->downloadContract($data['contract_id'], $data['student_id'])); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取学员基本信息 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function getStudentInfo() |
||||
|
{ |
||||
|
$data = $this->request->params([ |
||||
|
['student_id', 0] |
||||
|
]); |
||||
|
|
||||
|
if (empty($data['student_id'])) { |
||||
|
return fail('学员ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
return success((new ContractService())->getStudentInfo($data['student_id'])); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 直接下载合同文件 |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function downloadFile() |
||||
|
{ |
||||
|
$data = $this->request->params([ |
||||
|
['contract_id', 0], |
||||
|
['student_id', 0] |
||||
|
]); |
||||
|
|
||||
|
if (empty($data['contract_id']) || empty($data['student_id'])) { |
||||
|
return fail('参数错误'); |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$fileInfo = (new ContractService())->downloadContract($data['contract_id'], $data['student_id']); |
||||
|
|
||||
|
// 构建文件的实际路径 |
||||
|
$filePath = ''; |
||||
|
if ($fileInfo['file_type'] === 'signed') { |
||||
|
// 已签署文档路径 |
||||
|
$contractSign = \think\facade\Db::table('school_contract_sign') |
||||
|
->where([ |
||||
|
['contract_id', '=', $data['contract_id']], |
||||
|
['student_id', '=', $data['student_id']], |
||||
|
['deleted_at', '=', 0] |
||||
|
]) |
||||
|
->find(); |
||||
|
$filePath = public_path() . '/upload/' . $contractSign['sign_file']; |
||||
|
} else { |
||||
|
// 模板文档路径 |
||||
|
$contract = \think\facade\Db::table('school_contract') |
||||
|
->where('id', $data['contract_id']) |
||||
|
->find(); |
||||
|
$filePath = public_path() . '/upload/' . $contract['contract_template']; |
||||
|
} |
||||
|
|
||||
|
// 检查文件是否存在 |
||||
|
if (!file_exists($filePath)) { |
||||
|
return fail('文件不存在'); |
||||
|
} |
||||
|
|
||||
|
// 设置下载响应头 |
||||
|
$response = response()->create('', 'html'); |
||||
|
$response->header([ |
||||
|
'Content-Type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
||||
|
'Content-Disposition' => 'attachment; filename=' . urlencode($fileInfo['file_name']), |
||||
|
'Content-Length' => filesize($filePath), |
||||
|
'Cache-Control' => 'private', |
||||
|
'Pragma' => 'private', |
||||
|
'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT' |
||||
|
]); |
||||
|
|
||||
|
// 输出文件内容 |
||||
|
$response->data(file_get_contents($filePath)); |
||||
|
|
||||
|
return $response; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return fail('下载失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
<?php |
||||
|
declare(strict_types=1); |
||||
|
|
||||
|
namespace app\model\school_approval; |
||||
|
|
||||
|
use core\base\BaseModel; |
||||
|
|
||||
|
/** |
||||
|
* 审批历史模型 |
||||
|
* Class SchoolApprovalHistory |
||||
|
* @package app\model\school_approval |
||||
|
*/ |
||||
|
class SchoolApprovalHistory extends BaseModel |
||||
|
{ |
||||
|
/** |
||||
|
* 数据表主键 |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $pk = 'id'; |
||||
|
|
||||
|
/** |
||||
|
* 模型名称 |
||||
|
* @var string |
||||
|
*/ |
||||
|
protected $name = 'approval_history'; |
||||
|
|
||||
|
// 审批动作常量 |
||||
|
const ACTION_APPROVE = 'approve'; |
||||
|
const ACTION_REJECT = 'reject'; |
||||
|
const ACTION_FORWARD = 'forward'; |
||||
|
const ACTION_CANCEL = 'cancel'; |
||||
|
|
||||
|
// 审批状态常量 |
||||
|
const STATUS_APPROVED = 'approved'; |
||||
|
const STATUS_REJECTED = 'rejected'; |
||||
|
const STATUS_FORWARDED = 'forwarded'; |
||||
|
const STATUS_CANCELLED = 'cancelled'; |
||||
|
|
||||
|
/** |
||||
|
* 关联审批流程 |
||||
|
* @return \think\model\relation\BelongsTo |
||||
|
*/ |
||||
|
public function approvalProcess() |
||||
|
{ |
||||
|
return $this->belongsTo(SchoolApprovalProcess::class, 'process_id', 'id'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 关联参与人(人员表) |
||||
|
* @return \think\model\relation\BelongsTo |
||||
|
*/ |
||||
|
public function participant() |
||||
|
{ |
||||
|
return $this->belongsTo('app\model\personnel\Personnel', 'participant_id', 'id'); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,578 @@ |
|||||
|
<template> |
||||
|
<view class="contract-form-container"> |
||||
|
<!-- 自定义导航栏 --> |
||||
|
<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"> |
||||
|
<view class="info-card"> |
||||
|
<text class="contract-name">{{ contractName }}</text> |
||||
|
<text class="contract-type">{{ contractInfo.contract_type || '学员课程协议' }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 表单区域 --> |
||||
|
<view class="form-section" v-if="formFields.length > 0"> |
||||
|
<view class="section-title">合同信息填写</view> |
||||
|
|
||||
|
<view class="form-content"> |
||||
|
<view v-for="(field, index) in formFields" :key="field.id || index" class="form-field"> |
||||
|
<!-- 普通输入字段 --> |
||||
|
<view v-if="field.data_type === 'user_input'" class="input-field"> |
||||
|
<view class="field-label"> |
||||
|
<text class="label-text">{{ field.name }}</text> |
||||
|
<text v-if="field.is_required" class="required-mark">*</text> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 文本输入 --> |
||||
|
<input |
||||
|
v-if="field.field_type === 'text'" |
||||
|
v-model="formData[field.placeholder]" |
||||
|
:placeholder="field.default_value || `请输入${field.name}`" |
||||
|
class="form-input" |
||||
|
/> |
||||
|
|
||||
|
<!-- 日期输入 --> |
||||
|
<picker |
||||
|
v-if="field.field_type === 'date'" |
||||
|
mode="date" |
||||
|
:value="formData[field.placeholder] || getCurrentDate()" |
||||
|
@change="onDateChange($event, field.placeholder)" |
||||
|
class="form-picker"> |
||||
|
<view class="picker-display"> |
||||
|
{{ formData[field.placeholder] || getCurrentDate() }} |
||||
|
</view> |
||||
|
</picker> |
||||
|
|
||||
|
<!-- 数字输入 --> |
||||
|
<input |
||||
|
v-if="field.field_type === 'number'" |
||||
|
v-model="formData[field.placeholder]" |
||||
|
type="number" |
||||
|
:placeholder="field.default_value || `请输入${field.name}`" |
||||
|
class="form-input" |
||||
|
/> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 签名字段 --> |
||||
|
<view v-if="field.data_type === 'signature'" class="signature-field"> |
||||
|
<view class="field-label"> |
||||
|
<text class="label-text">{{ field.name }}</text> |
||||
|
<text v-if="field.is_required" class="required-mark">*</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="signature-container"> |
||||
|
<view v-if="!signatures[field.placeholder]" class="signature-placeholder" @click="goToSignature(field)"> |
||||
|
<text class="placeholder-icon">✍️</text> |
||||
|
<text class="placeholder-text">点击进行签名</text> |
||||
|
</view> |
||||
|
|
||||
|
<view v-else class="signature-preview" @click="goToSignature(field)"> |
||||
|
<image :src="signatures[field.placeholder]" class="signature-image" mode="aspectFit"></image> |
||||
|
<text class="signature-tip">点击重新签名</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 显示字段 --> |
||||
|
<view v-if="field.data_type === 'database' || field.data_type === 'system'" class="display-field"> |
||||
|
<view class="field-label"> |
||||
|
<text class="label-text">{{ field.name }}</text> |
||||
|
</view> |
||||
|
<view class="display-value">{{ field.default_value || '自动填充' }}</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 合同条款(如果有) --> |
||||
|
<view class="terms-section" v-if="contractInfo.contract_content"> |
||||
|
<view class="section-title">合同条款</view> |
||||
|
<view class="terms-content"> |
||||
|
<text class="terms-text">{{ contractInfo.contract_content }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 底部操作按钮 --> |
||||
|
<view class="footer-actions"> |
||||
|
<button class="action-btn secondary" @click="goBack">取消</button> |
||||
|
<button class="action-btn primary" |
||||
|
@click="submitContract" |
||||
|
:disabled="!isFormValid || submitting" |
||||
|
:loading="submitting"> |
||||
|
{{ submitting ? '提交中...' : '确认签署' }} |
||||
|
</button> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import apiRoute from '@/api/apiRoute.js' |
||||
|
|
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
contractId: 0, |
||||
|
studentId: 0, |
||||
|
contractName: '', |
||||
|
contractInfo: {}, |
||||
|
formFields: [], |
||||
|
formData: {}, |
||||
|
signatures: {}, |
||||
|
submitting: false, |
||||
|
loading: false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
computed: { |
||||
|
isFormValid() { |
||||
|
// 检查必填字段是否都已填写 |
||||
|
for (let field of this.formFields) { |
||||
|
if (field.is_required) { |
||||
|
if (field.data_type === 'signature') { |
||||
|
if (!this.signatures[field.placeholder]) { |
||||
|
return false |
||||
|
} |
||||
|
} else if (field.data_type === 'user_input') { |
||||
|
if (!this.formData[field.placeholder] || this.formData[field.placeholder].trim() === '') { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return true |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onLoad(options) { |
||||
|
this.contractId = parseInt(options.contract_id) || 0 |
||||
|
this.studentId = parseInt(options.student_id) || 0 |
||||
|
this.contractName = options.contractName ? decodeURIComponent(options.contractName) : '合同签署' |
||||
|
|
||||
|
if (this.contractId && this.studentId) { |
||||
|
this.loadSignForm() |
||||
|
} else { |
||||
|
uni.showToast({ |
||||
|
title: '参数错误', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onShow() { |
||||
|
// 从签名页面返回时,检查是否有新的签名数据 |
||||
|
const pages = getCurrentPages() |
||||
|
const currentPage = pages[pages.length - 1] |
||||
|
|
||||
|
if (currentPage.data && currentPage.data.newSignature) { |
||||
|
const signData = currentPage.data.newSignature |
||||
|
this.signatures[signData.fieldPlaceholder] = signData.signatureData |
||||
|
// 清理临时数据 |
||||
|
currentPage.data.newSignature = null |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
goBack() { |
||||
|
uni.navigateBack() |
||||
|
}, |
||||
|
|
||||
|
async loadSignForm() { |
||||
|
this.loading = true |
||||
|
try { |
||||
|
const response = await apiRoute.getStudentContractSignForm({ |
||||
|
contract_id: this.contractId, |
||||
|
student_id: this.studentId |
||||
|
}) |
||||
|
|
||||
|
if (response.code === 1) { |
||||
|
this.contractInfo = response.data |
||||
|
this.formFields = response.data.form_fields || [] |
||||
|
|
||||
|
// 初始化表单数据 |
||||
|
this.formFields.forEach(field => { |
||||
|
if (field.data_type === 'user_input' && field.default_value) { |
||||
|
this.$set(this.formData, field.placeholder, field.default_value) |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
console.log('表单配置加载成功:', this.formFields) |
||||
|
} else { |
||||
|
console.error('API返回错误:', response.msg) |
||||
|
uni.showToast({ |
||||
|
title: response.msg || '获取表单配置失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('获取表单配置失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '获取表单配置失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} finally { |
||||
|
this.loading = false |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getCurrentDate() { |
||||
|
const now = new Date() |
||||
|
const year = now.getFullYear() |
||||
|
const month = String(now.getMonth() + 1).padStart(2, '0') |
||||
|
const day = String(now.getDate()).padStart(2, '0') |
||||
|
return `${year}-${month}-${day}` |
||||
|
}, |
||||
|
|
||||
|
onDateChange(event, fieldPlaceholder) { |
||||
|
this.$set(this.formData, fieldPlaceholder, event.detail.value) |
||||
|
}, |
||||
|
|
||||
|
goToSignature(field) { |
||||
|
uni.navigateTo({ |
||||
|
url: `/pages-common/contract/contract_sign?contract_id=${this.contractId}&field_name=${encodeURIComponent(field.name)}&field_placeholder=${encodeURIComponent(field.placeholder)}` |
||||
|
}) |
||||
|
}, |
||||
|
|
||||
|
async submitContract() { |
||||
|
if (!this.isFormValid) { |
||||
|
uni.showToast({ |
||||
|
title: '请填写完整信息并完成签名', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
this.submitting = true |
||||
|
|
||||
|
try { |
||||
|
// 准备提交数据 |
||||
|
const submitData = { |
||||
|
contract_id: this.contractId, |
||||
|
student_id: this.studentId, |
||||
|
form_data: { ...this.formData }, |
||||
|
signature_image: '' |
||||
|
} |
||||
|
|
||||
|
// 添加签名数据到form_data中 |
||||
|
for (let field of this.formFields) { |
||||
|
if (field.data_type === 'signature' && this.signatures[field.placeholder]) { |
||||
|
submitData.form_data[field.placeholder] = this.signatures[field.placeholder] |
||||
|
|
||||
|
// 如果是主要签名,设置为signature_image |
||||
|
if (!submitData.signature_image) { |
||||
|
submitData.signature_image = this.signatures[field.placeholder] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
console.log('提交合同数据:', submitData) |
||||
|
|
||||
|
const response = await apiRoute.signStudentContract(submitData) |
||||
|
|
||||
|
if (response.code === 1) { |
||||
|
uni.showToast({ |
||||
|
title: '合同签署成功', |
||||
|
icon: 'success', |
||||
|
duration: 2000 |
||||
|
}) |
||||
|
|
||||
|
setTimeout(() => { |
||||
|
// 返回合同列表页面 |
||||
|
uni.navigateBack({ |
||||
|
delta: 2 |
||||
|
}) |
||||
|
}, 2000) |
||||
|
} else { |
||||
|
throw new Error(response.msg || '合同签署失败') |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('提交合同失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: error.message || '网络错误,请稍后重试', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} finally { |
||||
|
this.submitting = false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
.contract-form-container { |
||||
|
min-height: 100vh; |
||||
|
background: #f8f9fa; |
||||
|
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 { |
||||
|
padding: 20rpx; |
||||
|
|
||||
|
.info-card { |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 32rpx; |
||||
|
text-align: center; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
|
||||
|
.contract-name { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
display: block; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.contract-type { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 表单区域 |
||||
|
.form-section { |
||||
|
margin: 20rpx; |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
padding: 24rpx 32rpx 16rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.form-content { |
||||
|
padding: 24rpx 32rpx 32rpx; |
||||
|
|
||||
|
.form-field { |
||||
|
margin-bottom: 32rpx; |
||||
|
|
||||
|
&:last-child { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.field-label { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-bottom: 12rpx; |
||||
|
|
||||
|
.label-text { |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.required-mark { |
||||
|
color: #ff4757; |
||||
|
font-size: 26rpx; |
||||
|
margin-left: 4rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.form-input { |
||||
|
width: 100%; |
||||
|
height: 80rpx; |
||||
|
background: #f8f9fa; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 0 20rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.form-picker { |
||||
|
width: 100%; |
||||
|
height: 80rpx; |
||||
|
background: #f8f9fa; |
||||
|
border-radius: 8rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
|
||||
|
.picker-display { |
||||
|
padding: 0 20rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
flex: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.display-value { |
||||
|
height: 80rpx; |
||||
|
background: #f0f0f0; |
||||
|
border-radius: 8rpx; |
||||
|
padding: 0 20rpx; |
||||
|
font-size: 26rpx; |
||||
|
color: #666; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 签名字段样式 |
||||
|
.signature-field { |
||||
|
.signature-container { |
||||
|
background: #f8f9fa; |
||||
|
border-radius: 8rpx; |
||||
|
min-height: 120rpx; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
border: 2rpx dashed #ddd; |
||||
|
|
||||
|
.signature-placeholder { |
||||
|
text-align: center; |
||||
|
color: #999; |
||||
|
|
||||
|
.placeholder-icon { |
||||
|
font-size: 40rpx; |
||||
|
display: block; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.placeholder-text { |
||||
|
font-size: 24rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.signature-preview { |
||||
|
width: 100%; |
||||
|
text-align: center; |
||||
|
|
||||
|
.signature-image { |
||||
|
width: 200rpx; |
||||
|
height: 80rpx; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.signature-tip { |
||||
|
font-size: 22rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 合同条款 |
||||
|
.terms-section { |
||||
|
margin: 20rpx; |
||||
|
background: #fff; |
||||
|
border-radius: 16rpx; |
||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08); |
||||
|
|
||||
|
.section-title { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
padding: 24rpx 32rpx 16rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.terms-content { |
||||
|
padding: 24rpx 32rpx 32rpx; |
||||
|
|
||||
|
.terms-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
line-height: 1.6; |
||||
|
white-space: pre-line; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 底部操作按钮 |
||||
|
.footer-actions { |
||||
|
position: fixed; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
background: #fff; |
||||
|
padding: 24rpx 32rpx; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
display: flex; |
||||
|
gap: 24rpx; |
||||
|
z-index: 100; |
||||
|
|
||||
|
.action-btn { |
||||
|
flex: 1; |
||||
|
height: 88rpx; |
||||
|
border-radius: 12rpx; |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
border: none; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
|
||||
|
&.primary { |
||||
|
background: #29D3B4; |
||||
|
color: #fff; |
||||
|
|
||||
|
&:active:not(:disabled) { |
||||
|
background: #26c6a0; |
||||
|
} |
||||
|
|
||||
|
&:disabled { |
||||
|
background: #ccc; |
||||
|
color: #999; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.secondary { |
||||
|
background: #f8f9fa; |
||||
|
color: #666; |
||||
|
|
||||
|
&:active { |
||||
|
background: #e9ecef; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue