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;