You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
578 lines
13 KiB
578 lines
13 KiB
<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>
|