fix: 更新搜索栏展示问题,以及销售订单的一些问题。

This commit is contained in:
c
2026-03-18 15:30:04 +08:00
parent 88aef44583
commit bf83b5e3b6
8 changed files with 181 additions and 85 deletions

View File

@@ -49,5 +49,5 @@ const i18nLocale = computed(() => {
return document.documentElement.lang === "zh-CN" ? zhCn : en;
});
if (isFunction(log.success)) log.success(__APP_INFO__.pkg.version, "欢迎使用 Teek Design Vue3 系统");
if (isFunction(log.success)) log.success(__APP_INFO__.pkg.version, "欢迎使用牛安管理系统");
</script>

View File

@@ -21,7 +21,15 @@ export const UserService = {
},
checkIsLogined() {
return get("/auth/user/me");
return get("/open/check-login");
},
/**
* 检测登录状态
* 用于前端页面刷新后检测当前token是否有效
*/
checkLogin() {
return get("/open/check-login");
},
// 获取用户信息

View File

@@ -21,6 +21,11 @@ const props = defineProps({
});
const emit = defineEmits<Emits>();
const tableMainRef = ref<InstanceType<typeof TableMain> | null>(null);
const tableHeaderEl = ref<HTMLElement | null>(null);
const tableMainHostEl = ref<HTMLElement | null>(null);
const headerBaseHeight = ref(0);
const headerExtraOffset = ref(0);
const tableMainHeight = ref(0);
const page = ref(1);
const pageSize = ref(30);
const total = ref(0);
@@ -62,6 +67,15 @@ const handleExpandChange = (row: any, expandedRows: any[]) => {
emit("expand-change", row, expandedRows);
};
const recomputeTableHeight = () => {
if (!tableMainHostEl.value) return;
const top = tableMainHostEl.value.getBoundingClientRect().top;
// 预留一点底部空隙,避免贴边导致分页视觉上“出屏”
const paddingBottom = 12;
const h = Math.floor(window.innerHeight - top - paddingBottom);
tableMainHeight.value = Math.max(240, h);
};
defineExpose({
reload: loadData,
tableMainRef,
@@ -73,31 +87,59 @@ defineExpose({
},
});
onMounted(loadData);
onMounted(async () => {
await loadData();
await nextTick();
if (tableHeaderEl.value) {
headerBaseHeight.value = tableHeaderEl.value.getBoundingClientRect().height;
}
recomputeTableHeight();
if (tableHeaderEl.value && "ResizeObserver" in window) {
const ro = new ResizeObserver(entries => {
const h = entries[0]?.contentRect?.height ?? 0;
headerExtraOffset.value = Math.max(0, Math.round(h - headerBaseHeight.value));
// 表头换行会影响 TableMain 的 top需要同步重算可用高度
requestAnimationFrame(recomputeTableHeight);
});
ro.observe(tableHeaderEl.value);
onBeforeUnmount(() => ro.disconnect());
}
window.addEventListener("resize", recomputeTableHeight);
onBeforeUnmount(() => window.removeEventListener("resize", recomputeTableHeight));
});
</script>
<template>
<TableHeader
v-if="searchers !== undefined"
:searchers="searchers"
:tool-buttons="toolButtons"
v-model:searcher-params="searcherParams"
:search-click="searchClick"
>
<template #tool-button><slot name="tool-button"></slot></template>
</TableHeader>
<TableMain
ref="tableMainRef"
:data="tableData"
:total="total"
:update-page="updatePage"
v-model:page="page"
v-model:page-size="pageSize"
:expand-row-keys="expandRowKeys"
:row-key="rowKey"
@expand-change="handleExpandChange"
>
<template #columns><slot name="columns"></slot></template>
</TableMain>
<div ref="tableHeaderEl">
<TableHeader
v-if="searchers !== undefined"
:searchers="searchers"
:tool-buttons="toolButtons"
v-model:searcher-params="searcherParams"
:search-click="searchClick"
>
<template #tool-button><slot name="tool-button"></slot></template>
</TableHeader>
</div>
<div ref="tableMainHostEl">
<TableMain
ref="tableMainRef"
:data="tableData"
:total="total"
:update-page="updatePage"
:container-height="tableMainHeight"
:extra-offset="headerExtraOffset"
v-model:page="page"
v-model:page-size="pageSize"
:expand-row-keys="expandRowKeys"
:row-key="rowKey"
@expand-change="handleExpandChange"
>
<template #columns><slot name="columns"></slot></template>
</TableMain>
</div>
</template>
<style scoped>

View File

@@ -15,16 +15,19 @@ const searcherParams = defineModel<Record<string, string>>("searcherParams");
</script>
<template>
<div class="table-header">
<SearchBar
:searchers="searchers"
:search-click="searchClick"
v-model:searcher-params="searcherParams"
:reset-click="resetClick"
/>
<!-- <TableToolBar class="table-tool-bar" :tool-buttons="toolButtons" /> -->
<div>
<slot name="tool-button"></slot>
<div class="table-header__search">
<SearchBar
:searchers="searchers"
:search-click="searchClick"
v-model:searcher-params="searcherParams"
:reset-click="resetClick"
>
<template #after-actions>
<slot name="tool-button"></slot>
</template>
</SearchBar>
</div>
<!-- <TableToolBar class="table-tool-bar" :tool-buttons="toolButtons" /> -->
</div>
</template>
<style>
@@ -32,9 +35,15 @@ const searcherParams = defineModel<Record<string, string>>("searcherParams");
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: flex-start;
margin-bottom: 10px;
}
.table-header__search {
flex: 1 1 auto;
min-width: 260px;
}
.table-tool-bar {
padding-left: 10px;
}

View File

@@ -17,6 +17,16 @@ defineProps({
type: Function,
required: true,
},
// 由父组件按实际视口计算的可用高度px。传了则优先生效。
containerHeight: {
type: Number,
default: 0,
},
// 表头等区域换行导致高度变化时的额外偏移量px
extraOffset: {
type: Number,
default: 0,
},
// 新增:透传给 el-table 的展开行 key 列表
expandRowKeys: {
type: Array as PropType<(string | number)[]>,
@@ -49,7 +59,15 @@ defineExpose({
});
</script>
<template>
<div style="display: flex; flex-direction: column; height: calc(100vh - 157px); overflow: hidden">
<div
:style="{
display: 'flex',
flexDirection: 'column',
height:
containerHeight && containerHeight > 0 ? `${containerHeight}px` : `calc(100vh - ${157 + (extraOffset || 0)}px)`,
overflow: 'hidden',
}"
>
<div style="flex: 1; overflow: hidden">
<el-table
ref="tableRef"

View File

@@ -41,6 +41,7 @@ function reset() {
<el-date-picker
v-model="searcherParams[searcher.name]"
v-if="searcher.type === 'date-range-pick'"
class="searcher"
type="daterange"
range-separator="-"
:start-placeholder="$t('_prop.common.startdate')"
@@ -48,24 +49,43 @@ function reset() {
value-format="YYYY-MM-DD"
></el-date-picker>
</div>
<el-button v-if="searchClick !== undefined" :icon="Search" type="primary" @click="searchButtonClick">
{{ $t("_button.search") }}
</el-button>
<el-button v-if="searchClick !== undefined" :icon="Refresh" @click="reset">{{ $t("_button.reset") }}</el-button>
<div class="search-actions" v-if="searchClick !== undefined">
<el-button :icon="Search" type="primary" @click="searchButtonClick">
{{ $t("_button.search") }}
</el-button>
<el-button :icon="Refresh" @click="reset">{{ $t("_button.reset") }}</el-button>
<slot name="after-actions"></slot>
</div>
</div>
</template>
<style>
.search-bar {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: flex-start;
padding-right: 20px;
}
.searcher-box {
padding-right: 5px;
padding-left: 5px;
box-sizing: border-box;
flex: 0 1 400px;
min-width: 240px;
max-width: 400px;
}
.searcher {
width: 400px;
box-sizing: border-box;
width: 100%;
}
.search-actions {
box-sizing: border-box;
display: flex;
flex: 0 0 auto;
gap: 10px;
align-items: center;
padding-left: 5px;
}
</style>

View File

@@ -6,11 +6,10 @@ import { usePage } from "@/composables/use-page";
import BaseFormWithTable from "@/components/base/base-form-with-table/BaseFormWithTable.vue";
import BaseItemDialog from "@/components/base/base-item-dialog/BaseItemDialog.vue";
import { $t } from "@/common/languages";
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
import { type FormInstance, type FormRules } from "element-plus";
import { formatDate } from "@/common/utils/format-utils";
import BaseSelect from "@/components/base/base-select/BaseSelect.vue";
import { generateDucumentNo } from "@/common/utils/document-no-generator/document-no-generator";
import { get, post } from "@/common/http/request";
import { useStatus } from "@/common/languages/mapping/base-info-mapping";
import ExpandablePageableTable from "@/components/base/expandable-pageable-table/ExpandablePageableTable.vue";
import { type FieldMappingConfig } from "@/components/base/base-form-with-table/type";
@@ -87,7 +86,7 @@ const mappingConfig: FieldMappingConfig = {
* 基本不变通用变量
*/
const tableRef = ref<InstanceType<typeof ExpandablePageableTable> | null>(null);
const { useAdd, useEdit, useRemove, useGeneralPageRef } = usePage(tableRef);
const { useAdd, useEdit, useRemove, useGeneralPageRef, useApprove, useReject } = usePage(tableRef);
const { title, visible, formType, form, itemVisible, itemParentId } = useGeneralPageRef();
const warehouseSelectRef = ref<InstanceType<typeof BaseSelect>>();
const { getFormStatusLabel } = useStatus();
@@ -162,35 +161,11 @@ const getFormStatusTagType = (code: number | null): string => {
};
const approve = (row: any) => {
ElMessageBox.confirm($t("_message.production.finishedproductshipment.approve_confirm"), $t("_level.warning"), {
confirmButtonText: $t("_button.confirm"),
cancelButtonText: $t("_button.cancel"),
type: "warning",
}).then(async () => {
try {
await post(approveUrl, { id: row.id });
ElMessage.success($t("_message.production.finishedproductshipment.approve_success"));
tableRef.value?.reload();
} catch (err: any) {
ElMessage.error($t("_message.production.finishedproductshipment.approve_fail") + err);
}
});
useApprove(approveUrl, row.id, "_message.production.finishedproductshipment.approve_confirm");
};
const reject = (row: any) => {
ElMessageBox.confirm($t("_message.production.finishedproductshipment.reject_confirm"), $t("_level.warning"), {
confirmButtonText: $t("_button.confirm"),
cancelButtonText: $t("_button.cancel"),
type: "warning",
}).then(async () => {
try {
await post(rejectUrl, { id: row.id });
ElMessage.success($t("_message.production.finishedproductshipment.reject_success"));
tableRef.value?.reload();
} catch (err: any) {
ElMessage.error($t("_message.production.finishedproductshipment.reject_fail") + err);
}
});
useReject(rejectUrl, row.id, "_message.production.finishedproductshipment.reject_confirm");
};
const showItem = (row: any) => {

View File

@@ -5,11 +5,11 @@ import DefaultStatusSwitchColumn from "@/components/base/default-column/DefaultS
import { usePage } from "@/composables/use-page";
import { $t } from "@/common/languages";
import { formatDate } from "@/common/utils/format-utils";
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from "element-plus";
import BaseFormWithTable from "@/components/base/base-form-with-table/BaseFormWithTable.vue";
import BaseItemDialog from "@/components/base/base-item-dialog/BaseItemDialog.vue";
import ExpandablePageableTable from "@/components/base/expandable-pageable-table/ExpandablePageableTable.vue";
import { get } from "@/common/http/request";
import { get, post } from "@/common/http/request";
import { type FieldMappingConfig } from "@/components/base/base-form-with-table/type";
import { useStatus } from "@/common/languages/mapping/base-info-mapping";
@@ -77,7 +77,7 @@ const getDetailUrl = "/sale/saleorder/getSaleOrderItemList";
* 基本不变通用变量
*/
const tableRef = ref<InstanceType<typeof ExpandablePageableTable> | null>(null);
const { useAdd, useEdit, useRemove, useGeneralPageRef, useApprove, useReject } = usePage(tableRef);
const { useAdd, useEdit, useRemove, useGeneralPageRef } = usePage(tableRef);
const { title, visible, formType, form, itemVisible, itemParentId } = useGeneralPageRef();
const { getFormStatusLabel } = useStatus();
const baseFormWithTableRef = ref<InstanceType<typeof BaseFormWithTable>>();
@@ -156,11 +156,35 @@ const remove = (row: any) => {
};
const approve = (row: any) => {
useApprove(approveUrl, row.id);
ElMessageBox.confirm($t("_message.sale.saleorder.approve_confirm"), $t("_level.warning"), {
confirmButtonText: $t("_button.confirm"),
cancelButtonText: $t("_button.cancel"),
type: "warning",
}).then(async () => {
try {
await post(approveUrl, { id: row.id });
ElMessage.success($t("_message.sale.saleorder.approve_success"));
tableRef.value?.reload();
} catch (err: any) {
ElMessage.error($t("_message.sale.saleorder.approve_fail") + err);
}
});
};
const reject = (row: any) => {
useReject(rejectUrl, row.id);
ElMessageBox.confirm($t("_message.sale.saleorder.reject_confirm"), $t("_level.warning"), {
confirmButtonText: $t("_button.confirm"),
cancelButtonText: $t("_button.cancel"),
type: "warning",
}).then(async () => {
try {
await post(rejectUrl, { id: row.id });
ElMessage.success($t("_message.sale.saleorder.reject_success"));
tableRef.value?.reload();
} catch (err: any) {
ElMessage.error($t("_message.sale.saleorder.reject_fail") + err);
}
});
};
const showItem = (row: any) => {
@@ -212,10 +236,12 @@ const operateButtonClick = (eventName: string, row: any) => {
};
const authShowFunc = (row: any, button: globalThis.ButtonProp) => {
if (row.formStatus === 1 && button.eventName === "approve") return false;
if (row.formStatus === 0 && button.eventName === "reject") return false;
if (row.formStatus === 1 && button.eventName === "edit") return false;
if (row.formStatus === 1 && button.eventName === "remove") return false;
// 已审核(status=1)时不显示审核按钮,显示反审按钮
// 未审核(status=0)时显示审核按钮,不显示反审按钮
if (row.status === 0 && button.eventName === "reject") return false;
if (row.status === 1 && button.eventName === "approve") return false;
// 已审核后不能编辑和删除
if (row.status === 1 && (button.eventName === "edit" || button.eventName === "remove")) return false;
return true;
};
@@ -294,11 +320,9 @@ const getFormStatusTagType = (code: number | null): string => {
<el-table-column :label="$t('_prop.sale.saleorder.formMark')" prop="formMark" show-overflow-tooltip />
<el-table-column :label="$t('_prop.common.createDate')" prop="createDate" :formatter="formatDate" />
<DefaultStatusSwitchColumn
status-param-name="formStatus"
status-param-name="status"
:status-label-mapping="getFormStatusLabel"
:tag-type-mapping="getFormStatusTagType"
:switch-on-value="1"
:switch-off-value="0"
/>
<DefaultOperateButtonColumn @operate-button-click="operateButtonClick" :auth-show-func="authShowFunc" />
</template>
@@ -322,7 +346,7 @@ const getFormStatusTagType = (code: number | null): string => {
:base-title="$t('_title.sale.saleorder.baseTitle')"
:table-title="$t('_title.sale.saleorder.tableTitle')"
item-array-name="saleOrderItems"
upload-desc="销售明细"
:upload-desc="$t('_title.sale.saleorder.tableTitle')"
:mapping-config="mappingConfig"
>
<template #form-items>
@@ -400,7 +424,7 @@ const getFormStatusTagType = (code: number | null): string => {
<el-table-column :label="$t('_prop.sale.saleorder.saleMark')" width="150">
<template #default="{ row, $index }">
<el-form-item :prop="`${itemArrayName}.${$index}.saleMark`">
<el-input v-model="row.saleMark" placeholder="请输入备注" size="small" />
<el-input v-model="row.saleMark" :placeholder="$t('_message.sale.saleorder.input_saleMark')" size="small" />
</el-form-item>
</template>
</el-table-column>