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.
1302 lines
32 KiB
1302 lines
32 KiB
<!--合同签署表单页面-->
|
|
<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 }">
|
|
<view v-html="renderContractContentWithFields" class="content_text"></view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 表单内容 -->
|
|
<view class="form_section" v-if="!loading">
|
|
<view class="form_title">请填写以下信息</view>
|
|
<view class="form_content">
|
|
<view
|
|
v-for="(field, index) in filteredFormFields"
|
|
:key="index"
|
|
:class="['form_field',getFieldClass(field)]"
|
|
>
|
|
<view class="field_label">
|
|
{{ field.name }}
|
|
<text v-if="field.is_required" class="required_mark">*</text>
|
|
<view v-if="isStaff" class="party_indicator">
|
|
<text :class="['party_tag',getPartyClass(field)]">{{ getPartyLabel(field) }}</text>
|
|
</view>
|
|
</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="!canEditField(field)"
|
|
:class="!canEditField(field) ? '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="!canEditField(field)"
|
|
:class="!canEditField(field) ? '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="!canEditField(field)"
|
|
:class="!canEditField(field) ? '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">
|
|
<!-- 当status=2且sign_file为空时,显示确认生成合同和修改合同按钮 -->
|
|
<view v-if="contractInfo && contractInfo.status == 2 && !contractInfo.sign_file" class="button_group">
|
|
<button
|
|
class="uni-button generate-btn"
|
|
:loading="generating"
|
|
@click="confirmGenerateContract"
|
|
>
|
|
{{ generating ? '生成中...' : '确认生成合同' }}
|
|
</button>
|
|
<button
|
|
class="uni-button modify-btn"
|
|
@click="submitContract"
|
|
>
|
|
修改合同
|
|
</button>
|
|
</view>
|
|
|
|
<!-- 当status=2且sign_file有值时,显示下载合同按钮 -->
|
|
<button
|
|
v-else-if="contractInfo && contractInfo.status == 3 && contractInfo.sign_file"
|
|
class="uni-button download-btn"
|
|
@click="downloadContract"
|
|
>
|
|
下载合同
|
|
</button>
|
|
|
|
<!-- 其他情况显示原有的提交签署按钮 -->
|
|
<button
|
|
v-else
|
|
class="uni-button submit-btn"
|
|
:loading="submitting"
|
|
@click="submitContract"
|
|
>
|
|
{{ submitting ? '提交中...' : '提交签署' }}
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import apiRoute from '@/api/apiRoute.js'
|
|
import { Api_url,img_domian } from '@/common/config.js'
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
contractId: 0,
|
|
studentId: 0,
|
|
contractSignId: 0, // 新增:合同签署记录ID
|
|
contractName: '',
|
|
contractInfo: null,
|
|
contractContent: '',
|
|
contentExpanded: false,
|
|
formFields: [],
|
|
formData: {},
|
|
loading: true,
|
|
submitting: false,
|
|
generating: false, // 生成合同状态
|
|
// 角色和权限控制
|
|
userInfo: null,
|
|
userRole: 'student', // 默认为学生角色
|
|
isStaffMode: false
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
// 检查当前用户是否为员工
|
|
isStaff() {
|
|
return this.userRole === 'staff' || this.isStaffMode
|
|
},
|
|
|
|
// 根据角色过滤表单字段
|
|
filteredFormFields() {
|
|
if (!this.formFields || this.formFields.length === 0) {
|
|
return []
|
|
}
|
|
|
|
// 员工可以填写所有字段
|
|
if (this.isStaff) {
|
|
return this.formFields
|
|
}
|
|
|
|
// 学生只能填写乙方字段
|
|
return this.formFields.filter(field => {
|
|
const signParty = field.sign_party || ''
|
|
return signParty === 'second_party' || signParty === 'party_b' || signParty === '乙方'
|
|
})
|
|
},
|
|
|
|
// 渲染合同内容,替换占位符为实际值
|
|
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
|
|
},
|
|
|
|
// 根据字段配置渲染合同内容,支持用户输入和电子签名字段
|
|
renderContractContentWithFields() {
|
|
if (!this.contractContent) return ''
|
|
|
|
let content = this.contractContent
|
|
|
|
// 遍历表单字段配置,根据数据类型处理占位符
|
|
this.formFields.forEach(field => {
|
|
const placeholder = field.placeholder || field.name
|
|
const regex = new RegExp(`\\{\\{${placeholder}\\}\\}`, 'g')
|
|
|
|
// 根据数据类型渲染不同的内容
|
|
let replacementContent = ''
|
|
|
|
switch (field.data_type) {
|
|
case 'user_input':
|
|
// 用户输入字段:显示输入框或当前值
|
|
const inputValue = this.formData[placeholder] || field.default_value || ''
|
|
if (inputValue) {
|
|
replacementContent = `<span style="border-bottom: 1px solid #333; padding: 2px 8px; min-width: 80px; display: inline-block;">${inputValue}</span>`
|
|
} else {
|
|
replacementContent = `<span style="border-bottom: 1px solid #ccc; padding: 2px 8px; min-width: 80px; display: inline-block; color: #999;">待填写</span>`
|
|
}
|
|
break
|
|
|
|
case 'signature':
|
|
// 电子签名字段:显示签名状态或占位符
|
|
const signatureValue = this.formData[placeholder]
|
|
if (signatureValue) {
|
|
replacementContent = `<span style="display: inline-block; border: 1px solid #27ae60; padding: 4px 8px; background: rgba(39, 174, 96, 0.1); color: #27ae60; border-radius: 4px;">✓ 已签名</span>`
|
|
} else {
|
|
replacementContent = `<span style="display: inline-block; border: 1px dashed #29D3B4; padding: 4px 8px; background: rgba(41, 211, 180, 0.05); color: #29D3B4; border-radius: 4px;">✒️ 待签名</span>`
|
|
}
|
|
break
|
|
|
|
case 'sign_img':
|
|
// 签名图片字段:显示上传状态
|
|
const signImgValue = this.formData[placeholder]
|
|
if (signImgValue) {
|
|
replacementContent = `<span style="display: inline-block; border: 1px solid #67c23a; padding: 4px 8px; background: rgba(103, 194, 58, 0.1); color: #67c23a; border-radius: 4px;">✓ 已上传</span>`
|
|
} else {
|
|
replacementContent = `<span style="display: inline-block; border: 1px dashed #409eff; padding: 4px 8px; background: rgba(64, 158, 255, 0.05); color: #409eff; border-radius: 4px;">📷 待上传</span>`
|
|
}
|
|
break
|
|
|
|
case 'database':
|
|
case 'system':
|
|
default:
|
|
// 数据库字段和系统字段:直接显示值
|
|
const defaultValue = this.formData[placeholder] || field.default_value || ''
|
|
if (defaultValue) {
|
|
replacementContent = `<span style="font-weight: 500;">${defaultValue}</span>`
|
|
} else {
|
|
replacementContent = `<span style="color: #999; font-style: italic;">系统自动获取</span>`
|
|
}
|
|
break
|
|
}
|
|
|
|
content = content.replace(regex, replacementContent)
|
|
})
|
|
|
|
return content
|
|
}
|
|
},
|
|
|
|
onLoad(options) {
|
|
this.contractId = parseInt(options.contract_id) || 0
|
|
this.studentId = parseInt(options.student_id) || 0
|
|
this.contractName = decodeURIComponent(options.contract_name || '')
|
|
// 新增:支持合同签署记录ID(用于已存在的签署记录)
|
|
this.contractSignId = parseInt(options.contract_sign_id) || 0
|
|
|
|
// 角色检测
|
|
this.userRole = options.user_role || 'student'
|
|
this.isStaffMode = options.user_role === 'staff'
|
|
|
|
// 获取用户信息进行角色验证
|
|
this.getUserInfo()
|
|
|
|
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()
|
|
},
|
|
|
|
// 获取用户信息
|
|
getUserInfo() {
|
|
try {
|
|
const userInfo = uni.getStorageSync('userInfo')
|
|
if (userInfo) {
|
|
this.userInfo = JSON.parse(userInfo)
|
|
// 如果缓存的用户类型与URL参数不符,以URL参数为准
|
|
if (this.userInfo.user_type && this.userRole === 'student') {
|
|
this.userRole = this.userInfo.user_type === 'staff' ? 'staff' : 'student'
|
|
this.isStaffMode = this.userRole === 'staff'
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('获取用户信息失败:', error)
|
|
}
|
|
},
|
|
|
|
// 检查字段是否可编辑
|
|
canEditField(field) {
|
|
// 非用户输入类型的字段都不可编辑
|
|
if (field.data_type !== 'user_input') {
|
|
return false
|
|
}
|
|
|
|
// 员工可以编辑所有用户输入字段
|
|
if (this.isStaff) {
|
|
return true
|
|
}
|
|
|
|
// 学生只能编辑乙方字段
|
|
const signParty = field.sign_party || ''
|
|
return signParty === 'second_party' || signParty === 'party_b' || signParty === '乙方'
|
|
},
|
|
|
|
// 获取字段样式类
|
|
getFieldClass(field) {
|
|
const classes = []
|
|
|
|
if (this.isStaff) {
|
|
const signParty = field.sign_party || ''
|
|
if (signParty === 'first_party' || signParty === 'party_a' || signParty === '甲方') {
|
|
classes.push('first_party_field')
|
|
} else if (signParty === 'second_party' || signParty === 'party_b' || signParty === '乙方') {
|
|
classes.push('second_party_field')
|
|
}
|
|
}
|
|
|
|
if (!this.canEditField(field)) {
|
|
classes.push('readonly_field')
|
|
}
|
|
|
|
return classes.join(' ')
|
|
},
|
|
|
|
// 获取甲乙方标签样式类
|
|
getPartyClass(field) {
|
|
const signParty = field.sign_party || ''
|
|
if (signParty === 'first_party' || signParty === 'party_a' || signParty === '甲方') {
|
|
return 'party_first'
|
|
} else if (signParty === 'second_party' || signParty === 'party_b' || signParty === '乙方') {
|
|
return 'party_second'
|
|
}
|
|
return 'party_unknown'
|
|
},
|
|
|
|
// 获取甲乙方标签文本
|
|
getPartyLabel(field) {
|
|
const signParty = field.sign_party || ''
|
|
if (signParty === 'first_party' || signParty === 'party_a' || signParty === '甲方') {
|
|
return '甲方'
|
|
} else if (signParty === 'second_party' || signParty === 'party_b' || signParty === '乙方') {
|
|
return '乙方'
|
|
}
|
|
return '未知'
|
|
},
|
|
|
|
async loadSignForm() {
|
|
this.loading = true
|
|
try {
|
|
console.log('加载签署表单:', {
|
|
contractId: this.contractId,
|
|
studentId: this.studentId,
|
|
contractSignId: this.contractSignId
|
|
})
|
|
|
|
let response;
|
|
|
|
// 优先使用contract_sign_id获取签署表单(支持按签署关系区分字段)
|
|
if (this.contractSignId && this.contractSignId > 0) {
|
|
console.log('使用签署记录ID获取表单配置')
|
|
response = await apiRoute.getStudentContractSignFormBySignId({
|
|
contract_sign_id: this.contractSignId,
|
|
student_id: this.studentId
|
|
})
|
|
} else {
|
|
console.log('使用合同模板ID获取表单配置(兼容模式)')
|
|
// 兼容旧版本:使用contract_id获取签署表单
|
|
response = await apiRoute.getStudentContractSignForm({
|
|
contract_id: this.contractId,
|
|
student_id: this.studentId
|
|
})
|
|
}
|
|
|
|
if (response.code === 1) {
|
|
const data = response.data
|
|
this.contractInfo = {
|
|
contract_sign_id: data.contract_sign_id,
|
|
contract_name: data.contract_name,
|
|
contract_type: data.contract_type,
|
|
status: data.status,
|
|
sign_file: data.sign_file
|
|
}
|
|
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: Api_url + '/file/image',
|
|
filePath: tempFilePath,
|
|
name: 'file',
|
|
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 ''
|
|
}
|
|
},
|
|
|
|
// 获取签名图片(从表单数据中提取签名字段)
|
|
getSignatureImage() {
|
|
// 查找签名类型的字段
|
|
const signatureFields = this.formFields.filter(field =>
|
|
field.data_type === 'signature' || field.data_type === 'sign_img'
|
|
)
|
|
|
|
// 返回第一个签名字段的值,如果没有则返回空字符串
|
|
if (signatureFields.length > 0) {
|
|
const signatureField = signatureFields[0]
|
|
const fieldKey = signatureField.placeholder || signatureField.name
|
|
return this.formData[fieldKey] || ''
|
|
}
|
|
|
|
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
|
|
})
|
|
|
|
// 根据角色提交签署数据
|
|
let response
|
|
if (this.isStaff) {
|
|
// 员工端签署
|
|
response = await apiRoute.signStaffContract({
|
|
contract_id: this.contractId,
|
|
contract_sign_id: this.contractSignId,
|
|
form_data: this.formData,
|
|
signature_image: this.getSignatureImage()
|
|
})
|
|
} else {
|
|
// 学生端签署
|
|
response = await apiRoute.signStudentContract({
|
|
contract_id: this.contractId,
|
|
contract_sign_id: this.contractSignId,
|
|
student_id: this.studentId,
|
|
form_data: this.formData,
|
|
signature_image: this.getSignatureImage()
|
|
})
|
|
}
|
|
|
|
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
|
|
}
|
|
},
|
|
|
|
// 确认生成合同
|
|
async confirmGenerateContract() {
|
|
this.generating = true
|
|
try {
|
|
console.log('确认生成合同:', {
|
|
contractSignId: this.contractSignId
|
|
})
|
|
|
|
const response = await apiRoute.confirmGenerateContract({
|
|
contract_sign_id: this.contractSignId
|
|
})
|
|
|
|
if (response.code === 1) {
|
|
uni.showToast({
|
|
title: '合同生成成功',
|
|
icon: 'success',
|
|
duration: 2000
|
|
})
|
|
|
|
// 重新加载表单数据以更新合同状态
|
|
setTimeout(() => {
|
|
this.loadSignForm()
|
|
}, 2000)
|
|
} else {
|
|
uni.showToast({
|
|
title: response.msg || '合同生成失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('合同生成失败:', error)
|
|
uni.showToast({
|
|
title: '合同生成失败',
|
|
icon: 'none'
|
|
})
|
|
} finally {
|
|
this.generating = false
|
|
}
|
|
},
|
|
// 下载合同
|
|
downloadContract() {
|
|
if (!this.contractInfo || !this.contractInfo.sign_file) {
|
|
uni.showToast({
|
|
title: '合同文件不存在',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
|
|
// 下载合同文件
|
|
uni.showLoading({
|
|
title: '准备下载...',
|
|
mask: true
|
|
})
|
|
|
|
// 构建完整的文件URL
|
|
const fileUrl = this.contractInfo.sign_file.startsWith('http')
|
|
? this.contractInfo.sign_file
|
|
: img_domian + this.contractInfo.sign_file
|
|
|
|
// 下载文件
|
|
uni.downloadFile({
|
|
url: fileUrl,
|
|
success: (res) => {
|
|
uni.hideLoading()
|
|
if (res.statusCode === 200) {
|
|
// 保存到相册或打开文件
|
|
uni.saveFile({
|
|
tempFilePath: res.tempFilePath,
|
|
success: (saveRes) => {
|
|
uni.showToast({
|
|
title: '下载成功',
|
|
icon: 'success'
|
|
})
|
|
|
|
// 打开文件
|
|
uni.openDocument({
|
|
filePath: saveRes.savedFilePath,
|
|
success: () => {
|
|
console.log('打开文档成功')
|
|
},
|
|
fail: (err) => {
|
|
console.error('打开文档失败:', err)
|
|
}
|
|
})
|
|
},
|
|
fail: (err) => {
|
|
console.error('保存文件失败:', err)
|
|
uni.showToast({
|
|
title: '保存失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
})
|
|
} else {
|
|
uni.showToast({
|
|
title: '下载失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
uni.hideLoading()
|
|
console.error('下载失败:', err)
|
|
uni.showToast({
|
|
title: '下载失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
</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); }
|
|
}
|
|
|
|
// 甲乙方标识和字段样式
|
|
.party_indicator {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
margin-left: 12rpx;
|
|
|
|
.party_tag {
|
|
font-size: 20rpx;
|
|
padding: 4rpx 8rpx;
|
|
border-radius: 6rpx;
|
|
font-weight: 500;
|
|
|
|
&.party_first {
|
|
background: rgba(255, 152, 0, 0.1);
|
|
color: #ff9800;
|
|
border: 1rpx solid rgba(255, 152, 0, 0.3);
|
|
}
|
|
|
|
&.party_second {
|
|
background: rgba(76, 175, 80, 0.1);
|
|
color: #4caf50;
|
|
border: 1rpx solid rgba(76, 175, 80, 0.3);
|
|
}
|
|
|
|
&.party_unknown {
|
|
background: rgba(158, 158, 158, 0.1);
|
|
color: #9e9e9e;
|
|
border: 1rpx solid rgba(158, 158, 158, 0.3);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 字段样式区分
|
|
.form_field {
|
|
&.first_party_field {
|
|
border-left: 4rpx solid #ff9800;
|
|
background: linear-gradient(90deg, rgba(255, 152, 0, 0.02) 0%, transparent 100%);
|
|
|
|
.field_label {
|
|
color: #e65100;
|
|
}
|
|
}
|
|
|
|
&.second_party_field {
|
|
border-left: 4rpx solid #4caf50;
|
|
background: linear-gradient(90deg, rgba(76, 175, 80, 0.02) 0%, transparent 100%);
|
|
|
|
.field_label {
|
|
color: #2e7d32;
|
|
}
|
|
}
|
|
|
|
&.readonly_field {
|
|
background: #f8f9fa;
|
|
|
|
.field_label {
|
|
color: #6c757d;
|
|
}
|
|
|
|
input, textarea {
|
|
background: #f8f9fa !important;
|
|
color: #6c757d !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 提交按钮
|
|
.submit_section {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
background: rgba(255, 255, 255, 0.98);
|
|
backdrop-filter: blur(10rpx);
|
|
padding: 20rpx 32rpx 40rpx;
|
|
border-top: 1rpx solid #f0f0f0;
|
|
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
|
z-index: 999;
|
|
|
|
// 小程序端适配底部安全区域
|
|
// #ifdef MP-WEIXIN
|
|
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
|
// #endif
|
|
|
|
// 为了确保完全不透明,添加伪元素背景
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: #fff;
|
|
z-index: -1;
|
|
}
|
|
|
|
// 按钮组布局
|
|
.button_group {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
// UniApp原生按钮样式
|
|
.uni-button {
|
|
height: 88rpx;
|
|
line-height: 88rpx;
|
|
border-radius: 44rpx;
|
|
font-size: 32rpx;
|
|
font-weight: 500;
|
|
border: none;
|
|
outline: none;
|
|
|
|
&.generate-btn {
|
|
flex: 1;
|
|
background: linear-gradient(135deg, #FF6B35 0%, #F7931E 100%);
|
|
color: #fff;
|
|
}
|
|
|
|
&.modify-btn {
|
|
flex: 1;
|
|
background: linear-gradient(135deg, #29D3B4 0%, #1BA89A 100%);
|
|
color: #fff;
|
|
}
|
|
|
|
&.download-btn {
|
|
width: 100%;
|
|
background: linear-gradient(135deg, #52C41A 0%, #389E0D 100%);
|
|
color: #fff;
|
|
}
|
|
|
|
&.submit-btn {
|
|
width: 100%;
|
|
background: linear-gradient(135deg, #FF6B35 0%, #F7931E 100%);
|
|
color: #fff;
|
|
}
|
|
|
|
// 按钮按下效果
|
|
&:active {
|
|
opacity: 0.8;
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
// 加载状态
|
|
&[loading] {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
}
|
|
</style>
|