feat: 小程序首页入口icon优化

This commit is contained in:
李琦
2026-08-18 12:51:48 +08:00
parent d159e514b2
commit b685257a93
2 changed files with 247 additions and 17 deletions

View File

@@ -69,7 +69,8 @@ export const modalFormProps: VbenFormProps = {
{
component: 'Avatar',
fieldName: 'icon',
label: '图',
label: '入口图',
help: '患者端首页专区入口图,建议透明底。未上传时用默认矢量图。',
defaultValue: '',
},
{

View File

@@ -1,27 +1,64 @@
<script lang="ts" setup>
/**
* 系统配置 - 患者端小程序 / 首页展示:金刚区布局 + 订单提醒样式
* 系统配置 - 患者端小程序 / 首页展示:待办入口排布 + 入口图 + 订单待办样式
* 排布作用在待支付挂号/商品、已发货、待确认收货,不新增药店专区
*/
import { onMounted, ref } from 'vue';
import { onMounted, reactive, ref } from 'vue';
import { Button, message, Radio } from 'ant-design-vue';
import FormAvatar from '#/components/form/components/avatar.vue';
import { getSystemConfigByKeys, saveSystemConfig } from '../api';
defineOptions({ name: 'HomeDisplayConfigPanel' });
const REMIND_ICON_FIELDS = [
{
key: 'unpaid_register',
configKey: 'home_remind_icon_unpaid_register',
label: '待支付挂号',
sort: 82,
},
{
key: 'unpaid_product',
configKey: 'home_remind_icon_unpaid_product',
label: '待支付商品',
sort: 83,
},
{
key: 'shipped',
configKey: 'home_remind_icon_shipped',
label: '已发货',
sort: 84,
},
{
key: 'arrived',
configKey: 'home_remind_icon_arrived',
label: '待确认收货',
sort: 85,
},
] as const;
const loading = ref(false);
const saving = ref(false);
const zoneLayout = ref<'scroll' | 'wrap2' | 'wrap'>('scroll');
const orderRemindStyle = ref<'grid' | 'zone'>('grid');
const remindIcons = reactive<Record<string, string>>({
unpaid_register: '',
unpaid_product: '',
shipped: '',
arrived: '',
});
/** 读取首页展示样式,缺省回落默认值 */
/** 读取排布、样式和四张待办入口图 */
async function load() {
loading.value = true;
try {
const res = await getSystemConfigByKeys([
'home_zone_layout',
'home_order_remind_style',
...REMIND_ICON_FIELDS.map((item) => item.configKey),
]);
const data = res && typeof res === 'object' ? res : {};
const layout = String(data.home_zone_layout || 'scroll');
@@ -29,6 +66,9 @@ async function load() {
layout === 'wrap' || layout === 'wrap2' ? layout : 'scroll';
orderRemindStyle.value =
String(data.home_order_remind_style || '') === 'zone' ? 'zone' : 'grid';
for (const item of REMIND_ICON_FIELDS) {
remindIcons[item.key] = String(data[item.configKey] || '');
}
} catch {
zoneLayout.value = 'scroll';
orderRemindStyle.value = 'grid';
@@ -37,7 +77,7 @@ async function load() {
}
}
/** 写入系统配置,患者端首页即时读取 */
/** 写入系统配置,患者端首页即时读取入口图和排布 */
async function handleSave() {
saving.value = true;
try {
@@ -47,7 +87,8 @@ async function handleSave() {
config_value: zoneLayout.value,
value_type: 'string',
config_group: 'home',
description: '首页金刚区布局scroll横向滑 / wrap换行 / wrap2一行两个',
description:
'首页待办入口排布scroll横向滑 / wrap一行五个 / wrap2一行两个',
sort: 80,
},
{
@@ -55,9 +96,17 @@ async function handleSave() {
config_value: orderRemindStyle.value,
value_type: 'string',
config_group: 'home',
description: '首页订单提醒样式grid一行两列卡片 / zone金刚区图标',
description: '首页订单待办样式grid一行两列卡片 / zone图标入口',
sort: 81,
},
...REMIND_ICON_FIELDS.map((item) => ({
config_key: item.configKey,
config_value: remindIcons[item.key] || '',
value_type: 'string',
config_group: 'home',
description: `首页待办入口图:${item.label}`,
sort: item.sort,
})),
]);
message.success('保存成功');
} finally {
@@ -72,23 +121,88 @@ onMounted(load);
<div class="cfg-panel" :class="{ 'opacity-60': loading }">
<div class="cfg-panel-body">
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">
金刚区布局
待办入口排布
</div>
<div class="mb-2 text-sm text-[hsl(var(--muted-foreground))]">
控制患者端首页专区入口的排列方式
<div class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
控制首页待支付挂号待支付商品已发货待确认收货的排列方式图标入口时生效药店专区入口也沿用同一排布
</div>
<div class="layout-pick">
<button
type="button"
class="layout-pick-card"
:class="{ 'is-active': zoneLayout === 'scroll' }"
@click="zoneLayout = 'scroll'"
>
<div class="layout-mock layout-mock--scroll">
<i class="dot dot-a"></i>
<i class="dot dot-b"></i>
<i class="dot dot-c"></i>
<i class="dot dot-d"></i>
<i class="dot dot-fade"></i>
</div>
<div class="layout-pick-title">横向滑动</div>
<div class="layout-pick-desc">一排图标左右滑看更多</div>
</button>
<button
type="button"
class="layout-pick-card"
:class="{ 'is-active': zoneLayout === 'wrap' }"
@click="zoneLayout = 'wrap'"
>
<div class="layout-mock layout-mock--wrap">
<i class="dot dot-a"></i>
<i class="dot dot-b"></i>
<i class="dot dot-c"></i>
<i class="dot dot-d"></i>
<i class="dot dot-empty"></i>
</div>
<div class="layout-pick-title">换行一行五个</div>
<div class="layout-pick-desc">五个一排多了自动换行</div>
</button>
<button
type="button"
class="layout-pick-card"
:class="{ 'is-active': zoneLayout === 'wrap2' }"
@click="zoneLayout = 'wrap2'"
>
<div class="layout-mock layout-mock--wrap2">
<i class="tile tile-a"></i>
<i class="tile tile-b"></i>
<i class="tile tile-c"></i>
<i class="tile tile-d"></i>
</div>
<div class="layout-pick-title">一行两个</div>
<div class="layout-pick-desc">两列卡片适合配入口图</div>
</button>
</div>
<div class="mb-2 mt-6 font-medium text-[hsl(var(--foreground))]">
待办入口图
</div>
<div class="mb-3 text-sm text-[hsl(var(--muted-foreground))]">
图标入口展示时使用未上传则用患者端默认图也可把生成图上传到这里替换
</div>
<div class="remind-icon-grid">
<div
v-for="item in REMIND_ICON_FIELDS"
:key="item.key"
class="remind-icon-cell"
>
<FormAvatar
:value="remindIcons[item.key]"
@update:value="(url) => (remindIcons[item.key] = url || '')"
/>
<div class="remind-icon-label">{{ item.label }}</div>
</div>
</div>
<div class="mb-2 mt-6 font-medium text-[hsl(var(--foreground))]">
订单待办样式
</div>
<Radio.Group v-model:value="zoneLayout" class="mb-6">
<Radio value="scroll">横向滑动</Radio>
<Radio value="wrap">换行一行五个</Radio>
<Radio value="wrap2">一行两个</Radio>
</Radio.Group>
<div class="mb-2 font-medium text-[hsl(var(--foreground))]">订单提醒</div>
<div class="mb-2 text-sm text-[hsl(var(--muted-foreground))]">
首页展示待支付挂号待支付商品已发货快递待收货已到货待确认收货自提
</div>
<Radio.Group v-model:value="orderRemindStyle">
<Radio value="grid">一行两列卡片</Radio>
<Radio value="zone">金刚区图标</Radio>
<Radio value="zone">图标入口</Radio>
</Radio.Group>
</div>
<div class="cfg-panel-footer">
@@ -98,3 +212,118 @@ onMounted(load);
</div>
</div>
</template>
<style scoped>
.layout-pick {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.layout-pick-card {
display: flex;
flex-direction: column;
align-items: stretch;
padding: 12px;
cursor: pointer;
background: hsl(var(--card, var(--background)));
border: 1px solid hsl(var(--border));
border-radius: 10px;
text-align: left;
}
.layout-pick-card.is-active {
border: 1px solid hsl(var(--primary) / 30%);
box-shadow: 0 0 6px hsl(var(--primary) / 18%);
}
.layout-pick-title {
margin-top: 10px;
font-size: 13px;
font-weight: 600;
color: hsl(var(--foreground));
}
.layout-pick-desc {
margin-top: 2px;
font-size: 12px;
color: hsl(var(--muted-foreground));
}
.layout-mock {
display: flex;
align-items: center;
min-height: 56px;
padding: 8px;
background: hsl(var(--muted) / 0.25);
border: 1px solid hsl(var(--border));
border-radius: 8px;
}
.layout-mock--scroll {
flex-wrap: nowrap;
gap: 6px;
overflow: hidden;
}
.layout-mock--wrap {
flex-wrap: wrap;
gap: 6px;
}
.layout-mock--wrap2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.dot,
.tile {
display: block;
border-radius: 6px;
}
.dot {
width: 18px;
height: 18px;
flex-shrink: 0;
}
.tile {
height: 18px;
}
.dot-a,
.tile-a {
background: #4175ee;
}
.dot-b,
.tile-b {
background: #00b578;
}
.dot-c,
.tile-c {
background: #e67e00;
}
.dot-d,
.tile-d {
background: #7c3aed;
}
.dot-fade {
opacity: 0.35;
background: hsl(var(--muted-foreground));
}
.dot-empty {
background: transparent;
border: 1px dashed hsl(var(--border));
}
.remind-icon-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 16px;
}
.remind-icon-cell {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.remind-icon-label {
font-size: 13px;
color: hsl(var(--foreground));
}
@media (max-width: 900px) {
.layout-pick,
.remind-icon-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
</style>