1、大量页面调整

2、完善运力模块
3、完善规范、需求卡
This commit is contained in:
2026-07-31 01:32:41 +08:00
parent ccaca521d3
commit 81b2a3e99b
54 changed files with 5641 additions and 1965 deletions

View File

@@ -1,20 +1,669 @@
<template>
<business-crud-page :api="api" :config="config" :crud-option="option" />
<basic-container class="common-route-page">
<avue-crud
:option="option"
:table-loading="loading"
:data="data"
v-model:page="page"
v-model="form"
ref="crud"
:permission="permissionList"
:before-open="beforeOpen"
@row-save="rowSave"
@row-update="rowUpdate"
@row-del="rowDel"
@search-change="searchChange"
@search-reset="searchReset"
@selection-change="selectionChange"
@current-change="currentChange"
@size-change="sizeChange"
@refresh-change="refreshChange"
@on-load="onLoad"
>
<template #menu-left>
<el-button
type="primary"
icon="el-icon-plus"
plain
v-if="hasPermission('common_route_add')"
@click="$refs.crud.rowAdd()"
>新增
</el-button>
<el-button
type="primary"
icon="el-icon-download"
plain
v-if="config.exportUrl && hasPermission('common_route_export')"
@click="handleExport"
>批量导出
</el-button>
<el-button
type="danger"
icon="el-icon-delete"
plain
v-if="hasPermission('common_route_delete')"
@click="handleDelete"
>批量删除
</el-button>
</template>
<template #departureAddress-form>
<div class="common-route-page__route-address">
<el-input
v-model="form.departureName"
readonly
placeholder="自动带出"
:disabled="dialogReadonly"
/>
<el-input
v-model="form.departureAddress"
readonly
placeholder="请输入并选择地址"
suffix-icon="el-icon-location"
:disabled="dialogReadonly"
/>
<el-tooltip content="选择常用地址" placement="top">
<el-button
type="primary"
:icon="List"
link
:disabled="dialogReadonly"
@click="openAddressDialog('departure')"
/>
</el-tooltip>
</div>
</template>
<template #arrivalAddress-form>
<div class="common-route-page__route-address">
<el-input
v-model="form.arrivalName"
readonly
placeholder="自动带出"
:disabled="dialogReadonly"
/>
<el-input
v-model="form.arrivalAddress"
readonly
placeholder="请输入并选择地址"
suffix-icon="el-icon-location"
:disabled="dialogReadonly"
/>
<el-tooltip content="选择常用地址" placement="top">
<el-button
type="primary"
:icon="List"
link
:disabled="dialogReadonly"
@click="openAddressDialog('arrival')"
/>
</el-tooltip>
</div>
</template>
<template #menu="{ row, index }">
<el-button
type="primary"
text
icon="el-icon-view"
v-if="hasPermission('common_route_view')"
@click="$refs.crud.rowView(row, index)"
>
查看
</el-button>
<el-button
type="primary"
text
icon="el-icon-edit"
v-if="hasPermission('common_route_edit') && !row.readonly"
@click="$refs.crud.rowEdit(row, index)"
>
编辑
</el-button>
<el-button
type="primary"
text
icon="el-icon-delete"
v-if="hasPermission('common_route_delete') && !row.readonly"
@click="rowDel(row)"
>
删除
</el-button>
</template>
</avue-crud>
<empty-pagination
:page="page"
@size-change="sizeChange"
@current-change="currentChange"
@load="onLoad(page, query)"
/>
<el-dialog
title="选择常用地址"
append-to-body
v-model="addressDialogVisible"
width="1080px"
class="common-route-page__address-dialog"
@opened="loadAddressList"
>
<div class="common-route-page__address-search">
<el-form :model="addressQuery" label-position="right" label-width="86px">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="地址名称">
<el-input v-model="addressQuery.addressName" clearable placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="地址类型">
<el-select
v-model="addressQuery.addressType"
clearable
placeholder="请选择"
style="width: 100%"
>
<el-option
v-for="item in addressTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="详细地址">
<el-input v-model="addressQuery.detailAddress" clearable placeholder="请输入" />
</el-form-item>
</el-col>
<el-col :span="6" class="common-route-page__address-search-actions">
<el-button type="primary" @click="handleAddressSearch">查询</el-button>
<el-button @click="handleAddressReset">重置</el-button>
</el-col>
<el-col :span="6">
<el-form-item label="组织">
<el-select
v-model="addressQuery.deptId"
clearable
filterable
placeholder="请选择"
style="width: 100%"
>
<el-option
v-for="item in deptOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<el-table
border
:data="addressData"
v-loading="addressLoading"
height="310"
highlight-current-row
@row-click="selectAddressRow"
>
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column prop="addressName" label="地址名称" min-width="150" />
<el-table-column prop="addressCode" label="地址编号" min-width="150" />
<el-table-column prop="addressType" label="类型" min-width="150" />
<el-table-column label="站点编码" min-width="170">
<template #default="{ row }">
{{ row.addressType === '常规地址' ? '/' : row.siteCodeDisplay || row.siteCode || '' }}
</template>
</el-table-column>
<el-table-column prop="detailAddress" label="详细地址" min-width="220" />
<el-table-column label="操作" width="120" fixed="right">
<template #default="{ row }">
<el-button type="primary" text @click.stop="selectAddressRow(row)">选择</el-button>
</template>
</el-table-column>
</el-table>
<div class="common-route-page__address-pagination">
<el-pagination
v-model:current-page="addressPage.currentPage"
v-model:page-size="addressPage.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, prev, pager, next, sizes"
:total="addressPage.total"
@size-change="handleAddressSizeChange"
@current-change="handleAddressCurrentChange"
/>
</div>
<template #footer>
<el-button @click="addressDialogVisible = false">关闭</el-button>
<el-button type="primary" @click="confirmAddressPick">确认</el-button>
</template>
</el-dialog>
</basic-container>
</template>
<script>
import BusinessCrudPage from './components/business-crud-page.vue';
import * as api from '@/api/business/common-route';
import { getList as getCommonAddressList } from '@/api/base/common-address';
import { exportBlob } from '@/api/common';
import { getDeptTree } from '@/api/system/dept';
import { addressTypeOptions } from '@/option/base/common-address';
import { config, option } from '@/option/business/common-route';
import { getToken } from '@/utils/auth';
import { downloadXls } from '@/utils/util';
import { List } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
export default {
components: { BusinessCrudPage },
data() {
return {
api,
List,
config,
option,
form: {},
query: {},
loading: true,
data: [],
dialogReadonly: false,
addressTypeOptions,
addressDialogVisible: false,
addressTarget: '',
addressQuery: {},
addressData: [],
addressLoading: false,
addressSelected: null,
deptOptions: [],
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
currentPage: 1,
total: 0,
},
addressPage: {
pageSize: 10,
currentPage: 1,
total: 0,
},
selectionList: [],
};
},
computed: {
...mapGetters(['permission', 'userInfo']),
permissionList() {
return {
addBtn: this.hasPermission('common_route_add'),
};
},
isAdmin() {
const authority = this.userInfo && this.userInfo.authority;
return Array.isArray(authority)
? authority.includes('admin')
: String(authority || '').includes('admin');
},
ids() {
return this.selectionList.map(item => item.id).join(',');
},
},
created() {
this.initDeptTree();
},
methods: {
hasPermission(code) {
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
},
initDeptTree() {
getDeptTree(this.userInfo.tenantId).then(res => {
this.deptOptions = this.flattenDept(res.data.data || []);
});
},
flattenDept(tree, level = 0) {
const result = [];
tree.forEach(item => {
result.push({
label: `${' '.repeat(level)}${item.title || item.deptName || item.name}`,
value: item.id,
});
if (item.children && item.children.length) {
result.push(...this.flattenDept(item.children, level + 1));
}
});
return result;
},
defaultDeptId() {
return (this.userInfo && (this.userInfo.deptId || this.userInfo.dept_id)) || '';
},
resetAddressQuery() {
this.addressQuery = {
addressName: '',
addressType: '',
detailAddress: '',
deptId: this.defaultDeptId(),
};
},
openAddressDialog(target) {
if (this.dialogReadonly) return;
this.addressTarget = target;
this.addressSelected = null;
this.addressPage.currentPage = 1;
this.resetAddressQuery();
this.addressDialogVisible = true;
},
handleAddressSearch() {
this.addressPage.currentPage = 1;
this.loadAddressList();
},
handleAddressReset() {
this.addressPage.currentPage = 1;
this.resetAddressQuery();
this.loadAddressList();
},
handleAddressSizeChange(pageSize) {
this.addressPage.pageSize = pageSize;
this.addressPage.currentPage = 1;
this.loadAddressList();
},
handleAddressCurrentChange(currentPage) {
this.addressPage.currentPage = currentPage;
this.loadAddressList();
},
loadAddressList() {
this.addressLoading = true;
getCommonAddressList(this.addressPage.currentPage, this.addressPage.pageSize, {
...this.addressQuery,
allDept: 0,
})
.then(res => {
const data = res.data.data || {};
this.addressPage.total = data.total || 0;
this.addressData = data.records || [];
})
.finally(() => {
this.addressLoading = false;
});
},
selectAddressRow(row) {
this.addressSelected = row;
},
confirmAddressPick() {
if (!this.addressSelected) {
this.$message.warning('请选择常用地址');
return;
}
this.applyAddress(this.addressTarget, this.addressSelected);
this.addressDialogVisible = false;
},
applyAddress(target, address) {
if (!target || !address) return;
const prefix = target === 'departure' ? 'departure' : 'arrival';
this.form[`${prefix}Name`] = address.addressName || '';
this.form[`${prefix}Address`] = address.detailAddress || '';
this.form[`${prefix}AddressCode`] = address.addressCode || '';
this.form[`${prefix}SiteCode`] =
address.addressType === '常规地址'
? '/'
: address.siteCodeDisplay || address.siteCode || '';
this.form[`${prefix}Longitude`] = address.longitude || '';
this.form[`${prefix}Latitude`] = address.latitude || '';
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
},
normalizeRow(row) {
const submitRow = { ...row };
Object.keys(submitRow).forEach(key => {
if (typeof submitRow[key] === 'string') {
submitRow[key] = submitRow[key].trim();
}
});
return submitRow;
},
validateRow(row) {
if (!row.routeName) {
this.$message.warning('请输入线路名称');
return false;
}
if (row.routeName.length > 100) {
this.$message.warning('线路名称不能超过100个字符');
return false;
}
if (!row.departureName || !row.departureAddress) {
this.$message.warning('请选择发货地址');
return false;
}
if (!row.arrivalName || !row.arrivalAddress) {
this.$message.warning('请选择收货地址');
return false;
}
if (row.departureAddress.length > 255) {
this.$message.warning('发货地址不能超过255个字符');
return false;
}
if (row.arrivalAddress.length > 255) {
this.$message.warning('收货地址不能超过255个字符');
return false;
}
if (row.remark && row.remark.length > 500) {
this.$message.warning('备注不能超过500个字符');
return false;
}
return true;
},
stopSubmitLoading(loading) {
if (typeof loading === 'function') {
loading();
}
},
rowSave(row, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateRow(submitRow)) {
this.stopSubmitLoading(loading);
return;
}
api.submit(submitRow).then(
() => {
this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' });
done();
},
error => {
window.console.log(error);
this.stopSubmitLoading(loading);
}
);
},
rowUpdate(row, index, done, loading) {
const submitRow = this.normalizeRow(row);
if (!this.validateRow(submitRow)) {
this.stopSubmitLoading(loading);
return;
}
api.submit(submitRow).then(
() => {
this.onLoad(this.page);
this.$message({ type: 'success', message: '操作成功!' });
done();
},
error => {
window.console.log(error);
this.stopSubmitLoading(loading);
}
);
},
rowDel(row) {
this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => api.remove(row.id))
.then(res => {
this.afterRemove(res.data.data);
});
},
handleDelete() {
if (this.selectionList.length === 0) {
this.$message.warning('请选择至少一条数据');
return;
}
const selection = this.selectionList.filter(item => !item.readonly);
if (!selection.length) {
this.$message.warning('所选数据均为只读数据');
return;
}
this.$confirm('确定将选择数据删除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => api.remove(selection.map(item => item.id).join(',')))
.then(res => {
this.afterRemove(res.data.data);
});
},
afterRemove(result = {}) {
this.onLoad(this.page);
const successCount = result.successCount || 0;
const skippedCount = result.skippedCount || 0;
if (skippedCount) {
this.$message.warning(`成功删除${successCount}条,因业务引用跳过${skippedCount}`);
} else {
this.$message({ type: 'success', message: '删除成功!' });
}
this.selectionClear();
},
beforeOpen(done, type) {
this.dialogReadonly = type === 'view';
if (type === 'add') {
this.form = {};
done();
return;
}
if (['edit', 'view'].includes(type)) {
api.getDetail(this.form.id).then(res => {
this.form = res.data.data || {};
done();
});
return;
}
done();
},
searchReset() {
this.query = {};
this.page.currentPage = 1;
this.onLoad(this.page);
},
searchChange(params, done) {
this.query = params;
this.page.currentPage = 1;
this.onLoad(this.page, params);
done();
},
selectionChange(list) {
this.selectionList = list;
},
selectionClear() {
this.selectionList = [];
if (this.$refs.crud) {
this.$refs.crud.toggleSelection();
}
},
currentChange(currentPage) {
this.page.currentPage = currentPage;
},
sizeChange(pageSize) {
this.page.pageSize = pageSize;
},
refreshChange() {
this.onLoad(this.page, this.query);
},
onLoad(page, params = {}) {
this.loading = true;
api
.getList(page.currentPage, page.pageSize, { ...params, ...this.query, allDept: 0 })
.then(res => {
const result = res.data.data;
this.page.total = result.total;
this.data = result.records;
this.selectionClear();
})
.finally(() => {
this.loading = false;
});
},
buildExportParams() {
return {
...this.query,
allDept: 0,
ids: this.ids,
[this.website.tokenHeader]: getToken(),
};
},
handleExport() {
this.$confirm(`是否导出${config.exportName}?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
NProgress.start();
exportBlob(config.exportUrl, this.buildExportParams())
.then(res => {
downloadXls(
res.data,
`${config.exportName}${this.$dayjs().format('YYYY-MM-DD HH:mm:ss')}.xlsx`
);
})
.finally(() => {
NProgress.done();
});
});
},
},
};
</script>
<style lang="scss" scoped>
.common-route-page {
&__route-address {
display: flex;
gap: 10px;
align-items: center;
.el-input {
flex: 1;
min-width: 0;
}
.el-input:first-child {
flex: 0 0 34%;
}
.el-button {
flex: 0 0 40px;
font-size: 30px;
}
}
&__address-search {
padding: 4px 8px 10px;
}
&__address-search-actions {
display: flex;
align-items: flex-start;
justify-content: flex-end;
}
&__address-pagination {
display: flex;
justify-content: flex-end;
margin-top: 14px;
}
}
</style>