获取焦点

This commit is contained in:
liu
2026-09-13 01:46:00 +08:00
parent 3005646179
commit 3d1ec9c833
13 changed files with 65 additions and 27 deletions
File diff suppressed because one or more lines are too long
@@ -58,7 +58,9 @@ class PurchaseOrderController extends BaseController
$data = $this->buildSearch($params, PurchaseOrderModel::query() $data = $this->buildSearch($params, PurchaseOrderModel::query()
->with('operator:id,nickname') ->with('operator:id,nickname')
// 应付商品金额 = Σ 已生成门店账单的商品金额(实际口径;未生成账单为 null) // 应付商品金额 = Σ 已生成门店账单的商品金额(实际口径;未生成账单为 null)
->withSum('bills as bill_product_amount', 'product_amount')) ->withSum('bills as bill_product_amount', 'product_amount')
// 账单总金额 = Σ 已生成门店账单的总金额(商品+配送费+附加+售后;未生成账单为 null)
->withSum('bills as bill_total_amount', 'total_amount'))
->orderBy('purchase_date', 'desc') ->orderBy('purchase_date', 'desc')
->orderBy('id', 'desc') ->orderBy('id', 'desc')
->paginate($pageSize) ->paginate($pageSize)
+2 -1
View File
@@ -44,8 +44,9 @@ class PurchaseOrderModel extends Model
'actual_amount' => 'decimal:2', 'actual_amount' => 'decimal:2',
'operator_id' => 'integer', 'operator_id' => 'integer',
'created_at' => 'datetime:Y-m-d H:i:s', 'created_at' => 'datetime:Y-m-d H:i:s',
// 列表 withSum 聚合属性(应付商品金额;无账单时保持 null) // 列表 withSum 聚合属性(应付商品金额/账单总金额;无账单时保持 null
'bill_product_amount' => 'decimal:2', 'bill_product_amount' => 'decimal:2',
'bill_total_amount' => 'decimal:2',
]; ];
/** /**
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/favicons.svg" /> <link rel="icon" type="image/svg+xml" href="/favicons.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XinAdmin</title> <title>XinAdmin</title>
<script type="module" crossorigin src="/assets/index-CrAEpRIR.js"></script> <script type="module" crossorigin src="/assets/index-_7W4DmEA.js"></script>
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-BgaNhQyE.js"> <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-BgaNhQyE.js">
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-CRBytmvs.js"> <link rel="modulepreload" crossorigin href="/assets/jsx-runtime-CRBytmvs.js">
<link rel="modulepreload" crossorigin href="/assets/chunk-KS7C4IRE-Zm15rq6F.js"> <link rel="modulepreload" crossorigin href="/assets/chunk-KS7C4IRE-Zm15rq6F.js">
+7 -5
View File
@@ -750,19 +750,21 @@ class PurchaseEditTest extends ProcurementTestCase
// 未生成账单 → null // 未生成账单 → null
$this->getJson('/purchase/order') $this->getJson('/purchase/order')
->assertJsonPath('data.data.0.bill_product_amount', null); ->assertJsonPath('data.data.0.bill_product_amount', null)
->assertJsonPath('data.data.0.bill_total_amount', null);
// 完成采购单并生成两店账单(各店商品金额 = 数量 × 等级价 10.00) // 完成采购单并生成两店账单(各店商品金额 = 数量 × 等级价 10.00)
$purchase->update(['status' => PurchaseOrderModel::STATUS_COMPLETED]); $purchase->update(['status' => PurchaseOrderModel::STATUS_COMPLETED]);
$this->postJson("/purchase/order/{$purchase->id}/bill", [ $this->postJson("/purchase/order/{$purchase->id}/bill", [
'stores' => [ 'stores' => [
['store_id' => $stores[0]->id, 'delivery_fee' => 0, 'box_num' => 0, 'tray_num' => 0], ['store_id' => $stores[0]->id, 'delivery_fee' => 5, 'box_num' => 0, 'tray_num' => 0],
['store_id' => $stores[1]->id, 'delivery_fee' => 0, 'box_num' => 0, 'tray_num' => 0], ['store_id' => $stores[1]->id, 'delivery_fee' => 3, 'box_num' => 0, 'tray_num' => 0],
], ],
])->assertJsonPath('success', true); ])->assertJsonPath('success', true);
// 列表回显:2×10.00 + 3×10.00 = 50.00 // 列表回显:商品金额 2×10.00 + 3×10.00 = 50.00;账单总金额 50.00 + 配送费 8.00 = 58.00
$this->getJson('/purchase/order') $this->getJson('/purchase/order')
->assertJsonPath('data.data.0.bill_product_amount', '50.00'); ->assertJsonPath('data.data.0.bill_product_amount', '50.00')
->assertJsonPath('data.data.0.bill_total_amount', '58.00');
} }
} }
+2
View File
@@ -76,6 +76,8 @@ export default interface IPurchaseOrder {
actual_amount?: string; actual_amount?: string;
/** 应付商品金额 = Σ 已生成门店账单的商品金额(列表接口附;未生成账单为 null) */ /** 应付商品金额 = Σ 已生成门店账单的商品金额(列表接口附;未生成账单为 null) */
bill_product_amount?: string | null; bill_product_amount?: string | null;
/** 账单总金额 = Σ 已生成门店账单的总金额(商品+配送费+附加+售后;未生成账单为 null) */
bill_total_amount?: string | null;
operator_id?: number; operator_id?: number;
operator?: { id: number; nickname: string }; operator?: { id: number; nickname: string };
remark?: string; remark?: string;
+11 -3
View File
@@ -37,6 +37,9 @@ const { Title, Text } = Typography;
/** 四舍五入保留两位 */ /** 四舍五入保留两位 */
const round2 = (v: number) => Math.round(v * 100) / 100; const round2 = (v: number) => Math.round(v * 100) / 100;
/** 输入框聚焦时全选已有内容,方便直接键入覆盖(文本 Input / 数字 InputNumber 通用) */
const selectAllOnFocus = (e: React.FocusEvent<HTMLInputElement>) => e.target.select();
/** 商品表单用分类树:有子分类的节点禁选(商品只能挂在末级分类上) */ /** 商品表单用分类树:有子分类的节点禁选(商品只能挂在末级分类上) */
const markParentDisabled = (nodes: IProductCategoryTree[]): IProductCategoryTree[] => const markParentDisabled = (nodes: IProductCategoryTree[]): IProductCategoryTree[] =>
nodes.map((node) => ({ nodes.map((node) => ({
@@ -309,6 +312,7 @@ const ProductGoodsPage: React.FC = () => {
valueType: 'text', valueType: 'text',
colProps: { span: 24 }, colProps: { span: 24 },
required: true, required: true,
fieldProps: { onFocus: selectAllOnFocus },
render: (_, record) => { render: (_, record) => {
return ( return (
<div> <div>
@@ -359,6 +363,7 @@ const ProductGoodsPage: React.FC = () => {
hideInSearch: true, hideInSearch: true,
hideInTable: true, hideInTable: true,
colProps: { span: 24 }, colProps: { span: 24 },
fieldProps: { onFocus: selectAllOnFocus },
}, },
{ {
title: '成本价', title: '成本价',
@@ -367,7 +372,7 @@ const ProductGoodsPage: React.FC = () => {
hideInSearch: true, hideInSearch: true,
align: 'center', align: 'center',
tooltip: '各等级售价 = 成本价 × (100 + 等级上浮比例) / 100', tooltip: '各等级售价 = 成本价 × (100 + 等级上浮比例) / 100',
fieldProps: { min: 0, precision: 2, prefix: '¥' }, fieldProps: { min: 0, precision: 2, prefix: '¥', onFocus: selectAllOnFocus },
render: (_, record) => { render: (_, record) => {
const cost = Number(record.cost_price ?? 0); const cost = Number(record.cost_price ?? 0);
return cost > 0 ? `¥${record.cost_price}` : <Text type="secondary"></Text>; return cost > 0 ? `¥${record.cost_price}` : <Text type="secondary"></Text>;
@@ -379,6 +384,7 @@ const ProductGoodsPage: React.FC = () => {
valueType: 'text', valueType: 'text',
hideInSearch: true, hideInSearch: true,
align: "center", align: "center",
fieldProps: { onFocus: selectAllOnFocus },
}, },
{ {
title: '单位', title: '单位',
@@ -387,6 +393,7 @@ const ProductGoodsPage: React.FC = () => {
hideInSearch: true, hideInSearch: true,
initialValue: '斤', initialValue: '斤',
align: "center", align: "center",
fieldProps: { onFocus: selectAllOnFocus },
}, },
{ {
title: '单价单位', title: '单价单位',
@@ -395,14 +402,14 @@ const ProductGoodsPage: React.FC = () => {
hideInTable: true, hideInTable: true,
hideInSearch: true, hideInSearch: true,
initialValue: '元/斤', initialValue: '元/斤',
fieldProps: { placeholder: '如:元/斤、元/箱', maxLength: 20 }, fieldProps: { placeholder: '如:元/斤、元/箱', maxLength: 20, onFocus: selectAllOnFocus },
}, },
{ {
title: '排序', title: '排序',
dataIndex: 'sort', dataIndex: 'sort',
valueType: 'digit', valueType: 'digit',
hideInSearch: true, hideInSearch: true,
fieldProps: { min: 0 }, fieldProps: { min: 0, onFocus: selectAllOnFocus },
align: 'center', align: 'center',
}, },
{ {
@@ -448,6 +455,7 @@ const ProductGoodsPage: React.FC = () => {
placeholder: '市场名称,如:新发地', placeholder: '市场名称,如:新发地',
maxLength: 50, maxLength: 50,
allowClear: true, allowClear: true,
onFocus: selectAllOnFocus,
}, },
render: (_, record) => record.market || '-', render: (_, record) => record.market || '-',
}, },
+34 -11
View File
@@ -90,6 +90,9 @@ const calcUnitRefPrice = (total: number, spec: string): number => {
const calcLevelAmount = (amount: number, percent: number): number => const calcLevelAmount = (amount: number, percent: number): number =>
Math.trunc(amount * (100 + percent)) / 100; Math.trunc(amount * (100 + percent)) / 100;
/** 输入框聚焦时全选已有内容,方便直接键入覆盖(Input / InputNumber / TextArea 通用) */
const selectAllOnFocus = (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => e.target.select();
/** /**
* C1 / C2-C3 / C4 * C1 / C2-C3 / C4
* × * ×
@@ -560,6 +563,7 @@ const PurchaseOrderPage: React.FC = () => {
prefix="¥" prefix="¥"
className="w-full" className="w-full"
defaultValue={Number(row.cost_price)} defaultValue={Number(row.cost_price)}
onFocus={selectAllOnFocus}
onPressEnter={(e) => void handleInlineSave(row, dataIndex, (e.target as HTMLInputElement).value)} onPressEnter={(e) => void handleInlineSave(row, dataIndex, (e.target as HTMLInputElement).value)}
onBlur={(e) => void handleInlineSave(row, dataIndex, e.target.value)} onBlur={(e) => void handleInlineSave(row, dataIndex, e.target.value)}
/> />
@@ -573,6 +577,7 @@ const PurchaseOrderPage: React.FC = () => {
style={{ textAlign: 'center' }} style={{ textAlign: 'center' }}
defaultValue={row[dataIndex]} defaultValue={row[dataIndex]}
maxLength={dataIndex === 'unit' ? 20 : 100} maxLength={dataIndex === 'unit' ? 20 : 100}
onFocus={selectAllOnFocus}
onPressEnter={(e) => void handleInlineSave(row, dataIndex, (e.target as HTMLInputElement).value)} onPressEnter={(e) => void handleInlineSave(row, dataIndex, (e.target as HTMLInputElement).value)}
onBlur={(e) => void handleInlineSave(row, dataIndex, e.target.value)} onBlur={(e) => void handleInlineSave(row, dataIndex, e.target.value)}
/> />
@@ -770,6 +775,7 @@ const PurchaseOrderPage: React.FC = () => {
precision={0} precision={0}
className="w-full" className="w-full"
defaultValue={quantity} defaultValue={quantity}
onFocus={selectAllOnFocus}
onPressEnter={(e) => void handleStoreCellSave(row, store.id, (e.target as HTMLInputElement).value)} onPressEnter={(e) => void handleStoreCellSave(row, store.id, (e.target as HTMLInputElement).value)}
onBlur={(e) => void handleStoreCellSave(row, store.id, e.target.value)} onBlur={(e) => void handleStoreCellSave(row, store.id, e.target.value)}
/> />
@@ -1092,23 +1098,32 @@ const PurchaseOrderPage: React.FC = () => {
{ {
title: '实际成本', title: '实际成本',
dataIndex: 'actual_amount', dataIndex: 'actual_amount',
hideInTable: true,
hideInSearch: true, hideInSearch: true,
valueType: "digit", valueType: "digit",
fieldProps: { fieldProps: {
min: 0, min: 0,
precision: 3, precision: 3,
placeholder: "请输入单价" placeholder: "请输入单价",
onFocus: selectAllOnFocus,
}, },
align: 'center', align: 'center',
},
{
title: '账单总金额',
dataIndex: 'bill_total_amount',
hideInForm: true,
hideInSearch: true,
align: 'center',
render: (_, record) => render: (_, record) =>
Number(record.actual_amount) > 0 ? ( record.bill_total_amount != null ? (
<Text strong>¥{record.actual_amount}</Text> <Text strong type="danger">¥{Number(record.bill_total_amount).toFixed(2)}</Text>
) : ( ) : (
<Text type="secondary"></Text> <Text type="secondary"></Text>
), ),
}, },
{ {
title: '应付商品金额', title: '商品金额',
dataIndex: 'bill_product_amount', dataIndex: 'bill_product_amount',
hideInForm: true, hideInForm: true,
hideInSearch: true, hideInSearch: true,
@@ -1132,7 +1147,10 @@ const PurchaseOrderPage: React.FC = () => {
dataIndex: 'remark', dataIndex: 'remark',
valueType: "textarea", valueType: "textarea",
hideInTable: true, hideInTable: true,
hideInSearch: true hideInSearch: true,
fieldProps: {
onFocus: selectAllOnFocus,
},
}, },
{ {
title: '总重量', title: '总重量',
@@ -1528,6 +1546,7 @@ const PurchaseOrderPage: React.FC = () => {
precision={2} precision={2}
disabled={billed} disabled={billed}
placeholder="配送费" placeholder="配送费"
onFocus={selectAllOnFocus}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -1540,6 +1559,7 @@ const PurchaseOrderPage: React.FC = () => {
precision={0} precision={0}
disabled={billed} disabled={billed}
placeholder="正压负回" placeholder="正压负回"
onFocus={selectAllOnFocus}
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -1552,6 +1572,7 @@ const PurchaseOrderPage: React.FC = () => {
precision={0} precision={0}
disabled={billed} disabled={billed}
placeholder="正压负回" placeholder="正压负回"
onFocus={selectAllOnFocus}
/> />
</Form.Item> </Form.Item>
<div className="w-50 shrink-0 px-1! flex justify-center"> <div className="w-50 shrink-0 px-1! flex justify-center">
@@ -1571,6 +1592,7 @@ const PurchaseOrderPage: React.FC = () => {
prefix={'¥'} prefix={'¥'}
disabled={billed} disabled={billed}
placeholder="可正负,计入总额" placeholder="可正负,计入总额"
onFocus={selectAllOnFocus}
/> />
</Form.Item> </Form.Item>
</Space.Compact> </Space.Compact>
@@ -1584,6 +1606,7 @@ const PurchaseOrderPage: React.FC = () => {
maxLength={255} maxLength={255}
disabled={billed} disabled={billed}
placeholder="备注(可选)" placeholder="备注(可选)"
onFocus={selectAllOnFocus}
/> />
</Form.Item> </Form.Item>
<div className="w-28 shrink-0 text-center"> <div className="w-28 shrink-0 text-center">
@@ -1641,10 +1664,10 @@ const PurchaseOrderPage: React.FC = () => {
name="quantity" name="quantity"
rules={[{ required: true, message: '请输入采购数量' }]} rules={[{ required: true, message: '请输入采购数量' }]}
> >
<InputNumber className="w-full" min={1} precision={0} placeholder="请输入采购数量(包数)" /> <InputNumber className="w-full" min={1} precision={0} placeholder="请输入采购数量(包数)" onFocus={selectAllOnFocus} />
</Form.Item> </Form.Item>
<Form.Item label="称重(斤,可选)" name="weight"> <Form.Item label="称重(斤,可选)" name="weight">
<InputNumber className="w-full" min={0} precision={3} placeholder="请输入称重" /> <InputNumber className="w-full" min={0} precision={3} placeholder="请输入称重" onFocus={selectAllOnFocus} />
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>
@@ -1668,17 +1691,17 @@ const PurchaseOrderPage: React.FC = () => {
name="price" name="price"
rules={[{ required: true, message: '请输入单价' }]} rules={[{ required: true, message: '请输入单价' }]}
> >
<InputNumber className="w-full" min={0} precision={2} placeholder="请输入单价" /> <InputNumber className="w-full" min={0} precision={2} placeholder="请输入单价" onFocus={selectAllOnFocus} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="采购数量" label="采购数量"
name="quantity" name="quantity"
rules={[{ required: true, message: '请输入采购数量' }]} rules={[{ required: true, message: '请输入采购数量' }]}
> >
<InputNumber className="w-full" min={0} precision={0} placeholder="请输入采购数量" /> <InputNumber className="w-full" min={0} precision={0} placeholder="请输入采购数量" onFocus={selectAllOnFocus} />
</Form.Item> </Form.Item>
<Form.Item label="称重(斤)" name="weight" rules={[{ required: true, message: '请输入称重' }]}> <Form.Item label="称重(斤)" name="weight" rules={[{ required: true, message: '请输入称重' }]}>
<InputNumber className="w-full" min={0} precision={3} placeholder="请输入称重" /> <InputNumber className="w-full" min={0} precision={3} placeholder="请输入称重" onFocus={selectAllOnFocus} />
</Form.Item> </Form.Item>
</Form> </Form>
</Modal> </Modal>