From 19dffbaa499ffffc1ddd06070fb4200af43cbad9 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Sat, 17 Oct 2026 17:05:46 +0800 Subject: [PATCH] fix(mobile-oa-h5): skin market neutral tags as barley soft. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Map Element Plus info tones through Vant default and style them as 浅底大麦黄 so unpaid/pending tags stay visible and distinct from solid semantic tags. Co-authored-by: Cursor --- ...-10-17-mobile-market-neutral-tag-demo.html | 460 ++++++++++++++++++ ...mobile-market-neutral-tag-barley-design.md | 40 ++ .../components/MarketDetailNestedCard.vue | 2 +- .../market/components/MarketDetailSection.vue | 8 +- .../src/market/components/MarketListCard.vue | 37 +- .../PaymentDetailTransfersPanel.vue | 4 +- .../market/components/PresignOrderCard.vue | 7 +- .../market/components/PresignTransferCard.vue | 5 +- .../components/ProtocolV1PaymentPanel.vue | 4 +- .../src/market/mobile-parity-ui.test.js | 22 +- mobile-oa-h5/src/market/status-tone.js | 6 +- mobile-oa-h5/src/market/status-tone.test.js | 10 +- mobile-oa-h5/src/styles/market.css | 26 +- 13 files changed, 598 insertions(+), 33 deletions(-) create mode 100644 docs/demos/2026-10-17-mobile-market-neutral-tag-demo.html create mode 100644 docs/superpowers/specs/2026-10-17-mobile-market-neutral-tag-barley-design.md diff --git a/docs/demos/2026-10-17-mobile-market-neutral-tag-demo.html b/docs/demos/2026-10-17-mobile-market-neutral-tag-demo.html new file mode 100644 index 00000000..bfe60eda --- /dev/null +++ b/docs/demos/2026-10-17-mobile-market-neutral-tag-demo.html @@ -0,0 +1,460 @@ + + + + + + 市场部 · 中性 Tag 样式对比 + + + +
+

市场部 · 中性 Tag 样式对比

+

+ 上一版把 Element Plus 的 info 映射成了 Vant default(灰底白字),可读但偏「硬」。 + 请点选一种中性态样式;实心绿 / 橙 / 红等已有语义色保持不变。 +

+ +
+ web-admin「存储服务」说明: + Element Plus 默认 el-tag type="info" 是浅灰底 + 灰字(light), + 不是灰底白字,也不是无底白字。 + 存储服务里大量「待缴费 / 待生效 / 草稿」走 info,视觉接近下方「Web 对照」区。 +
+ +
+

现状问题 Vant default

+
+
+ 1 + + 欧阳妮妮 + ZKZX34000194 + + 待缴费 +
+
+ 待缴费 + 待生效 + 草稿 + 部分已缴 + 已缴清 + 催缴中 +
+

灰底白字对比度高,但和「语义实心色」挤在一起时显得脏、抢戏。

+
+
+ +
+

方案选择 点一张卡片

+
+ + + + + + + +
+
+ +
+

Web 对照 Element Plus info

+
+
+
存储服务常见 info(浅底深/灰字)
+
+ 待缴费 + 待生效 + 草稿 + 已缴清 + 部分已缴 +
+

无「无底白字」问题;默认也不是灰底白字。

+
+
+
若选方案 A,移动端中性态将更接近 Web
+
+ 待缴费 + 待缴费 +
+

左:移动方案 A · 右:Web info light(圆角规范不同,色感接近)

+
+
+
+ +
+
已选:未选择
+ +
+
+ + + + diff --git a/docs/superpowers/specs/2026-10-17-mobile-market-neutral-tag-barley-design.md b/docs/superpowers/specs/2026-10-17-mobile-market-neutral-tag-barley-design.md new file mode 100644 index 00000000..d6090831 --- /dev/null +++ b/docs/superpowers/specs/2026-10-17-mobile-market-neutral-tag-barley-design.md @@ -0,0 +1,40 @@ +# 市场部移动端中性 Tag · 大麦黄浅底(方案 D) + +## 背景 + +`info`(Element Plus 语义)映射为 Vant `default` 后,中性态呈现「灰底白字」,与实心语义色同屏时观感偏硬。用户选定方案 D:三墙大麦黄浅底。 + +## 决策 + +| 项 | 选择 | +|----|------| +| 方案 | D · 大麦黄浅底 | +| 范围 | 仅 `mobile-oa-h5` 市场部(`.market-page` / `.presign-order-page`) | +| 不改 | `primary` / `success` / `warning` / `danger` 实心色;web-admin | +| 数据层 | 登记表继续 `tagType: 'info'`;展示层仍 `info` → Vant `default` | + +## 视觉 Token + +- 背景:`#fff7e8`(`--market` 大麦浅底,与规范 `#fff7e8` 一致) +- 文字:`#9a7020` +- 描边:`#f0e0b8` +- 形状:沿用现有胶囊(`.market-tag` / round) + +与 `warning` 区分:中性 = 浅底深字;warning = 实心橙 + 白字。 + +## 实现 + +在 `market.css` 覆盖: + +```css +.market-page .van-tag--default, +.presign-order-page .van-tag--default { ... } +``` + +不新增 tone 枚举;`resolveStatusTone('…', 'info')` 继续返回 `default`。 + +## 验收 + +- 缴费列表「待缴费」等中性态为大麦黄浅底,非灰底白字 +- 已缴清 / 部分已缴 / 催缴等实心色不变 +- 相关单测通过 diff --git a/mobile-oa-h5/src/market/components/MarketDetailNestedCard.vue b/mobile-oa-h5/src/market/components/MarketDetailNestedCard.vue index 711baf84..ea21d06c 100644 --- a/mobile-oa-h5/src/market/components/MarketDetailNestedCard.vue +++ b/mobile-oa-h5/src/market/components/MarketDetailNestedCard.vue @@ -124,7 +124,7 @@ diff --git a/mobile-oa-h5/src/market/components/MarketDetailSection.vue b/mobile-oa-h5/src/market/components/MarketDetailSection.vue index 75555d72..b3cd330e 100644 --- a/mobile-oa-h5/src/market/components/MarketDetailSection.vue +++ b/mobile-oa-h5/src/market/components/MarketDetailSection.vue @@ -10,7 +10,13 @@ :model-value="field.value" :editable="false" /> - + {{ field.value }} {{ field.value }} diff --git a/mobile-oa-h5/src/market/components/MarketListCard.vue b/mobile-oa-h5/src/market/components/MarketListCard.vue index dd8facfe..d882af73 100644 --- a/mobile-oa-h5/src/market/components/MarketListCard.vue +++ b/mobile-oa-h5/src/market/components/MarketListCard.vue @@ -17,7 +17,9 @@ {{ card.title }} {{ card.subtitle }} - {{ card.status }} + + {{ card.status }} +
@@ -28,7 +30,14 @@ :class="{ 'is-wide': field.wide }" > {{ field.label }} - {{ field.value }} + {{ field.value }} {{ field.value }}
@@ -100,7 +109,14 @@ :class="{ 'is-wide': field.wide }" > {{ field.label }} - {{ field.value }} + {{ field.value }} {{ field.value }} @@ -114,7 +130,14 @@ :class="{ 'is-wide': field.wide }" > {{ field.label }} - {{ field.value }} + {{ field.value }} {{ field.value }} @@ -151,7 +174,7 @@ buildMarketCard(props.module, props.role, props.item)) -const statusTone = computed(() => card.value.statusTone || resolveStatusTone(card.value.status)) +const statusTone = computed(() => + resolveStatusTone(card.value.status, card.value.statusTone) +) const allFields = computed(() => (Array.isArray(card.value.fields) ? card.value.fields : [])) const footerFieldCount = computed(() => Array.isArray(card.value.footerFields) ? card.value.footerFields.length : 0 diff --git a/mobile-oa-h5/src/market/components/PaymentDetailTransfersPanel.vue b/mobile-oa-h5/src/market/components/PaymentDetailTransfersPanel.vue index 2c29a640..d843b4e3 100644 --- a/mobile-oa-h5/src/market/components/PaymentDetailTransfersPanel.vue +++ b/mobile-oa-h5/src/market/components/PaymentDetailTransfersPanel.vue @@ -46,6 +46,7 @@ import { ref, watch } from 'vue' import { formatMoney } from '../money.js' import { resolveStatusTag, paymentScheduleStatus } from '../status-tag-registry.js' +import { resolveStatusTone } from '../status-tone.js' import ScheduleTransferEditor from './ScheduleTransferEditor.vue' const props = defineProps({ @@ -78,7 +79,8 @@ function scheduleStatusLabel(schedule) { } function scheduleStatusTone(schedule) { - return resolveStatusTag(paymentScheduleStatus, schedule?.scheduleStatus).tagType + const tag = resolveStatusTag(paymentScheduleStatus, schedule?.scheduleStatus) + return resolveStatusTone(tag.label, tag.tagType) } function onScheduleUpdate(index, nextSchedule) { diff --git a/mobile-oa-h5/src/market/components/PresignOrderCard.vue b/mobile-oa-h5/src/market/components/PresignOrderCard.vue index be04de59..88967f92 100644 --- a/mobile-oa-h5/src/market/components/PresignOrderCard.vue +++ b/mobile-oa-h5/src/market/components/PresignOrderCard.vue @@ -11,7 +11,11 @@ {{ card.title }} 母亲姓名 - {{ statusField.value }} + {{ statusField.value }} @@ -78,6 +82,7 @@ import { buildPresignCardModel, presignCardActionEvent } from '../presign/presign-list-model.js' +import { resolveStatusTone } from '../status-tone.js' /** 默认展示两行(4 项),其余折叠 */ const FIELD_PREVIEW_COUNT = 4 diff --git a/mobile-oa-h5/src/market/components/PresignTransferCard.vue b/mobile-oa-h5/src/market/components/PresignTransferCard.vue index 4de826b8..a7d47e26 100644 --- a/mobile-oa-h5/src/market/components/PresignTransferCard.vue +++ b/mobile-oa-h5/src/market/components/PresignTransferCard.vue @@ -108,6 +108,7 @@ import OaDatePicker from '../../oa/components/OaDatePicker.vue' import OaMoneyField from '../../oa/components/OaMoneyField.vue' import { removeMarketAttachmentAt } from '../attachment-upload-state.js' import { resolveStatusTag, payStatus } from '../status-tag-registry.js' +import { resolveStatusTone } from '../status-tone.js' import PresignAttachmentUploader from './PresignAttachmentUploader.vue' import PresignSelectField from './PresignSelectField.vue' @@ -144,7 +145,9 @@ const statusTag = computed(() => resolveStatusTag(payStatus, props.modelValue.payStatus || 'dfj') ) const statusLabel = computed(() => statusTag.value.label) -const statusTone = computed(() => statusTag.value.tagType) +const statusTone = computed(() => + resolveStatusTone(statusTag.value.label, statusTag.value.tagType) +) function can(action) { return !props.disabled && actionSet.value.has(action) diff --git a/mobile-oa-h5/src/market/components/ProtocolV1PaymentPanel.vue b/mobile-oa-h5/src/market/components/ProtocolV1PaymentPanel.vue index 33c0660d..fa7e207c 100644 --- a/mobile-oa-h5/src/market/components/ProtocolV1PaymentPanel.vue +++ b/mobile-oa-h5/src/market/components/ProtocolV1PaymentPanel.vue @@ -288,6 +288,7 @@ import { } from '../attachment-upload-policy.js' import MarketAttachmentCards from './MarketAttachmentCards.vue' import { resolveStatusTag, payStatus } from '../status-tag-registry.js' +import { resolveStatusTone } from '../status-tone.js' const props = defineProps({ payment: { type: Object, default: () => ({}) }, @@ -365,7 +366,8 @@ function statusLabel(value) { } function statusTone(value) { - return resolveStatusTag(payStatus, value).tagType + const tag = resolveStatusTag(payStatus, value) + return resolveStatusTone(tag.label, tag.tagType) } function transferTypeLabel(value) { diff --git a/mobile-oa-h5/src/market/mobile-parity-ui.test.js b/mobile-oa-h5/src/market/mobile-parity-ui.test.js index 528266cf..8b79e056 100644 --- a/mobile-oa-h5/src/market/mobile-parity-ui.test.js +++ b/mobile-oa-h5/src/market/mobile-parity-ui.test.js @@ -73,6 +73,11 @@ test('market list visual contract keeps search white, tags filled and actions ri styles, /\.market-page \.van-tag,\s*\n\.presign-order-page \.van-tag\s*\{[\s\S]*?padding:\s*5px 12px;[\s\S]*?border-radius:\s*999px;/ ) + // Neutral info→default: barley soft (not Vant gray-on-white) + assert.match( + styles, + /\.market-page \.van-tag--default,\s*\n\.presign-order-page \.van-tag--default\s*\{[\s\S]*?background:\s*#fff7e8;[\s\S]*?color:\s*#9a7020;[\s\S]*?border:\s*1px solid #f0e0b8;/ + ) assert.match(card, /class="market-tag"/) assert.doesNotMatch(card, /]*\bplain\b/) for (const component of [ @@ -205,7 +210,8 @@ test('freight list cards collapse sample blocks by default and allow expand', () assert.match(presenter, /sampleBlocks:\s*rows\.map\(freightSampleBlock\)/) assert.match(card, /card\.sampleBlocks/) - assert.match(card, /block\.statusTone \|\| resolveStatusTone\(block\.status\)/) + assert.match(card, /resolveStatusTone\(block\.status, block\.statusTone\)/) + assert.match(card, /resolveStatusTone\(card\.value\.status, card\.value\.statusTone\)/) assert.match(card, /samplesOpen/) assert.match(card, /关联样本(/) assert.match(card, /点击展开/) @@ -539,7 +545,7 @@ test('nested market detail cards use explicit labels instead of API property nam const detailSection = source('./components/MarketDetailSection.vue') assert.match(detailSection, /cards:/) - assert.match(detailSection, /field\.tagType \|\| resolveStatusTone\(field\.value\)/) + assert.match(detailSection, /resolveStatusTone\(field\.value, field\.tagType\)/) assert.doesNotMatch(detailSection, /function flattenFields/) }) @@ -684,7 +690,7 @@ test('protocol create and edit forms expose every writable web business field', assert.equal(bySection.samples, undefined) assert.deepEqual(bySection.attachments, [ 'protocolAttachments', 'consentFormUrls', 'healthSurveyFormUrls', - 'attachmentUrls', 'infectiousDiseaseReportUrls' + 'infectiousDiseaseReportUrls', 'attachmentUrls' ]) const customer = module.formSections.find((section) => section.key === 'customer') assert.deepEqual( @@ -708,6 +714,9 @@ test('protocol create and edit forms expose every writable web business field', assert.match(form, /protocolTotalFollowSamples/) assert.match(form, /syncProtocolTotalFromSamples/) assert.match(form, /sampleContractSum/) + assert.match(form, /refreshSampleCodes/) + assert.match(form, /@refresh-sample-codes="refreshSampleCodes"/) + assert.match(form, /codeFilter/) const samplePanel = source('../market/components/ProtocolSampleTransportPanel.vue') assert.match(samplePanel, /添加样本/) assert.match(samplePanel, /配置运费/) @@ -722,6 +731,9 @@ test('protocol create and edit forms expose every writable web business field', assert.match(samplePanel, /codeSelectOptions/) assert.match(samplePanel, /searchable/) assert.match(samplePanel, /market-form-field/) + assert.match(samplePanel, /resolveSampleCodeFilter/) + assert.match(samplePanel, /refresh-sample-codes/) + assert.match(samplePanel, /sampleCodeFilter/) const payPanel = source('../market/components/ProtocolSamplePaymentPanel.vue') const transferEditor = source('../market/components/ScheduleTransferEditor.vue') assert.match(payPanel, /applyPreferentialPriceToPayment/) @@ -755,6 +767,10 @@ test('protocol create and edit forms expose every writable web business field', assert.doesNotMatch(form, /:can-manage-transfers="true"/) assert.match(form, /presignDepositHint/) assert.match(form, /depositTransfers/) + assert.match(form, /PresignTransferCard/) + assert.match(form, /showDepositSection/) + assert.match(form, / { - assert.equal(resolveStatusTone('待处理', 'info'), 'info') + assert.equal(resolveStatusTone('待处理', 'warning'), 'warning') assert.equal(resolveStatusTone('失败', 'not-a-tone'), 'danger') }) +test('maps Element Plus info tone to Vant default so unpaid tags stay visible', () => { + // Vant Tag has no `info`; bare van-tag--info is white text on transparent → invisible. + // Market CSS then skins van-tag--default as barley soft (#fff7e8 / #9a7020). + assert.equal(resolveStatusTone('待缴费', 'info'), 'default') + assert.equal(resolveStatusTone('停用'), 'default') + assert.equal(resolveStatusTone('', 'info'), 'default') +}) + test('matches negative status before positive substrings', () => { assert.equal(resolveStatusTone('不通过'), 'danger') assert.equal(resolveStatusTone('通过'), 'success') diff --git a/mobile-oa-h5/src/styles/market.css b/mobile-oa-h5/src/styles/market.css index 84096da8..3a07eb2a 100644 --- a/mobile-oa-h5/src/styles/market.css +++ b/mobile-oa-h5/src/styles/market.css @@ -1214,22 +1214,10 @@ body.van-toast--unclickable .presign-order-page * { border-color: #f0c989; } -.market-deposit-transfer { - display: flex; - flex-wrap: wrap; - gap: 8px 12px; - align-items: center; +.market-deposit-transfer-list { + display: grid; + gap: 12px; margin-top: 10px; - padding: 10px 12px; - border: 1px solid #e3f0ea; - border-radius: 12px; - background: #f7fffb; - color: #18332a; - font-size: 13px; -} - -.market-deposit-transfer strong { - font-weight: 600; } .market-form-field { @@ -1511,6 +1499,14 @@ body.van-toast--unclickable .presign-order-page * { background: var(--market-primary); } +/* 中性态(info→default):大麦黄浅底,区别于 Vant 灰底白字与 warning 实心橙 */ +.market-page .van-tag--default, +.presign-order-page .van-tag--default { + background: #fff7e8; + color: #9a7020; + border: 1px solid #f0e0b8; +} + @media (min-width: 414px) { .market-page__body { padding-right: 18px;