1、车船模块fix bug

2、运力模块fix bug
3、新增设备台账
4、其他bug 修复
5、调整组织、人员模块
This commit is contained in:
2026-08-07 08:28:21 +08:00
parent 1e4de76978
commit 6a02e9126b
37 changed files with 2593 additions and 645 deletions

View File

@@ -44,14 +44,17 @@
批量删除
</el-button>
</template>
<template #menu-right>
<el-radio-group v-model="query.expireStatus" class="ship-stat" @change="handleExpireChange">
<el-radio-button label="">全部{{ expiryStat.total }}</el-radio-button>
<el-radio-button label="within30"
>30天内到期{{ expiryStat.within30 }}</el-radio-button
<template #expireStatus-search>
<div class="transport-ship-page__expiry-tags">
<el-check-tag
v-for="item in expiryTagOptions"
:key="item.value"
:checked="query.expireStatus === item.value"
@change="handleExpireChange(item.value)"
>
<el-radio-button label="expired">已到期{{ expiryStat.expired }}</el-radio-button>
</el-radio-group>
{{ item.label }}{{ expiryStat[item.statKey] || 0 }}
</el-check-tag>
</div>
</template>
<template #shipName="{ row }">
<el-button type="primary" link @click="openShip(row, true)">
@@ -69,14 +72,22 @@
</span>
</template>
<template #safeManningCertEndDate="{ row }">
<span :class="getDateClass(row, 'safeManningCertEndDate', 'safeManningCertLongTerm')">
<span
:class="
getCertificateExpiryClass(row, 'safeManningCertEndDate', 'safeManningCertLongTerm')
"
>
{{ formatEndDate(row.safeManningCertEndDate, row.safeManningCertLongTerm) }}
</span>
</template>
<template #businessTransportCertEndDate="{ row }">
<span
:class="
getDateClass(row, 'businessTransportCertEndDate', 'businessTransportCertLongTerm')
getCertificateExpiryClass(
row,
'businessTransportCertEndDate',
'businessTransportCertLongTerm'
)
"
>
{{ formatEndDate(row.businessTransportCertEndDate, row.businessTransportCertLongTerm) }}
@@ -94,7 +105,7 @@
<el-link
type="primary"
v-if="hasPermission('transport_ship_view')"
@click="openShip(row, true)"
@click="openShipLedger(row)"
>
综合台账
</el-link>
@@ -108,6 +119,8 @@
</template>
</avue-crud>
<ship-ledger v-model="shipLedgerBox" :ship="ledgerShip" />
<el-dialog
:title="dialogTitle"
append-to-body
@@ -146,7 +159,7 @@
</el-col>
<el-col :span="8">
<el-form-item label="船名" prop="shipName">
<el-input v-model="shipForm.shipName" maxlength="50" show-word-limit />
<el-input v-model="shipForm.shipName" maxlength="20" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="8">
@@ -172,64 +185,64 @@
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="总长" prop="totalLength">
<el-input-number
<el-input
v-model="shipForm.totalLength"
:min="0"
:precision="2"
:controls="false"
/>
@input="normalizeNonNegativeDecimal('totalLength')"
>
<template #suffix>m</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="船宽" prop="shipWidth">
<el-input-number
<el-input
v-model="shipForm.shipWidth"
:min="0"
:precision="2"
:controls="false"
/>
@input="normalizeNonNegativeDecimal('shipWidth')"
>
<template #suffix>m</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="型深" prop="moldedDepth">
<el-input-number
<el-input
v-model="shipForm.moldedDepth"
:min="0"
:precision="2"
:controls="false"
/>
@input="normalizeNonNegativeDecimal('moldedDepth')"
>
<template #suffix>m</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="最大船高" prop="maxShipHeight">
<el-input-number
<el-input
v-model="shipForm.maxShipHeight"
:min="0"
:precision="2"
:controls="false"
/>
@input="normalizeNonNegativeDecimal('maxShipHeight')"
>
<template #suffix>m</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="空载吃水" prop="lightDraft">
<el-input-number
<el-input
v-model="shipForm.lightDraft"
:min="0"
:precision="2"
:controls="false"
/>
@input="normalizeNonNegativeDecimal('lightDraft')"
>
<template #suffix>t</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="满载吃水" prop="fullLoadDraft">
<el-input-number
<el-input
v-model="shipForm.fullLoadDraft"
:min="0"
:precision="2"
:controls="false"
/>
@input="normalizeNonNegativeDecimal('fullLoadDraft')"
>
<template #suffix>t</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
@@ -349,7 +362,12 @@
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="船舶类型" prop="shipType">
<el-input v-model="shipForm.shipType" maxlength="50" />
<el-select v-model="shipForm.shipType" placeholder="请选择船舶类型" clearable>
<el-option label="散货船" value="散货船" />
<el-option label="集装箱船" value="集装箱船" />
<el-option label="杂货船" value="杂货船" />
<el-option label="油船" value="油船" />
</el-select>
</el-form-item>
</el-col>
</el-row>
@@ -577,6 +595,7 @@ import { getToken } from '@/utils/auth';
import { getUploadHeaders } from '@/utils/upload';
import { downloadXls } from '@/utils/util';
import ImageUploadField from '@/components/image-upload-field/main.vue';
import ShipLedger from './components/ship-ledger.vue';
const emptyForm = () => ({
id: '',
@@ -626,7 +645,7 @@ const emptyForm = () => ({
});
export default {
components: { ImageUploadField, InfoFilled },
components: { ImageUploadField, InfoFilled, ShipLedger },
data() {
return {
option,
@@ -641,13 +660,18 @@ export default {
page: { pageSize: 10, currentPage: 1, total: 0 },
expiryStat: { total: 0, within30: 0, expired: 0 },
shipBox: false,
shipLedgerBox: false,
ledgerShip: {},
readonly: false,
shipForm: emptyForm(),
uploadHeaders: getUploadHeaders(),
organizationOptions: [],
formRules: {
organizationName: [{ required: true, message: '请选择船舶所属组织', trigger: 'change' }],
shipName: [{ required: true, message: '请输入船名', trigger: 'blur' }],
shipName: [
{ required: true, message: '请输入船名', trigger: 'blur' },
{ max: 20, message: '船名不能超过20个字符', trigger: 'blur' },
],
keelLayingDate: [{ validator: this.validateKeelCompletionDate, trigger: 'change' }],
shipIdentifierNo: [{ required: true, message: '请输入船舶识别号', trigger: 'blur' }],
totalLength: [{ validator: this.validateNonNegativeField, trigger: 'change' }],
@@ -663,7 +687,7 @@ export default {
grossTonnage: [{ required: true, message: '请输入总吨', trigger: 'change' }],
netTonnage: [{ required: true, message: '请输入净吨', trigger: 'change' }],
shipInspectionNo: [{ required: true, message: '请输入船检登记号', trigger: 'blur' }],
shipType: [{ required: true, message: '请输入船舶类型', trigger: 'blur' }],
shipType: [{ required: true, message: '请选择船舶类型', trigger: 'change' }],
nationalityCertStartDate: [
{ validator: this.validateNationalityCertDate, trigger: 'change' },
],
@@ -704,6 +728,13 @@ export default {
if (this.readonly) return '船舶综合台账';
return this.shipForm.id ? '修改船舶' : '新增船舶';
},
expiryTagOptions() {
return [
{ label: '全部', value: '', statKey: 'total' },
{ label: '30天内到期', value: 'within30', statKey: 'within30' },
{ label: '已到期', value: 'expired', statKey: 'expired' },
];
},
},
created() {
this.initDeptTree();
@@ -717,6 +748,9 @@ export default {
this.organizationOptions = this.flattenDeptOptions(res.data.data || []);
const column = this.findColumn(this.option.column, 'organizationName');
column.dicData = this.organizationOptions;
if (this.shipBox && !this.shipForm.id && !this.shipForm.organizationName) {
this.shipForm.organizationName = this.getCurrentOrganizationName();
}
});
},
flattenDeptOptions(tree = [], level = 0) {
@@ -726,6 +760,8 @@ export default {
result.push({
label: `${' '.repeat(level)}${label}`,
value: label,
rawLabel: label,
id: item.id || item.value,
});
if (item.children && item.children.length) {
result.push(...this.flattenDeptOptions(item.children, level + 1));
@@ -733,9 +769,22 @@ export default {
});
return result;
},
getCurrentOrganizationName() {
const currentDeptId = this.userInfo.deptId || this.userInfo.dept_id;
const currentDept = this.organizationOptions.find(
item => String(item.id) === String(currentDeptId)
);
return currentDept?.rawLabel || this.userInfo.deptName || this.userInfo.dept_name || '';
},
validateField(prop) {
this.$refs.shipForm?.validateField(prop);
},
normalizeNonNegativeDecimal(prop) {
const value = String(this.shipForm[prop] ?? '').replace(/[^\d.]/g, '');
const [integerPart = '', ...decimalParts] = value.split('.');
const decimalPart = decimalParts.join('').slice(0, 2);
this.shipForm[prop] = decimalParts.length ? `${integerPart}.${decimalPart}` : integerPart;
},
validateNonNegativeField(rule, value, callback) {
if (value !== undefined && value !== null && value < 0) {
callback(new Error('数值不能小于0'));
@@ -818,6 +867,7 @@ export default {
this.readonly = readonly;
if (!row?.id) {
this.shipForm = emptyForm();
this.shipForm.organizationName = this.getCurrentOrganizationName();
this.shipBox = true;
return;
}
@@ -826,6 +876,10 @@ export default {
this.shipBox = true;
});
},
openShipLedger(row) {
this.ledgerShip = { ...row };
this.shipLedgerBox = true;
},
resetShip() {
this.shipForm = emptyForm();
this.readonly = false;
@@ -881,7 +935,8 @@ export default {
});
});
},
handleExpireChange() {
handleExpireChange(expireStatus) {
this.query.expireStatus = expireStatus;
this.page.currentPage = 1;
this.onLoad(this.page);
},
@@ -977,14 +1032,28 @@ export default {
if (endDate.diff(today, 'day') <= 30) return 'date-warning';
return '';
},
getCertificateExpiryClass(row, dateProp, longTermProp) {
if (row[longTermProp] === 1 || !row[dateProp]) return '';
const endDate = this.$dayjs(row[dateProp]);
return endDate.isValid() && endDate.diff(this.$dayjs(), 'day') <= 30 ? 'date-danger' : '';
},
},
};
</script>
<style lang="scss" scoped>
.transport-ship-page {
.ship-stat {
flex-shrink: 0;
&__expiry-tags {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
width: 100%;
:deep(.el-check-tag) {
margin: 0;
white-space: nowrap;
}
}
:deep(.el-table th .cell),