|
|
<template>
|
|
|
<view class="reservation-page">
|
|
|
<view class="fixed-nav">
|
|
|
<NavBar :title="isEdit ? '编辑船只' : '添加船只'" />
|
|
|
</view>
|
|
|
<view class="content-area">
|
|
|
<!-- 步骤条 -->
|
|
|
<view class="step-bar">
|
|
|
<view class="step-group" v-for="(step, idx) in steps" :key="idx">
|
|
|
<view class="step-circle" :class="{active: idx + 1 === currentStep}">{{ idx + 1 }}</view>
|
|
|
<view class="step-label" :class="{active: idx + 1 === currentStep}">{{ step }}</view>
|
|
|
<view v-if="idx < steps.length - 1" class="step-line"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 第一步:基本信息 -->
|
|
|
<view v-if="currentStep === 1" class="info-card">
|
|
|
<view class="info-title">基本信息</view>
|
|
|
<view class="info-form">
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">船舶所有人</text>
|
|
|
<input class="form-input" v-model="form.owner" placeholder="姓名与身份证一致" />
|
|
|
</view>
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">身份证号</text>
|
|
|
<input class="form-input" v-model="form.idCard" placeholder="输入正确的18位身份证号码" />
|
|
|
</view>
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">联系电话</text>
|
|
|
<input class="form-input" v-model="form.phone" placeholder="11位常用的手机号" />
|
|
|
</view>
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">船舶编号</text>
|
|
|
<input class="form-input" v-model="form.shipNo" placeholder="请输入船舶编号" />
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 第二步:船舶参数 -->
|
|
|
<view v-if="currentStep === 2" class="info-card">
|
|
|
<view class="info-title">船舶参数 <text class="info-desc">(请按船舶检验证书填写)</text></view>
|
|
|
<!-- 总吨位 -->
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">总吨位</text>
|
|
|
<input class="form-input" v-model="form.ton" placeholder="请输入总吨位" />
|
|
|
<text class="form-unit">吨</text>
|
|
|
</view>
|
|
|
<view class="form-tip">填写船舶的总吨位数值</view>
|
|
|
<!-- 总长度 -->
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">总长度</text>
|
|
|
<input class="form-input" v-model="form.length" placeholder="请输入总长度" />
|
|
|
<text class="form-unit">米</text>
|
|
|
</view>
|
|
|
<view class="form-tip">填写船舶的总长度数值</view>
|
|
|
<!-- 总宽 -->
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">总宽</text>
|
|
|
<input class="form-input" v-model="form.width" placeholder="请输入总宽" />
|
|
|
<text class="form-unit">米</text>
|
|
|
</view>
|
|
|
<view class="form-tip">填写船舶的总宽数值</view>
|
|
|
<!-- 型深 -->
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">型深</text>
|
|
|
<input class="form-input" v-model="form.depth" placeholder="请输入型深" />
|
|
|
<text class="form-unit">米</text>
|
|
|
</view>
|
|
|
<view class="form-tip">填写船舶的型深数值</view>
|
|
|
<!-- 参考载重吨位 -->
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">参考载重吨位</text>
|
|
|
<radio-group class="form-radio-group" v-model="form.tonLevel" @change="onTonLevelChange">
|
|
|
<radio
|
|
|
v-for="(value, label) in tonnageClassEnum"
|
|
|
:key="value"
|
|
|
class="form-radio"
|
|
|
:value="value"
|
|
|
:checked="form.tonLevel === value"
|
|
|
>{{ label }}</radio>
|
|
|
</radio-group>
|
|
|
</view>
|
|
|
<view class="form-tip">请选择船舶的参考载重吨位等级</view>
|
|
|
<!-- 船型 -->
|
|
|
<view class="form-row">
|
|
|
<text class="form-label required">船型</text>
|
|
|
<radio-group class="form-radio-group" v-model="form.shipType" @change="onShipTypeChange">
|
|
|
<radio
|
|
|
v-for="(value, label) in shipTypeEnum"
|
|
|
:key="value"
|
|
|
class="form-radio"
|
|
|
:value="value.toString()"
|
|
|
:checked="form.shipType === value.toString()"
|
|
|
>{{ label }}</radio>
|
|
|
</radio-group>
|
|
|
</view>
|
|
|
<view class="form-tip">请选择船舶的类型</view>
|
|
|
</view>
|
|
|
<!-- 第三步:船检簿上传 -->
|
|
|
<view v-if="currentStep === 3" class="info-card">
|
|
|
<view class="info-title-row">
|
|
|
<view class="info-title">船检簿上传</view>
|
|
|
<button class="example-btn" @click="viewExample">查看示例</button>
|
|
|
</view>
|
|
|
<!-- 第一页 -->
|
|
|
<view class="upload-section">
|
|
|
<text class="form-label required">第一页</text>
|
|
|
<view class="upload-row">
|
|
|
<view class="upload-img-box">
|
|
|
<image v-if="page1Img" :src="page1Img" class="upload-img" />
|
|
|
<view v-else class="upload-add" @click="chooseImage('page1')">
|
|
|
<text>+</text>
|
|
|
<text>添加图片</text>
|
|
|
</view>
|
|
|
<view v-if="page1Img" class="upload-del" @click="deleteImage('page1')">×</view>
|
|
|
</view>
|
|
|
<input class="upload-desc" v-model="page1Desc" placeholder="请输入第一页说明" />
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="divider"></view>
|
|
|
<!-- 第二页 -->
|
|
|
<view class="upload-section">
|
|
|
<text class="form-label required">第二页</text>
|
|
|
<view class="upload-row">
|
|
|
<view class="upload-img-box">
|
|
|
<image v-if="page2Img" :src="page2Img" class="upload-img" />
|
|
|
<view v-else class="upload-add" @click="chooseImage('page2')">
|
|
|
<text>+</text>
|
|
|
<text>添加图片</text>
|
|
|
</view>
|
|
|
<view v-if="page2Img" class="upload-del" @click="deleteImage('page2')">×</view>
|
|
|
</view>
|
|
|
<input class="upload-desc" v-model="page2Desc" placeholder="请输入第二页说明" />
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="divider"></view>
|
|
|
<!-- 第三页 -->
|
|
|
<view class="upload-section">
|
|
|
<text class="form-label required">第三页</text>
|
|
|
<view class="upload-row">
|
|
|
<view class="upload-img-box">
|
|
|
<image v-if="page3Img" :src="page3Img" class="upload-img" />
|
|
|
<view v-else class="upload-add" @click="chooseImage('page3')">
|
|
|
<text>+</text>
|
|
|
<text>添加图片</text>
|
|
|
</view>
|
|
|
<view v-if="page3Img" class="upload-del" @click="deleteImage('page3')">×</view>
|
|
|
</view>
|
|
|
<input class="upload-desc" v-model="page3Desc" placeholder="请输入第三页说明" />
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 第四步:签名确认 -->
|
|
|
<view v-if="currentStep === 4" class="info-card">
|
|
|
<view class="info-title">签名确认</view>
|
|
|
<view class="sign-declare-row">
|
|
|
<view style="position: relative; display: flex; align-items: center;">
|
|
|
<checkbox :checked="signChecked" />
|
|
|
<view style="position: absolute; left: 0; top: 0; right: 0; bottom: 0; background: rgba(0,0,0,0);" @tap="toggleSignChecked"></view>
|
|
|
</view>
|
|
|
<text class="sign-declare-text">
|
|
|
本人承诺所提供材料皆真实有效;如有虚假,本人承担因此造成的全部责任。
|
|
|
</text>
|
|
|
</view>
|
|
|
<view class="divider"></view>
|
|
|
<view class="form-label required" style="margin-bottom: 16rpx; color: #217aff;">手写签名</view>
|
|
|
<view class="sign-area">
|
|
|
<canvas
|
|
|
v-if="!signImg"
|
|
|
id="signCanvas"
|
|
|
canvas-id="signCanvas"
|
|
|
class="sign-canvas"
|
|
|
@touchstart="startSign"
|
|
|
@touchmove="moveSign"
|
|
|
@touchend="endSign"
|
|
|
disable-scroll="true"
|
|
|
></canvas>
|
|
|
<text class="sign-placeholder" v-if="!signImg && !hasSigned">此处签名</text>
|
|
|
<image v-if="signImg" :src="signImg" class="sign-img" />
|
|
|
</view>
|
|
|
<view class="sign-btn-bar">
|
|
|
<button class="sign-btn reset-btn" @click="resetSign">重新签名</button>
|
|
|
<button class="sign-btn preview-btn" @click="previewSign">预览签名</button>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
<!-- 步骤按钮区 -->
|
|
|
<view class="step-btn-bar">
|
|
|
<button
|
|
|
v-if="currentStep > 1"
|
|
|
class="step-btn prev-btn"
|
|
|
@click="prevStep"
|
|
|
>上一步</button>
|
|
|
<button
|
|
|
v-if="currentStep < 4"
|
|
|
class="step-btn next-btn"
|
|
|
@click="nextStep"
|
|
|
:class="{ 'single-btn': currentStep === 1 }"
|
|
|
:style="currentStep === 1 ? 'margin: 0 auto;' : ''"
|
|
|
>下一步</button>
|
|
|
<button
|
|
|
v-if="currentStep === 4"
|
|
|
class="step-btn next-btn"
|
|
|
@click="submit"
|
|
|
>提交</button>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import NavBar from '@/components/NavBar.vue'
|
|
|
import { API } from '@/config/index.js'
|
|
|
|
|
|
export default {
|
|
|
name: 'ShipManagerPage',
|
|
|
components: { NavBar },
|
|
|
data() {
|
|
|
return {
|
|
|
steps: ['基本信息', '船舶参数', '船检簿上传', '签名确认'],
|
|
|
currentStep: 1,
|
|
|
page1Img: '',
|
|
|
page2Img: '',
|
|
|
page3Img: '',
|
|
|
originalPage1Img: '',
|
|
|
originalPage2Img: '',
|
|
|
originalPage3Img: '',
|
|
|
signChecked: false,
|
|
|
signImg: '',
|
|
|
isSigning: false,
|
|
|
lastPoint: null,
|
|
|
canvasWidth: 0,
|
|
|
canvasHeight: 0,
|
|
|
hasDrawn: false,
|
|
|
hasSigned: false,
|
|
|
isEdit: false,
|
|
|
shipTypeEnum: {}, // 船型枚举
|
|
|
tonnageClassEnum: {}, // 载重吨位枚举
|
|
|
form: {
|
|
|
owner: 'sdf',
|
|
|
idCard: '510521198904074359',
|
|
|
phone: '12312312311',
|
|
|
shipNo: '111',
|
|
|
ton: '',
|
|
|
length: '',
|
|
|
width: '',
|
|
|
depth: '',
|
|
|
tonLevel: 'B',
|
|
|
shipType: '1',
|
|
|
page1FileId: '',
|
|
|
page2FileId: '',
|
|
|
page3FileId: ''
|
|
|
},
|
|
|
page1Desc: '',
|
|
|
page2Desc: '',
|
|
|
page3Desc: ''
|
|
|
}
|
|
|
},
|
|
|
onLoad(options) {
|
|
|
// 获取船舶属性枚举
|
|
|
this.fetchShipPropertyEnum().then(success => {
|
|
|
if (!success) {
|
|
|
// 如果获取失败,返回上一页
|
|
|
setTimeout(() => {
|
|
|
uni.navigateBack();
|
|
|
}, 1500);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (options.edit === '1' && options.ship) {
|
|
|
try {
|
|
|
const ship = JSON.parse(decodeURIComponent(options.ship));
|
|
|
this.isEdit = true;
|
|
|
// 填充表单
|
|
|
this.form.owner = ship.owner_name || '';
|
|
|
this.form.idCard = ship.id_card || '';
|
|
|
this.form.phone = ship.phone || '';
|
|
|
this.form.shipNo = ship.ship_number || '';
|
|
|
this.form.ton = ship.total_tonnage || '';
|
|
|
this.form.length = ship.total_length || '';
|
|
|
this.form.width = ship.total_width || '';
|
|
|
this.form.depth = ship.molded_depth || '';
|
|
|
this.form.tonLevel = ship.tonnage_class || '';
|
|
|
this.form.shipType = ship.ship_type || '';
|
|
|
this.form.page1FileId = ship.picture1 || '';
|
|
|
this.form.page2FileId = ship.picture2 || '';
|
|
|
this.form.page3FileId = ship.picture3 || '';
|
|
|
// 图片预览
|
|
|
this.page1Img = this.getFileUrl(ship.picture1);
|
|
|
this.page2Img = this.getFileUrl(ship.picture2);
|
|
|
this.page3Img = this.getFileUrl(ship.picture3);
|
|
|
this.originalPage1Img = this.page1Img;
|
|
|
this.originalPage2Img = this.page2Img;
|
|
|
this.originalPage3Img = this.page3Img;
|
|
|
// 签名图片(不自动填充,需用户重新签名)
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
});
|
|
|
},
|
|
|
methods: {
|
|
|
async fetchShipPropertyEnum() {
|
|
|
const token = uni.getStorageSync('token');
|
|
|
if (!token) {
|
|
|
uni.showToast({ title: '请先登录', icon: 'none' });
|
|
|
return false;
|
|
|
}
|
|
|
uni.showLoading({ title: '加载中...' });
|
|
|
try {
|
|
|
const res = await new Promise((resolve, reject) => {
|
|
|
uni.request({
|
|
|
url: API.SHIP_PROPERTY_ENUM,
|
|
|
method: 'GET',
|
|
|
data: { token },
|
|
|
success: resolve,
|
|
|
fail: reject
|
|
|
});
|
|
|
});
|
|
|
uni.hideLoading();
|
|
|
if (res.data && res.data.errcode === 0) {
|
|
|
const enumData = res.data.data;
|
|
|
this.shipTypeEnum = enumData.ship_type;
|
|
|
this.tonnageClassEnum = enumData.tonnage_class;
|
|
|
// 设置默认值
|
|
|
if (!this.isEdit) {
|
|
|
this.form.shipType = Object.values(this.shipTypeEnum)[0].toString();
|
|
|
this.form.tonLevel = Object.values(this.tonnageClassEnum)[0];
|
|
|
}
|
|
|
return true;
|
|
|
} else {
|
|
|
uni.showToast({ title: res.data.errmsg || '获取枚举失败', icon: 'none' });
|
|
|
return false;
|
|
|
}
|
|
|
} catch (error) {
|
|
|
uni.hideLoading();
|
|
|
uni.showToast({ title: error.message || '网络错误', icon: 'none' });
|
|
|
return false;
|
|
|
}
|
|
|
},
|
|
|
getFileUrl(fileId) {
|
|
|
if (!fileId) return '';
|
|
|
return `${API.BASE_URL}/api/customer/upload-file/preview?id=${fileId}`;
|
|
|
},
|
|
|
async uploadFile(filePath) {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const token = uni.getStorageSync('token')
|
|
|
if (!token) {
|
|
|
reject(new Error('未登录或登录已过期'))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
uni.uploadFile({
|
|
|
url: API.UPLOAD_FILE,
|
|
|
filePath: filePath,
|
|
|
name: 'file',
|
|
|
header: {
|
|
|
'Authorization': `Bearer ${token}`
|
|
|
},
|
|
|
success: (res) => {
|
|
|
if (res.statusCode === 200) {
|
|
|
const data = JSON.parse(res.data)
|
|
|
if (data.errcode && data.errcode !== 0) {
|
|
|
reject(new Error(data.errmsg || 'Upload failed'))
|
|
|
} else {
|
|
|
resolve(data)
|
|
|
}
|
|
|
} else {
|
|
|
reject(new Error('Upload failed'))
|
|
|
}
|
|
|
},
|
|
|
fail: (err) => {
|
|
|
reject(err)
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
},
|
|
|
async nextStep() {
|
|
|
if (this.currentStep === 1) {
|
|
|
// 基本信息校验
|
|
|
if (!this.form.owner) {
|
|
|
uni.showToast({ title: '请填写船舶所有人', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.idCard) {
|
|
|
uni.showToast({ title: '请填写身份证号', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!/^[1-9]\d{5}(18|19|20)?\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/.test(this.form.idCard)) {
|
|
|
uni.showToast({ title: '身份证号格式不正确', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.phone) {
|
|
|
uni.showToast({ title: '请填写联系电话', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!/^\d{11}$/.test(this.form.phone)) {
|
|
|
uni.showToast({ title: '联系电话格式不正确', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.shipNo) {
|
|
|
uni.showToast({ title: '请填写船舶编号', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
if (this.currentStep === 2) {
|
|
|
if (!this.form.ton) {
|
|
|
uni.showToast({ title: '请填写总吨位', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.length) {
|
|
|
uni.showToast({ title: '请填写总长度', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.width) {
|
|
|
uni.showToast({ title: '请填写总宽', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.depth) {
|
|
|
uni.showToast({ title: '请填写型深', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.tonLevel) {
|
|
|
uni.showToast({ title: '请选择参考载重吨位', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.form.shipType) {
|
|
|
uni.showToast({ title: '请选择船型', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
if (this.currentStep === 3) {
|
|
|
// 上传船检簿图片
|
|
|
if (!this.page1Img) {
|
|
|
uni.showToast({ title: '请上传第一页图片', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.page2Img) {
|
|
|
uni.showToast({ title: '请上传第二页图片', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.page3Img) {
|
|
|
uni.showToast({ title: '请上传第三页图片', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.page1Desc) {
|
|
|
uni.showToast({ title: '请填写第一页说明', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.page2Desc) {
|
|
|
uni.showToast({ title: '请填写第二页说明', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
if (!this.page3Desc) {
|
|
|
uni.showToast({ title: '请填写第三页说明', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
uni.showLoading({ title: '上传中...' });
|
|
|
|
|
|
// 检查第一页是否需要上传
|
|
|
if (this.page1Img !== this.originalPage1Img || !this.form.page1FileId) {
|
|
|
const page1Result = await this.uploadFile(this.page1Img);
|
|
|
this.form.page1FileId = page1Result.data.id;
|
|
|
}
|
|
|
|
|
|
// 检查第二页是否需要上传
|
|
|
if (this.page2Img !== this.originalPage2Img || !this.form.page2FileId) {
|
|
|
const page2Result = await this.uploadFile(this.page2Img);
|
|
|
this.form.page2FileId = page2Result.data.id;
|
|
|
}
|
|
|
|
|
|
// 检查第三页是否需要上传
|
|
|
if (this.page3Img !== this.originalPage3Img || !this.form.page3FileId) {
|
|
|
const page3Result = await this.uploadFile(this.page3Img);
|
|
|
this.form.page3FileId = page3Result.data.id;
|
|
|
}
|
|
|
|
|
|
uni.hideLoading();
|
|
|
} catch (error) {
|
|
|
uni.hideLoading();
|
|
|
uni.showToast({ title: error.message || '上传失败,请重试', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
if (this.currentStep < 4) this.currentStep++
|
|
|
},
|
|
|
async submit() {
|
|
|
// 验证签名确认
|
|
|
if (!this.signChecked) {
|
|
|
uni.showToast({ title: '请勾选承诺声明', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!this.hasSigned) {
|
|
|
uni.showToast({ title: '请完成签名', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
uni.showLoading({ title: '处理中...' });
|
|
|
const signBase64 = await this.getSignBase64();
|
|
|
console.log("signBase64",signBase64)
|
|
|
const token = uni.getStorageSync('token');
|
|
|
if (!token) {
|
|
|
uni.hideLoading();
|
|
|
uni.showToast({ title: '登录已失效', icon: 'none' });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 组装接口参数
|
|
|
const params = {
|
|
|
token,
|
|
|
owner_name: this.form.owner,
|
|
|
id_card: this.form.idCard,
|
|
|
phone: this.form.phone,
|
|
|
ship_number: this.form.shipNo,
|
|
|
total_tonnage: this.form.ton,
|
|
|
total_length: this.form.length,
|
|
|
total_width: this.form.width,
|
|
|
molded_depth: this.form.depth,
|
|
|
tonnage_class: this.form.tonLevel,
|
|
|
ship_type: this.form.shipType,
|
|
|
picture1: this.form.page1FileId,
|
|
|
picture2: this.form.page2FileId,
|
|
|
picture3: this.form.page3FileId,
|
|
|
signature: signBase64
|
|
|
};
|
|
|
|
|
|
// 提交表单
|
|
|
const res = await new Promise((resolve, reject) => {
|
|
|
uni.request({
|
|
|
url: API.SHIP_CREATE,
|
|
|
method: 'POST',
|
|
|
data: params,
|
|
|
header: {
|
|
|
'Content-Type': 'application/json'
|
|
|
},
|
|
|
success: resolve,
|
|
|
fail: reject
|
|
|
});
|
|
|
});
|
|
|
|
|
|
uni.hideLoading();
|
|
|
if (res.data && res.data.errcode === 0) {
|
|
|
uni.showToast({ title: '提交成功', icon: 'success' });
|
|
|
setTimeout(() => {
|
|
|
uni.navigateBack();
|
|
|
}, 800);
|
|
|
} else {
|
|
|
uni.showToast({ title: res.data.errmsg || '提交失败', icon: 'none' });
|
|
|
}
|
|
|
} catch (error) {
|
|
|
uni.hideLoading();
|
|
|
uni.showToast({ title: error.message || '提交失败,请重试', icon: 'none' });
|
|
|
}
|
|
|
},
|
|
|
prevStep() {
|
|
|
if (this.currentStep > 1) this.currentStep--
|
|
|
},
|
|
|
chooseImage(page) {
|
|
|
uni.chooseImage({
|
|
|
count: 1,
|
|
|
success: (res) => {
|
|
|
if (page === 'page1') {
|
|
|
this.page1Img = res.tempFilePaths[0]
|
|
|
this.originalPage1Img = res.tempFilePaths[0]
|
|
|
}
|
|
|
if (page === 'page2') {
|
|
|
this.page2Img = res.tempFilePaths[0]
|
|
|
this.originalPage2Img = res.tempFilePaths[0]
|
|
|
}
|
|
|
if (page === 'page3') {
|
|
|
this.page3Img = res.tempFilePaths[0]
|
|
|
this.originalPage3Img = res.tempFilePaths[0]
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
deleteImage(page) {
|
|
|
if (page === 'page1') {
|
|
|
this.page1Img = ''
|
|
|
this.originalPage1Img = ''
|
|
|
this.form.page1FileId = ''
|
|
|
}
|
|
|
if (page === 'page2') {
|
|
|
this.page2Img = ''
|
|
|
this.originalPage2Img = ''
|
|
|
this.form.page2FileId = ''
|
|
|
}
|
|
|
if (page === 'page3') {
|
|
|
this.page3Img = ''
|
|
|
this.originalPage3Img = ''
|
|
|
this.form.page3FileId = ''
|
|
|
}
|
|
|
},
|
|
|
viewExample() {
|
|
|
uni.showToast({ title: '查看示例', icon: 'none' })
|
|
|
},
|
|
|
startSign(e) {
|
|
|
this.isSigning = true;
|
|
|
this.hasSigned = true;
|
|
|
const ctx = uni.createCanvasContext('signCanvas', this);
|
|
|
const { x, y } = e.touches[0];
|
|
|
// 填充白底(只在第一次签名时填充)
|
|
|
if (!this.hasDrawn) {
|
|
|
ctx.setFillStyle('#fff');
|
|
|
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
|
|
this.hasDrawn = true;
|
|
|
}
|
|
|
ctx.moveTo(x, y);
|
|
|
ctx.setStrokeStyle('#222');
|
|
|
ctx.setLineWidth(4);
|
|
|
ctx.beginPath();
|
|
|
this.lastPoint = { x, y };
|
|
|
ctx.draw(true);
|
|
|
},
|
|
|
moveSign(e) {
|
|
|
if (!this.isSigning) return;
|
|
|
const ctx = uni.createCanvasContext('signCanvas', this);
|
|
|
const { x, y } = e.touches[0];
|
|
|
ctx.moveTo(this.lastPoint.x, this.lastPoint.y);
|
|
|
ctx.lineTo(x, y);
|
|
|
ctx.setStrokeStyle('#222');
|
|
|
ctx.setLineWidth(4);
|
|
|
ctx.stroke();
|
|
|
ctx.draw(true);
|
|
|
this.lastPoint = { x, y };
|
|
|
},
|
|
|
endSign() {
|
|
|
this.isSigning = false;
|
|
|
},
|
|
|
resetSign() {
|
|
|
this.signImg = '';
|
|
|
this.hasDrawn = false;
|
|
|
this.hasSigned = false;
|
|
|
const ctx = uni.createCanvasContext('signCanvas', this);
|
|
|
ctx.setFillStyle('#fff');
|
|
|
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
|
|
ctx.draw();
|
|
|
},
|
|
|
previewSign() {
|
|
|
uni.createSelectorQuery().select('#signCanvas').boundingClientRect(rect => {
|
|
|
uni.canvasToTempFilePath({
|
|
|
canvasId: 'signCanvas',
|
|
|
width: rect.width,
|
|
|
height: rect.height,
|
|
|
success: (res) => {
|
|
|
// this.signImg = res.tempFilePath
|
|
|
uni.previewImage({
|
|
|
urls: [res.tempFilePath]
|
|
|
})
|
|
|
},
|
|
|
fail: (err) => {
|
|
|
uni.showToast({ title: '签名生成失败', icon: 'none' })
|
|
|
}
|
|
|
}, this)
|
|
|
}).exec()
|
|
|
},
|
|
|
onTonLevelChange(e) {
|
|
|
this.form.tonLevel = e.detail.value
|
|
|
console.log(e.detail.value)
|
|
|
},
|
|
|
onShipTypeChange(e) {
|
|
|
this.form.shipType = e.detail.value
|
|
|
},
|
|
|
toggleSignChecked() {
|
|
|
this.signChecked = !this.signChecked;
|
|
|
},
|
|
|
// 获取签名base64
|
|
|
getSignBase64() {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
uni.canvasToTempFilePath({
|
|
|
canvasId: 'signCanvas',
|
|
|
success: (res) => {
|
|
|
// #ifdef H5
|
|
|
// H5 端直接用 base64
|
|
|
resolve(res.tempFilePath);
|
|
|
// #endif
|
|
|
|
|
|
// #ifndef H5
|
|
|
// 小程序端用 FileSystemManager 读取为 base64
|
|
|
if (typeof wx !== 'undefined' && wx.getFileSystemManager) {
|
|
|
wx.getFileSystemManager().readFile({
|
|
|
filePath: res.tempFilePath,
|
|
|
encoding: 'base64',
|
|
|
success: (fileRes) => {
|
|
|
resolve(fileRes.data);
|
|
|
},
|
|
|
fail: reject
|
|
|
});
|
|
|
} else {
|
|
|
// 其他端 fallback
|
|
|
resolve(res.tempFilePath);
|
|
|
}
|
|
|
// #endif
|
|
|
},
|
|
|
fail: reject
|
|
|
}, this);
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.reservation-page {
|
|
|
background: linear-gradient(180deg, #eaf3ff 0%, #f6faff 100%);
|
|
|
min-height: 100vh;
|
|
|
padding-bottom: 40rpx;
|
|
|
}
|
|
|
.fixed-nav {
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
z-index: 100;
|
|
|
background: linear-gradient(180deg, #cbe6ff 0%, #f6faff 100%);
|
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
|
|
|
}
|
|
|
.content-area {
|
|
|
padding-top: 90px;
|
|
|
}
|
|
|
.step-bar {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
justify-content: space-between;
|
|
|
width: 100%;
|
|
|
margin: 64rpx auto 0 auto;
|
|
|
padding: 0 32rpx;
|
|
|
position: relative;
|
|
|
}
|
|
|
.step-group {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
flex: 1;
|
|
|
position: relative;
|
|
|
}
|
|
|
.step-circle {
|
|
|
width: 60rpx;
|
|
|
height: 60rpx;
|
|
|
border-radius: 50%;
|
|
|
background: #e3eaf7;
|
|
|
color: #b0b8c6;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
font-size: 32rpx;
|
|
|
font-weight: bold;
|
|
|
transition: background 0.2s, color 0.2s;
|
|
|
z-index: 1;
|
|
|
}
|
|
|
.step-circle.active {
|
|
|
background: #fff;
|
|
|
color: #217aff;
|
|
|
border: 4rpx solid #217aff;
|
|
|
}
|
|
|
.step-label {
|
|
|
margin-top: 18rpx;
|
|
|
font-size: 24rpx;
|
|
|
color: #222;
|
|
|
font-weight: normal;
|
|
|
text-align: center;
|
|
|
min-width: 60rpx;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
.step-label.active {
|
|
|
color: #217aff;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
.step-line {
|
|
|
position: absolute;
|
|
|
top: 30rpx;
|
|
|
left: 50%;
|
|
|
width: 100%;
|
|
|
height: 4rpx;
|
|
|
background: #e3eaf7;
|
|
|
z-index: 0;
|
|
|
}
|
|
|
.info-card {
|
|
|
background: #fff;
|
|
|
border-radius: 24rpx;
|
|
|
margin: 0 32rpx;
|
|
|
padding: 32rpx 24rpx;
|
|
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
|
|
|
margin-top: 60rpx;
|
|
|
}
|
|
|
.info-title {
|
|
|
font-size: 28rpx;
|
|
|
font-weight: bold;
|
|
|
margin-bottom: 24rpx;
|
|
|
color: #222;
|
|
|
}
|
|
|
.info-form {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 18rpx;
|
|
|
}
|
|
|
.form-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
padding: 16rpx 0;
|
|
|
}
|
|
|
.form-label {
|
|
|
width: 180rpx;
|
|
|
font-size: 25rpx;
|
|
|
color: #222;
|
|
|
}
|
|
|
.form-label.required::before {
|
|
|
content: '*';
|
|
|
color: #ff5c5c;
|
|
|
margin-right: 6rpx;
|
|
|
}
|
|
|
.form-input {
|
|
|
flex: 1;
|
|
|
font-size: 25rpx;
|
|
|
color: #333;
|
|
|
border: none;
|
|
|
outline: none;
|
|
|
background: transparent;
|
|
|
}
|
|
|
.form-static {
|
|
|
flex: 1;
|
|
|
font-size: 25rpx;
|
|
|
color: #222;
|
|
|
text-align: left;
|
|
|
}
|
|
|
.step-btn-bar {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
gap: 32rpx;
|
|
|
margin: 64rpx 0 0 0;
|
|
|
margin-top: 100rpx;
|
|
|
}
|
|
|
.step-btn {
|
|
|
min-width: 270rpx;
|
|
|
height: 80rpx;
|
|
|
border-radius: 40rpx;
|
|
|
font-size: 32rpx;
|
|
|
font-weight: 500;
|
|
|
border: none;
|
|
|
outline: none;
|
|
|
background: #217aff;
|
|
|
color: #fff;
|
|
|
box-shadow: 0 4rpx 16rpx rgba(33,122,255,0.08);
|
|
|
transition: background 0.2s;
|
|
|
}
|
|
|
.step-btn.single-btn {
|
|
|
min-width: 420rpx;
|
|
|
}
|
|
|
.prev-btn {
|
|
|
background: #e3eaf7;
|
|
|
color: black;
|
|
|
}
|
|
|
.next-btn {
|
|
|
background: #217aff;
|
|
|
color: #fff;
|
|
|
}
|
|
|
.info-desc {
|
|
|
font-size: 20rpx;
|
|
|
color: #888;
|
|
|
margin-left: 12rpx;
|
|
|
font-weight: normal;
|
|
|
}
|
|
|
.form-unit {
|
|
|
font-size: 22rpx;
|
|
|
color: #888;
|
|
|
margin-left: 8rpx;
|
|
|
}
|
|
|
.form-tip {
|
|
|
font-size: 20rpx;
|
|
|
color: #b0b8c6;
|
|
|
margin-bottom: 8rpx;
|
|
|
margin-left: 180rpx;
|
|
|
}
|
|
|
.form-radio-group {
|
|
|
display: flex;
|
|
|
gap: 32rpx;
|
|
|
margin-left: 24rpx;
|
|
|
}
|
|
|
.form-radio {
|
|
|
font-size: 25rpx;
|
|
|
color: #222;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
.info-title-row {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 24rpx;
|
|
|
}
|
|
|
.example-btn {
|
|
|
background: #edf0f5;
|
|
|
color: #222;
|
|
|
border-radius: 40rpx;
|
|
|
font-size: 30rpx;
|
|
|
font-weight: 500;
|
|
|
padding: 18rpx 78rpx;
|
|
|
border: none;
|
|
|
height: 64rpx;
|
|
|
line-height: 1;
|
|
|
box-shadow: none;
|
|
|
margin-right: 20rpx;
|
|
|
}
|
|
|
.upload-section {
|
|
|
margin-bottom: 32rpx;
|
|
|
}
|
|
|
.upload-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 24rpx;
|
|
|
margin-top: 12rpx;
|
|
|
}
|
|
|
.upload-img-box {
|
|
|
position: relative;
|
|
|
width: 140rpx;
|
|
|
height: 140rpx;
|
|
|
background: #f0f0f0;
|
|
|
border-radius: 12rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
.upload-img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: cover;
|
|
|
border-radius: 12rpx;
|
|
|
}
|
|
|
.upload-add {
|
|
|
width: 140rpx;
|
|
|
height: 140rpx;
|
|
|
color: #fff;
|
|
|
font-size: 32rpx;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
background: #a6a8ab;
|
|
|
border-radius: 12rpx;
|
|
|
cursor: pointer;
|
|
|
font-weight: 500;
|
|
|
letter-spacing: 1rpx;
|
|
|
margin-bottom: 20rpx;
|
|
|
}
|
|
|
.upload-add text:first-child {
|
|
|
font-size: 48rpx;
|
|
|
}
|
|
|
.upload-add text:last-child {
|
|
|
font-size: 22rpx;
|
|
|
}
|
|
|
.upload-del {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
right: 0;
|
|
|
width: 36rpx;
|
|
|
height: 36rpx;
|
|
|
background: rgba(0,0,0,0.5);
|
|
|
color: #fff;
|
|
|
font-size: 28rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
border-radius: 0 12rpx 0 12rpx;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
.upload-desc {
|
|
|
flex: 1;
|
|
|
background: #f5f7fa;
|
|
|
color: #b0b8c6;
|
|
|
font-size: 24rpx;
|
|
|
border-radius: 12rpx;
|
|
|
padding: 0 24rpx;
|
|
|
min-height: 140rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
.divider {
|
|
|
height: 2rpx;
|
|
|
background: #f0f0f0;
|
|
|
margin: 32rpx 0;
|
|
|
}
|
|
|
.sign-declare-row {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
margin-bottom: 24rpx;
|
|
|
}
|
|
|
.sign-declare-text {
|
|
|
font-size: 25rpx;
|
|
|
color: #666;
|
|
|
margin-left: 16rpx;
|
|
|
line-height: 1.6;
|
|
|
}
|
|
|
.sign-area {
|
|
|
width: 100%;
|
|
|
min-height: 460rpx;
|
|
|
background: #f5f7fa;
|
|
|
border-radius: 16rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
margin-bottom: 32rpx;
|
|
|
position: relative;
|
|
|
}
|
|
|
.sign-placeholder {
|
|
|
color: #b0b8c6;
|
|
|
font-size: 28rpx;
|
|
|
}
|
|
|
.sign-img {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
object-fit: contain;
|
|
|
border-radius: 16rpx;
|
|
|
}
|
|
|
.sign-btn-bar {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
gap: 32rpx;
|
|
|
margin-top: 48rpx;
|
|
|
}
|
|
|
.sign-btn {
|
|
|
min-width: 220rpx;
|
|
|
height: 70rpx;
|
|
|
border-radius: 35rpx;
|
|
|
font-size: 28rpx;
|
|
|
font-weight: 500;
|
|
|
border: none;
|
|
|
outline: none;
|
|
|
transition: background 0.2s;
|
|
|
}
|
|
|
.reset-btn {
|
|
|
background: #f5f7fa;
|
|
|
color: #888;
|
|
|
}
|
|
|
.preview-btn {
|
|
|
background: #217aff;
|
|
|
color: #fff;
|
|
|
}
|
|
|
.sign-canvas {
|
|
|
width: 100%;
|
|
|
height: 460rpx;
|
|
|
background: transparent;
|
|
|
border-radius: 16rpx;
|
|
|
position: absolute;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
}
|
|
|
</style> |