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.

1140 lines
32 KiB

<template>
<view class="ship-detail-bg" :class="{ 'wechat-browser': isWeixinBrowser }">
<view class="fixed-nav" v-if="!isWeixinBrowser">
7 months ago
<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>
7 months ago
<input class="form-input" v-model="form.owner" placeholder="姓名与身份证一致" />
</view>
<view class="form-row">
<text class="form-label required">身份证号</text>
7 months ago
<input class="form-input" v-model="form.idCard" placeholder="输入正确的18位身份证号码" />
</view>
<view class="form-row">
<text class="form-label required">联系电话</text>
7 months ago
<input class="form-input" v-model="form.phone" placeholder="11位常用的手机号" />
</view>
<view class="form-row">
<text class="form-label required">船舶编号</text>
7 months ago
<input class="form-input" v-model="form.shipNo" placeholder="请输入船舶编号" />
</view>
6 months ago
<view class="form-row">
<text class="form-label required">船舶类型</text>
<view class="fee-type-group">
<view
v-for="(value, label) in feeTypeEnum"
:key="value.value"
class="fee-type-item"
>
<radio
class="form-radio"
6 months ago
:value="value.value"
:checked="form.feeType === value.value"
@tap="onFeeTypeChange({detail: {value: value.value}})"
6 months ago
/>
<view class="fee-type-info">
<text class="fee-type-label">{{ label }}</text>
<text class="fee-type-desc">{{ value.desc }}</text>
</view>
</view>
</view>
</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>
7 months ago
<input class="form-input" type="digit" v-model="form.ton" placeholder="请输入总吨位" />
<text class="form-unit"></text>
</view>
<!-- 总长度 -->
<view class="form-row">
<text class="form-label required">总长度</text>
7 months ago
<input class="form-input" type="digit" v-model="form.length" placeholder="请输入总长度" />
<text class="form-unit"></text>
</view>
<!-- 总宽 -->
<view class="form-row">
<text class="form-label required">总宽</text>
7 months ago
<input class="form-input" type="digit" v-model="form.width" placeholder="请输入总宽" />
<text class="form-unit"></text>
</view>
<!-- 型深 -->
<view class="form-row">
<text class="form-label required">型深</text>
7 months ago
<input class="form-input" type="digit" v-model="form.depth" placeholder="请输入型深" />
<text class="form-unit"></text>
</view>
<!-- 参考载重吨位 -->
<view class="form-row">
<text class="form-label required">参考载重吨位</text>
7 months ago
<radio-group class="form-radio-group" v-model="form.tonLevel" @change="onTonLevelChange">
7 months ago
<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-row">
<text class="form-label required">船型</text>
7 months ago
<radio-group class="form-radio-group" v-model="form.shipType" @change="onShipTypeChange">
7 months ago
<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>
<!-- 第三步船检簿上传 -->
<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>
6 months ago
<view class="upload-desc">第一页相关说明</view>
</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>
6 months ago
<view class="upload-desc">第二页相关说明</view>
7 months ago
</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>
6 months ago
<view class="upload-desc">第三页相关说明</view>
</view>
</view>
</view>
<!-- 第四步签名确认 -->
<view v-if="currentStep === 4" class="info-card">
<view class="info-title">签名确认</view>
<view class="sign-declare-row">
7 months ago
<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'
7 months ago
import { API } from '@/config/index.js'
export default {
name: 'ShipManagerPage',
components: { NavBar },
data() {
return {
steps: ['基本信息', '船舶参数', '船检簿上传', '签名确认'],
currentStep: 1,
page1Img: '',
page2Img: '',
7 months ago
page3Img: '',
originalPage1Img: '',
originalPage2Img: '',
originalPage3Img: '',
signChecked: false,
signImg: '',
isSigning: false,
lastPoint: null,
canvasWidth: 0,
canvasHeight: 0,
hasDrawn: false,
7 months ago
hasSigned: false,
isEdit: false,
7 months ago
shipTypeEnum: {}, // 船型枚举
tonnageClassEnum: {}, // 载重吨位枚举
6 months ago
feeTypeEnum: {}, // 船舶类型枚举
7 months ago
form: {
6 months ago
owner: '',
idCard: '',
phone: '',
shipNo: '',
feeType: 1, // 默认货船
7 months ago
ton: '',
length: '',
width: '',
depth: '',
tonLevel: 'B',
shipType: '1',
page1FileId: '',
page2FileId: '',
page3FileId: ''
},
isWeixinBrowser: false,
7 months ago
}
},
6 months ago
onLoad(options) {
7 months ago
// 获取船舶属性枚举
this.fetchShipPropertyEnum().then(success => {
if (!success) {
// 如果获取失败,返回上一页
setTimeout(() => {
uni.navigateBack();
}, 1500);
return;
}
6 months ago
if (options && options.edit === '1' && options.ship) {
7 months ago
try {
const ship = JSON.parse(decodeURIComponent(options.ship));
6 months ago
console.log("填充数据:",ship.fee_type)
7 months ago
this.isEdit = true;
// 填充表单
6 months ago
this.form.id = ship.id || '';
7 months ago
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 || '';
6 months ago
this.form.feeType = ship.fee_type || 1; // 默认货船
7 months ago
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) {}
}
});
// #ifdef H5
this.isWeixinBrowser = /MicroMessenger/i.test(navigator.userAgent)
// #endif
},
methods: {
7 months ago
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;
6 months ago
this.feeTypeEnum = enumData.fee_type;
7 months ago
// 设置默认值
if (!this.isEdit) {
this.form.shipType = Object.values(this.shipTypeEnum)[0].toString();
this.form.tonLevel = Object.values(this.tonnageClassEnum)[0];
6 months ago
this.form.feeType = 1; // 默认货船
7 months ago
}
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;
}
},
7 months ago
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',
7 months ago
formData: {
token: token
7 months ago
},
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)
}
})
})
},
7 months ago
// 添加数字校验方法
isValidNumber(value) {
if (!value) return false;
// 允许小数点的数字
return /^\d+(\.\d+)?$/.test(value);
},
7 months ago
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 (!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;
}
7 months ago
if (!this.isValidNumber(this.form.ton)) {
uni.showToast({ title: '总吨位必须是数字', icon: 'none' });
return;
}
7 months ago
if (!this.form.length) {
uni.showToast({ title: '请填写总长度', icon: 'none' });
return;
}
7 months ago
if (!this.isValidNumber(this.form.length)) {
uni.showToast({ title: '总长度必须是数字', icon: 'none' });
return;
}
7 months ago
if (!this.form.width) {
uni.showToast({ title: '请填写总宽', icon: 'none' });
return;
}
7 months ago
if (!this.isValidNumber(this.form.width)) {
uni.showToast({ title: '总宽必须是数字', icon: 'none' });
return;
}
7 months ago
if (!this.form.depth) {
uni.showToast({ title: '请填写型深', icon: 'none' });
return;
}
7 months ago
if (!this.isValidNumber(this.form.depth)) {
uni.showToast({ title: '型深必须是数字', icon: 'none' });
return;
}
7 months ago
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;
}
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++
},
7 months ago
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();
const token = uni.getStorageSync('token');
if (!token) {
uni.hideLoading();
uni.showToast({ title: '登录已失效', icon: 'none' });
return;
}
// 组装接口参数
const params = {
6 months ago
token: token,
7 months ago
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,
6 months ago
fee_type: this.form.feeType,
7 months ago
picture1: this.form.page1FileId,
picture2: this.form.page2FileId,
picture3: this.form.page3FileId,
signature: signBase64
};
6 months ago
// 编辑模式下调用更新接口
let url = API.SHIP_CREATE;
let method = 'POST';
if (this.isEdit && this.form.id) {
url = `${API.SHIP_UPDATE}/${encodeURIComponent(this.form.id)}`;
method = 'POST';
}
console.log('提交参数:', params, url);
7 months ago
7 months ago
// 提交表单
const res = await new Promise((resolve, reject) => {
uni.request({
6 months ago
url: url,
method: method,
7 months ago
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 {
7 months ago
console.log(res.data)
uni.showToast({ title: res.data.data || '提交失败', icon: 'none' });
7 months ago
}
} 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) => {
7 months ago
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) {
7 months ago
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) {
7 months ago
this.isSigning = true;
this.hasSigned = true;
const ctx = uni.createCanvasContext('signCanvas', this);
const { x, y } = e.touches[0];
// 填充白底(只在第一次签名时填充)
if (!this.hasDrawn) {
7 months ago
ctx.setFillStyle('#fff');
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
this.hasDrawn = true;
}
7 months ago
ctx.moveTo(x, y);
ctx.setStrokeStyle('#222');
ctx.setLineWidth(4);
this.lastPoint = { x, y };
6 months ago
ctx.beginPath();
7 months ago
ctx.draw(true);
},
moveSign(e) {
7 months ago
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() {
7 months ago
this.isSigning = false;
},
resetSign() {
7 months ago
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()
7 months ago
},
onTonLevelChange(e) {
this.form.tonLevel = e.detail.value
console.log(e.detail.value)
},
onShipTypeChange(e) {
this.form.shipType = e.detail.value
},
6 months ago
onFeeTypeChange(e) {
this.form.feeType = e.detail.value;
},
7 months ago
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>
7 months ago
<style lang="scss" scoped>
@import '@/styles/common.scss';
.ship-detail-bg {
min-height: 100vh;
background: linear-gradient(180deg, #cbe6ff 0%, #f6faff 100%);
padding-bottom: 32rpx;
}
.wechat-browser {
padding-top: 10rpx;
}
.wechat-browser .content-area {
padding-top: 0;
}
.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 {
7 months ago
@include font-primary;
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 {
7 months ago
width: 200rpx;
@include font-secondary;
color: #222;
}
.form-label.required::before {
content: '*';
color: #ff5c5c;
margin-right: 6rpx;
}
.form-input {
flex: 1;
6 months ago
@include font-secondary;
color: #333;
border: none;
outline: none;
background: transparent;
}
.form-static {
flex: 1;
7 months ago
@include font-secondary;
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;
7 months ago
@include font-secondary;
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;
6 months ago
display: flex;
align-items: center;
justify-content: center;
}
.step-btn.single-btn {
min-width: 420rpx;
}
.prev-btn {
background: #e3eaf7;
color: black;
7 months ago
border: none;
outline: none;
&::after {
border: none;
}
}
.next-btn {
background: #217aff;
color: #fff;
}
.info-desc {
7 months ago
@include font-tertiary;
color: #888;
margin-left: 12rpx;
font-weight: normal;
}
.form-unit {
7 months ago
@include font-tertiary;
color: #888;
margin-left: 8rpx;
}
.form-tip {
7 months ago
@include font-tertiary;
color: #b0b8c6;
margin-bottom: 8rpx;
margin-left: 180rpx;
}
.form-radio-group {
display: flex;
gap: 32rpx;
margin-left: 24rpx;
}
.form-radio {
7 months ago
@include font-secondary;
color: #222;
display: flex;
align-items: center;
}
6 months ago
/* 船舶类型特殊样式 */
.fee-type-group {
display: flex;
flex-direction: column;
gap: 24rpx;
margin-left: 24rpx;
width: 100%;
}
.fee-type-item {
display: flex;
align-items: flex-start;
width: 100%;
}
.fee-type-info {
display: flex;
flex-direction: column;
margin-left: 8rpx;
flex: 1;
}
.fee-type-label {
font-size: 28rpx;
color: #222;
}
.fee-type-desc {
font-size: 24rpx;
color: #888;
margin-top: 4rpx;
}
.info-title-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.example-btn {
background: #edf0f5;
7 months ago
@include font-secondary;
color: #222;
border-radius: 40rpx;
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;
7 months ago
@include font-secondary;
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 {
7 months ago
@include font-tertiary;
}
.upload-del {
position: absolute;
top: 0;
right: 0;
width: 36rpx;
height: 36rpx;
background: rgba(0,0,0,0.5);
color: #fff;
7 months ago
@include font-tertiary;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 12rpx 0 12rpx;
cursor: pointer;
}
.upload-desc {
flex: 1;
background: #f5f7fa;
7 months ago
@include font-tertiary;
color: #b0b8c6;
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 {
7 months ago
@include font-secondary;
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 {
7 months ago
@include font-tertiary;
color: #b0b8c6;
}
.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;
7 months ago
@include font-secondary;
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;
}
7 months ago
// 统一设置占位文字样式
::v-deep input::placeholder {
font-size: 24rpx !important;
color: #b0b8c6;
}
</style>