add customer or operator info

This commit is contained in:
Timi
2026-09-17 01:23:17 +08:00
parent 3135c41b21
commit 47723c6403
2 changed files with 104 additions and 22 deletions
+98 -18
View File
@@ -20,24 +20,46 @@
@refresh="refresh" @refresh="refresh"
@scrolltolower="loadMore" @scrolltolower="loadMore"
> >
<t-cell-group v-if="recordList.length" class="list" theme="card"> <t-cell-group v-if="recordList.length" class="list empty-cell-title-margin" theme="card">
<t-cell <t-cell
v-for="(record, index) in recordList" v-for="(record, index) in recordList"
:key="record.id || `${record.occurredAt}-${record.requestNo}-${index}`" :key="record.id || `${record.occurredAt}-${record.requestNo}-${index}`"
class="record" class="item"
:arrow="!!record.id" :arrow="!!record.id"
:hover="!!record.id" :hover="!!record.id"
v-entrance-transition="index * 30" v-entrance-transition="index * 30"
@click="toDetail(record)" @click="toDetail(record)"
> >
<template #leftIcon> <template #leftIcon>
<span class="icon" :class="getDeltaClass(record.onHandDelta)"> <t-image
v-if="getCustomerPhotoURL(record.customer, 64)"
:src="getCustomerPhotoURL(record.customer, 64)"
:alt="getCustomerText(record)"
fit="cover"
class="avatar"
/>
<div
v-else-if="record.customer"
class="avatar fallback"
v-text="getCustomerText(record).slice(0, 1)"
/>
<t-image
v-else-if="record.operator"
:src="getOperatorAvatar(record)"
:alt="getOperatorText(record)"
fit="cover"
class="avatar"
/>
<span v-else class="icon" :class="getDeltaClass(record.onHandDelta)">
<t-icon :name="getOperationIcon(record.operationType)" size="1.25rem" /> <t-icon :name="getOperationIcon(record.operationType)" size="1.25rem" />
</span> </span>
</template> </template>
<template #title> <template #title>
<div class="title"> <div class="title">
<strong v-text="getStockOperationText(record.operationType)" /> <span v-if="record.customer" class="actor" v-text="getCustomerText(record)" />
<span v-else-if="record.operator" class="actor" v-text="getOperatorText(record)" />
<strong class="operation" v-text="getStockOperationText(record.operationType)" />
<strong class="product-name" v-text="record.productNameSnapshot || '未命名产品'" />
<span <span
v-if="record.quantity !== undefined && record.quantity !== null && record.quantity !== ''" v-if="record.quantity !== undefined && record.quantity !== null && record.quantity !== ''"
class="quantity" class="quantity"
@@ -45,18 +67,16 @@
/> />
</div> </div>
</template> </template>
<template #note> <template #description>
<div class="description">
<pass-time-label :timestamp="record.occurredAt" underline />
<div class="delta">
<span>库存变更:</span>
<span <span
class="delta" class="value"
:class="getDeltaClass(record.onHandDelta)" :class="getDeltaClass(record.onHandDelta)"
v-text="getDeltaText(record.onHandDelta)" v-text="getDeltaText(record.onHandDelta)"
/> />
</template>
<template #description>
<div class="description">
<strong v-text="record.productNameSnapshot || '未命名产品'" />
<div>
<pass-time-label :timestamp="record.occurredAt" underline />
</div> </div>
</div> </div>
</template> </template>
@@ -71,6 +91,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type DropdownValue } from "tdesign-mobile-vue"; import { type DropdownValue } from "tdesign-mobile-vue";
import { PassTimeLabel } from "timi-web"; import { PassTimeLabel } from "timi-web";
import { UserAPI } from "timi-web-api";
import ProductAPI from "@/api/ProductAPI"; import ProductAPI from "@/api/ProductAPI";
import { useNavBarStore } from "@/store/navBarStore"; import { useNavBarStore } from "@/store/navBarStore";
import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product"; import type { StockOperationType, StockRecord, StockRecordDirection } from "@/types/Product";
@@ -78,6 +99,8 @@ import { getErrorMessage } from "@/utils/error";
import { getProductQuantityText, getStockOperationText } from "@/utils/product"; import { getProductQuantityText, getStockOperationText } from "@/utils/product";
import { Message } from "@/utils/message"; import { Message } from "@/utils/message";
import { permissionCodes } from "@/utils/permission"; import { permissionCodes } from "@/utils/permission";
import { getCustomerPhotoURL } from "@/utils/customer";
import { buildFallbackAvatar } from "@/utils/avatar";
defineOptions({ name: "ProductStockRecordList" }); defineOptions({ name: "ProductStockRecordList" });
@@ -159,6 +182,18 @@ function getStockQuantityText(record: StockRecord): string {
return `${getProductQuantityText(record.quantity)}${unitText}`; return `${getProductQuantityText(record.quantity)}${unitText}`;
} }
function getCustomerText(record: StockRecord): string {
return record.customer?.name || record.customer?.code || "未知客户";
}
function getOperatorText(record: StockRecord): string {
return record.operator?.nick || record.operator?.name || "未知操作人";
}
function getOperatorAvatar(record: StockRecord): string {
return UserAPI.getAvatarURL(record.operator) || buildFallbackAvatar(getOperatorText(record), "#0f766e");
}
function getDeltaText(value?: number | string): string { function getDeltaText(value?: number | string): string {
const amount = Number(value || 0); const amount = Number(value || 0);
const text = String(value || "0").replace(/^-/, ""); const text = String(value || "0").replace(/^-/, "");
@@ -340,7 +375,24 @@ onUnmounted(() => {
.list { .list {
margin: 0; margin: 0;
.record { .item {
padding: 10px;
min-width: 0;
:deep(.t-cell__title) {
min-width: 0;
width: 0;
}
:deep(.t-cell__title-text) {
min-width: 0;
width: 100%;
}
.avatar {
width: 3.5rem;
height: 3.5rem;
}
.icon { .icon {
width: 2.25rem; width: 2.25rem;
@@ -354,23 +406,51 @@ onUnmounted(() => {
.title { .title {
gap: .5rem; gap: .5rem;
min-width: 0;
max-width: 100%;
flex: 1;
display: flex; display: flex;
font-size: .75rem;
align-items: center; align-items: center;
white-space: nowrap;
.actor {
color: var(--td-text-color-secondary);
flex: 0 0 auto;
}
.operation {
flex: 0 0 auto;
}
.product-name {
min-width: 0;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.quantity { .quantity {
color: var(--td-text-color-secondary); color: var(--td-text-color-secondary);
font-size: .875rem; flex: 0 0 auto;
font-weight: 400; font-weight: 400;
} }
} }
.description {
gap: .5rem;
display: flex;
font-size: .75rem;
align-items: center;
justify-content: space-between;
.delta { .delta {
.value {
font-size: 1.25rem; font-size: 1.25rem;
} }
}
.description {
gap: .125rem;
display: grid;
} }
} }
} }
+2
View File
@@ -1,4 +1,5 @@
import type { Attachment, Model, PageResult, User } from "timi-web-api"; import type { Attachment, Model, PageResult, User } from "timi-web-api";
import type { Customer } from "@/types/Customer";
export type ProductType = "SERVICE" | "GOODS" | "CONSUMABLE"; export type ProductType = "SERVICE" | "GOODS" | "CONSUMABLE";
export type ProductAttachmentType = "PHOTO" | "OTHER"; export type ProductAttachmentType = "PHOTO" | "OTHER";
@@ -184,6 +185,7 @@ export type StockRecord = {
remark?: string | null; remark?: string | null;
operator?: Partial<User>; operator?: Partial<User>;
attachmentList?: Partial<Attachment>[]; attachmentList?: Partial<Attachment>[];
customer?: Customer;
} & Model; } & Model;
export type StockRecordPage = { export type StockRecordPage = {