Merge pull request '20261027' (#13) from test into master
All checks were successful
Build and Deploy Front / deploy (push) Successful in 2m16s
All checks were successful
Build and Deploy Front / deploy (push) Successful in 2m16s
Reviewed-on: #13
This commit is contained in:
commit
3d7a19f4a7
@ -1,17 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { getWarehouseList } from '@/api/system/warehouse'
|
||||
import { useCurrentWarehouseStore } from '@/store'
|
||||
import WarehouseAddressPeekModal from '@/components/customer-side-rail/WarehouseAddressPeekModal.vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
variant?: 'navbar' | 'login' | 'sider'
|
||||
/** 下拉位置;侧栏沉底用 rt */
|
||||
/** 下拉位置;侧栏沉底用 rt(login 下拉保留) */
|
||||
dropdownPosition?: 'br' | 'bl' | 'tr' | 'tl' | 'rt' | 'rb'
|
||||
/** 侧栏收起:窄宽展示缩略文案(无图标) */
|
||||
compact?: boolean
|
||||
/** 下拉触发;侧栏默认 click,顶栏 hover */
|
||||
/** 下拉触发;侧栏默认 click,顶栏 hover(已改为弹窗,仅 login 相关遗留) */
|
||||
trigger?: 'hover' | 'click'
|
||||
/** 侧栏展开:显示「发货仓库」语义前缀 */
|
||||
showCaption?: boolean
|
||||
@ -29,6 +30,7 @@ const props = withDefaults(
|
||||
|
||||
const { t } = useI18n()
|
||||
const warehouseStore = useCurrentWarehouseStore()
|
||||
const peekVisible = ref(false)
|
||||
|
||||
const displayName = computed(
|
||||
() =>
|
||||
@ -45,24 +47,13 @@ const labelText = computed(() => {
|
||||
return name
|
||||
})
|
||||
|
||||
const useDropdown = computed(() => props.variant === 'navbar' || props.variant === 'sider')
|
||||
|
||||
const dropdownTrigger = computed(() => {
|
||||
if (props.trigger) return props.trigger
|
||||
return props.variant === 'sider' ? 'click' : 'hover'
|
||||
})
|
||||
/** 顶栏 / 侧栏:点击打开仓库地址卡片弹窗(与客户侧栏一致) */
|
||||
const usePeekModal = computed(() => props.variant === 'navbar' || props.variant === 'sider')
|
||||
|
||||
const showSiderCaption = computed(
|
||||
() => props.showCaption && props.variant === 'sider' && !props.compact,
|
||||
)
|
||||
|
||||
const contentClass = computed(() => props.contentClass || undefined)
|
||||
|
||||
const popupTranslate = computed<[number, number] | undefined>(() => {
|
||||
const pos = props.dropdownPosition
|
||||
return pos === 'tl' || pos === 'tr' ? [0, -6] : undefined
|
||||
})
|
||||
|
||||
async function loadServiceDataList() {
|
||||
try {
|
||||
const { items } = await getWarehouseList({
|
||||
@ -82,8 +73,8 @@ async function loadServiceDataList() {
|
||||
}
|
||||
}
|
||||
|
||||
function onSelectWarehouse(id: number) {
|
||||
warehouseStore.setWarehouseId(id)
|
||||
function openPeek() {
|
||||
peekVisible.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -99,37 +90,19 @@ onMounted(() => {
|
||||
{ 'is-compact': compact },
|
||||
]"
|
||||
>
|
||||
<a-dropdown
|
||||
v-if="useDropdown"
|
||||
:trigger="dropdownTrigger"
|
||||
:position="dropdownPosition"
|
||||
:content-class="contentClass"
|
||||
:popup-translate="popupTranslate"
|
||||
:popup-max-height="320"
|
||||
<button
|
||||
v-if="usePeekModal"
|
||||
type="button"
|
||||
class="contact-service__label"
|
||||
:title="displayName"
|
||||
:aria-label="`${t('nav.contactService.shipWarehouse')}: ${displayName}`"
|
||||
@click="openPeek"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="contact-service__label"
|
||||
:title="displayName"
|
||||
:aria-label="`${t('nav.contactService.shipWarehouse')}: ${displayName}`"
|
||||
>
|
||||
<span v-if="showSiderCaption" class="contact-service__caption">
|
||||
{{ t('nav.contactService.shipWarehouse') }}
|
||||
</span>
|
||||
<span class="contact-service__text">{{ labelText }}</span>
|
||||
</button>
|
||||
<template #content>
|
||||
<a-doption
|
||||
v-for="opt in warehouseStore.options"
|
||||
:key="String(opt.value)"
|
||||
:value="opt.value"
|
||||
:selected="opt.value === warehouseStore.warehouseId"
|
||||
@click="onSelectWarehouse(opt.value)"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<span v-if="showSiderCaption" class="contact-service__caption">
|
||||
{{ t('nav.contactService.shipWarehouse') }}
|
||||
</span>
|
||||
<span class="contact-service__text">{{ labelText }}</span>
|
||||
</button>
|
||||
|
||||
<a-select
|
||||
v-else
|
||||
@ -147,6 +120,11 @@ onMounted(() => {
|
||||
{{ opt.label }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
|
||||
<WarehouseAddressPeekModal
|
||||
v-if="usePeekModal"
|
||||
v-model:visible="peekVisible"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ARCO_ICON_NAMES, filterArcoIconNames } from '@/utils/arco-icon-list';
|
||||
import { filterArcoIconNames } from '@/utils/arco-icon-list';
|
||||
import { MENU_ICON_CATEGORIES } from '@/utils/menu-icon-catalog';
|
||||
import { resolveArcoIcon, toPascalIconName } from '@/utils/menu-icon';
|
||||
|
||||
defineOptions({ name: 'MenuIconPicker' });
|
||||
@ -28,60 +29,33 @@
|
||||
const pickerVisible = ref(false);
|
||||
const keyword = ref('');
|
||||
const draftIcon = ref('');
|
||||
const activeCategory = ref(MENU_ICON_CATEGORIES[0]?.key || 'common');
|
||||
|
||||
const normalizedValue = computed(() => toPascalIconName(props.modelValue || ''));
|
||||
const previewIcon = computed(() => resolveArcoIcon(normalizedValue.value));
|
||||
|
||||
const filteredIcons = computed(() => {
|
||||
const list = filterArcoIconNames(keyword.value)
|
||||
if (keyword.value.trim()) return list
|
||||
const commonSet = new Set(commonIcons)
|
||||
return list.filter((name) => !commonSet.has(name))
|
||||
const categoryTabs = computed(() =>
|
||||
MENU_ICON_CATEGORIES.map((cat) => ({
|
||||
key: cat.key,
|
||||
label: t(`system.menu.iconPicker.category.${cat.key}`),
|
||||
count: cat.icons.length,
|
||||
})),
|
||||
);
|
||||
|
||||
const categoryIcons = computed(() => {
|
||||
const cat = MENU_ICON_CATEGORIES.find((item) => item.key === activeCategory.value);
|
||||
return cat?.icons || [];
|
||||
});
|
||||
|
||||
const commonIcons = [
|
||||
'IconHome',
|
||||
'IconMenu',
|
||||
'IconApps',
|
||||
'IconBook',
|
||||
'IconFile',
|
||||
'IconFolder',
|
||||
'IconList',
|
||||
'IconOrderedList',
|
||||
'IconSettings',
|
||||
'IconUser',
|
||||
'IconUserGroup',
|
||||
'IconSearch',
|
||||
'IconPlus',
|
||||
'IconEdit',
|
||||
'IconDelete',
|
||||
'IconRefresh',
|
||||
'IconLink',
|
||||
'IconSend',
|
||||
'IconDownload',
|
||||
'IconUpload',
|
||||
'IconCloud',
|
||||
'IconComputer',
|
||||
'IconStorage',
|
||||
'IconTag',
|
||||
'IconLocation',
|
||||
'IconBarChart',
|
||||
'IconSchedule',
|
||||
'IconSafe',
|
||||
'IconLock',
|
||||
'IconPrinter',
|
||||
'IconBug',
|
||||
'IconCode',
|
||||
'IconExperiment',
|
||||
'IconThunderbolt',
|
||||
'IconRobot',
|
||||
'IconDashboard',
|
||||
].filter((name) => ARCO_ICON_NAMES.includes(name));
|
||||
const searchIcons = computed(() => filterArcoIconNames(keyword.value));
|
||||
|
||||
const isSearching = computed(() => Boolean(keyword.value.trim()));
|
||||
|
||||
watch(pickerVisible, (visible) => {
|
||||
if (visible) {
|
||||
draftIcon.value = normalizedValue.value;
|
||||
keyword.value = '';
|
||||
activeCategory.value = MENU_ICON_CATEGORIES[0]?.key || 'common';
|
||||
}
|
||||
});
|
||||
|
||||
@ -133,7 +107,7 @@
|
||||
v-model:visible="pickerVisible"
|
||||
:title="t('system.menu.iconPicker.title')"
|
||||
:footer="false"
|
||||
width="720px"
|
||||
width="840px"
|
||||
unmount-on-close
|
||||
class="menu-icon-picker-modal"
|
||||
>
|
||||
@ -150,33 +124,74 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!keyword.trim()" class="picker-section">
|
||||
<div class="picker-section-title">{{ t('system.menu.iconPicker.common') }}</div>
|
||||
<div class="icon-grid">
|
||||
<template v-if="!isSearching">
|
||||
<div class="picker-category-tabs">
|
||||
<button
|
||||
v-for="name in commonIcons"
|
||||
:key="`common-${name}`"
|
||||
v-for="tab in categoryTabs"
|
||||
:key="tab.key"
|
||||
type="button"
|
||||
class="icon-item"
|
||||
:class="{ active: draftIcon === name }"
|
||||
:title="name"
|
||||
@click="selectIcon(name)"
|
||||
class="category-tab"
|
||||
:class="{ active: activeCategory === tab.key }"
|
||||
@click="activeCategory = tab.key"
|
||||
>
|
||||
<component :is="resolveArcoIcon(name)" />
|
||||
<span class="icon-name">{{ name.replace(/^Icon/, '') }}</span>
|
||||
<span>{{ tab.label }}</span>
|
||||
<em>{{ tab.count }}</em>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="picker-section picker-section-scroll">
|
||||
<div class="picker-section">
|
||||
<div class="picker-section-title">
|
||||
{{ t(`system.menu.iconPicker.category.${activeCategory}`) }}
|
||||
<span class="picker-count">({{ categoryIcons.length }})</span>
|
||||
</div>
|
||||
<div class="icon-grid">
|
||||
<button
|
||||
v-for="name in categoryIcons"
|
||||
:key="`cat-${name}`"
|
||||
type="button"
|
||||
class="icon-item"
|
||||
:class="{ active: draftIcon === name }"
|
||||
:title="name"
|
||||
@click="selectIcon(name)"
|
||||
>
|
||||
<component :is="resolveArcoIcon(name)" />
|
||||
<span class="icon-name">{{ name.replace(/^Icon/, '') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="picker-section picker-section-scroll">
|
||||
<div class="picker-section-title">
|
||||
{{ t('system.menu.iconPicker.all') }}
|
||||
<span class="picker-count">({{ searchIcons.length }})</span>
|
||||
<span class="picker-hint">{{ t('system.menu.iconPicker.allHint') }}</span>
|
||||
</div>
|
||||
<div class="icon-grid">
|
||||
<button
|
||||
v-for="name in searchIcons"
|
||||
:key="`all-${name}`"
|
||||
type="button"
|
||||
class="icon-item"
|
||||
:class="{ active: draftIcon === name }"
|
||||
:title="name"
|
||||
@click="selectIcon(name)"
|
||||
>
|
||||
<component :is="resolveArcoIcon(name)" />
|
||||
<span class="icon-name">{{ name.replace(/^Icon/, '') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="picker-section picker-section-scroll">
|
||||
<div class="picker-section-title">
|
||||
{{ keyword.trim() ? t('system.menu.iconPicker.searchResult') : t('system.menu.iconPicker.all') }}
|
||||
<span class="picker-count">({{ filteredIcons.length }})</span>
|
||||
{{ t('system.menu.iconPicker.searchResult') }}
|
||||
<span class="picker-count">({{ searchIcons.length }})</span>
|
||||
</div>
|
||||
<div class="icon-grid">
|
||||
<button
|
||||
v-for="name in filteredIcons"
|
||||
:key="name"
|
||||
v-for="name in searchIcons"
|
||||
:key="`search-${name}`"
|
||||
type="button"
|
||||
class="icon-item"
|
||||
:class="{ active: draftIcon === name }"
|
||||
@ -188,7 +203,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<a-empty
|
||||
v-if="filteredIcons.length === 0"
|
||||
v-if="searchIcons.length === 0"
|
||||
:description="t('system.menu.iconPicker.empty')"
|
||||
/>
|
||||
</div>
|
||||
@ -236,7 +251,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.picker-current {
|
||||
@ -254,6 +269,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
.picker-category-tabs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.category-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
border: 1px solid var(--color-border-2);
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
color: var(--color-text-2);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
em {
|
||||
font-style: normal;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--app-primary-color);
|
||||
color: var(--app-primary-color);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: var(--app-primary-color);
|
||||
background: var(--app-primary-color-light, #eef7f8);
|
||||
color: var(--app-primary-color);
|
||||
|
||||
em {
|
||||
color: var(--app-primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.picker-section {
|
||||
& + .picker-section {
|
||||
margin-top: 16px;
|
||||
@ -279,9 +336,16 @@
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
|
||||
.picker-hint {
|
||||
margin-left: 8px;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
|
||||
.icon-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
@ -716,14 +716,25 @@ const onMenuClick = async (event: { key: string }) => {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 固定宽度面板:禁止横向滚动条 */
|
||||
.arco-scrollbar-container {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.arco-scrollbar-track-direction-horizontal {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.csr-user-panel {
|
||||
/* 与客户侧栏展开宽对齐 */
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
background: #fff;
|
||||
color: var(--color-text-1);
|
||||
|
||||
@ -840,7 +851,8 @@ const onMenuClick = async (event: { key: string }) => {
|
||||
&__body {
|
||||
padding: 6px;
|
||||
max-height: min(52vh, 360px);
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
/**
|
||||
* 订单状态(与 oms0414-back OrderStatusCodes、设计文档 §10.3.1 一致)
|
||||
*
|
||||
* 标签色系:复用 utils/statusTag 信息强弱分档。
|
||||
* 标签色系:按生命周期语义分色;渲染仍走 utils/statusTag。
|
||||
*/
|
||||
import {
|
||||
STATUS_TAG_TONES,
|
||||
buildBooleanTagHtml,
|
||||
buildStatusTagHtml,
|
||||
getStatusTagStyle,
|
||||
type StatusTagColors,
|
||||
type StatusTagTone,
|
||||
} from '@/utils/statusTag'
|
||||
|
||||
@ -46,10 +45,6 @@ export function isOrderDeletable(status: unknown): boolean {
|
||||
return ORDER_DELETABLE_STATUSES.includes(code as OrderStatusCode)
|
||||
}
|
||||
|
||||
function tone(t: StatusTagTone): StatusTagColors {
|
||||
return STATUS_TAG_TONES[t]
|
||||
}
|
||||
|
||||
export interface OrderStatusMeta {
|
||||
value: OrderStatusCode
|
||||
/** i18n key:tabs.* 与 status.* 共用同一套文案 */
|
||||
@ -87,62 +82,106 @@ export const ORDER_STATUS_NAME_TO_CODE: Record<string, OrderStatusCode> = {
|
||||
已认领: 'claimed',
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单状态标签色:按生命周期语义分色(软底标签),便于列表一眼区分。
|
||||
* 待办暖色 / 在途品牌色 / 成功绿 / 失败红 / 问题橙 / 理赔紫。
|
||||
*/
|
||||
export const ORDER_STATUS_LIST: OrderStatusMeta[] = [
|
||||
{ value: 'draft', labelKey: 'order.myOrders.status.draft', ...tone('neutral') },
|
||||
{ value: 'predeclared', labelKey: 'order.myOrders.status.predeclared', ...tone('brand') },
|
||||
{
|
||||
value: 'draft',
|
||||
labelKey: 'order.myOrders.status.draft',
|
||||
tagColor: '#6b7280',
|
||||
tagBgColor: '#f3f4f6',
|
||||
tagBorder: '#e5e7eb',
|
||||
},
|
||||
{
|
||||
value: 'predeclared',
|
||||
labelKey: 'order.myOrders.status.predeclared',
|
||||
tagColor: '#3d6a8a',
|
||||
tagBgColor: '#eef4f8',
|
||||
tagBorder: '#d0dee8',
|
||||
},
|
||||
{
|
||||
value: 'pendingAllocation',
|
||||
labelKey: 'order.myOrders.status.pendingAllocation',
|
||||
...tone('wait'),
|
||||
tagColor: '#8a6d3b',
|
||||
tagBgColor: '#f7f2e8',
|
||||
tagBorder: '#e8dcc6',
|
||||
rowBg: '#faf8f5',
|
||||
showTabCount: true,
|
||||
},
|
||||
{
|
||||
value: 'pendingPayment',
|
||||
labelKey: 'order.myOrders.status.pendingPayment',
|
||||
tagColor: '#f53f3f',
|
||||
tagBgColor: '#ffece8',
|
||||
tagBorder: '#fbaca3',
|
||||
rowBg: '#ffece8',
|
||||
tagColor: '#ea580c',
|
||||
tagBgColor: '#fff4e5',
|
||||
tagBorder: '#fb923c',
|
||||
rowBg: '#fff7ed',
|
||||
showTabCount: true,
|
||||
},
|
||||
{
|
||||
value: 'pendingShip',
|
||||
labelKey: 'order.myOrders.status.pendingShip',
|
||||
...tone('wait'),
|
||||
tagColor: '#0f766e',
|
||||
tagBgColor: '#f0fdfa',
|
||||
tagBorder: '#99f6e4',
|
||||
showTabCount: true,
|
||||
},
|
||||
{
|
||||
value: 'pendingOnline',
|
||||
labelKey: 'order.myOrders.status.pendingOnline',
|
||||
...tone('brand'),
|
||||
tagColor: '#1d4ed8',
|
||||
tagBgColor: '#eff6ff',
|
||||
tagBorder: '#bfdbfe',
|
||||
showTabCount: true,
|
||||
},
|
||||
{
|
||||
value: 'shipping',
|
||||
labelKey: 'order.myOrders.status.shipping',
|
||||
...tone('brand'),
|
||||
tagColor: '#054f74',
|
||||
tagBgColor: '#eef3f6',
|
||||
tagBorder: '#b6cdd9',
|
||||
showTabCount: true,
|
||||
},
|
||||
{
|
||||
value: 'deliverySuccess',
|
||||
labelKey: 'order.myOrders.status.deliverySuccess',
|
||||
...tone('ok'),
|
||||
tagColor: '#15803d',
|
||||
tagBgColor: '#f0fdf4',
|
||||
tagBorder: '#bbf7d0',
|
||||
showTabCount: true,
|
||||
},
|
||||
{
|
||||
value: 'deliveryFailed',
|
||||
labelKey: 'order.myOrders.status.deliveryFailed',
|
||||
...tone('alert'),
|
||||
tagColor: '#b91c1c',
|
||||
tagBgColor: '#fef2f2',
|
||||
tagBorder: '#fecaca',
|
||||
showTabCount: true,
|
||||
},
|
||||
{ value: 'deleted', labelKey: 'order.myOrders.status.deleted', ...tone('mute') },
|
||||
{
|
||||
value: 'deleted',
|
||||
labelKey: 'order.myOrders.status.deleted',
|
||||
tagColor: '#9ca3af',
|
||||
tagBgColor: '#f9fafb',
|
||||
tagBorder: '#e5e7eb',
|
||||
},
|
||||
{
|
||||
value: 'problematic',
|
||||
labelKey: 'order.myOrders.status.problematic',
|
||||
...tone('alert'),
|
||||
tagColor: '#c2410c',
|
||||
tagBgColor: '#fff7ed',
|
||||
tagBorder: '#fdba74',
|
||||
rowBg: '#fff7ed',
|
||||
showTabCount: true,
|
||||
},
|
||||
{ value: 'claimed', labelKey: 'order.myOrders.status.claimed', ...tone('neutral') },
|
||||
{
|
||||
value: 'claimed',
|
||||
labelKey: 'order.myOrders.status.claimed',
|
||||
tagColor: '#6d28d9',
|
||||
tagBgColor: '#f5f3ff',
|
||||
tagBorder: '#ddd6fe',
|
||||
},
|
||||
]
|
||||
|
||||
/** 已出库/在途及之后(不可拦截) */
|
||||
|
||||
@ -358,6 +358,14 @@ const CORE_GROUPS: PageButtonGroup[] = [
|
||||
{ code: 'Query', name: '查询' },
|
||||
{ code: 'View', name: '查看监控' },
|
||||
]),
|
||||
B('Home.EventBusAdmin', [
|
||||
{ code: 'Query', name: '查询' },
|
||||
{ code: 'View', name: '查看 OMS EventBus' },
|
||||
]),
|
||||
B('Home.EventBusTrack', [
|
||||
{ code: 'Query', name: '查询' },
|
||||
{ code: 'View', name: '查看轨迹 EventBus' },
|
||||
]),
|
||||
|
||||
// ── API ──
|
||||
B('Api.UpstreamInterface', [
|
||||
|
||||
@ -7,6 +7,7 @@ import localeH5AdminCenter from '@/views/h5/admin-center/locale/en-US';
|
||||
import localeWorkplace from '@/views/home/workplace/locale/en-US';
|
||||
|
||||
import localeMonitor from '@/views/home/monitor/locale/en-US';
|
||||
import localeEventBus from '@/views/home/eventbus/locale/en-US';
|
||||
|
||||
import localeSettings from './en-US/settings';
|
||||
import marketingEn from './marketing/en-US';
|
||||
@ -3442,7 +3443,7 @@ export default {
|
||||
'system.menu.message.syncLevel3Skipped': 'Nothing to sync, {skipped} existing button(s) skipped',
|
||||
'system.menu.message.syncLevel3Failed': 'Failed to sync level-3 buttons',
|
||||
'system.menu.message.syncModuleSuccess':
|
||||
'Synced: {level2Created} level-2 created, {level2Skipped} skipped; level-3 {level3Created} created, {level3Updated} updated, {level3Deleted} deleted, {level3Skipped} skipped',
|
||||
'Synced: {level2Created} level-2 created, {level2Updated} updated, {level2Skipped} skipped; level-3 {level3Created} created, {level3Updated} updated, {level3Deleted} deleted, {level3Skipped} skipped',
|
||||
'system.menu.message.syncModuleSkipped':
|
||||
'Nothing to sync, skipped {level2Skipped} level-2 menu(s) and {level3Skipped} level-3 button(s)',
|
||||
'system.menu.message.syncModulePartialFailed': 'Some pages failed to sync level-3 buttons: {details}',
|
||||
@ -3646,12 +3647,21 @@ export default {
|
||||
'system.menu.iconPicker.choose': 'Choose icon',
|
||||
'system.menu.iconPicker.title': 'Choose menu icon',
|
||||
'system.menu.iconPicker.placeholder': 'Enter or pick an icon, e.g. IconBook',
|
||||
'system.menu.iconPicker.searchPlaceholder': 'Search icon name, e.g. Book, User, File',
|
||||
'system.menu.iconPicker.searchPlaceholder': 'Search icon name, e.g. Book, User, File, Dashboard',
|
||||
'system.menu.iconPicker.current': 'Selected',
|
||||
'system.menu.iconPicker.common': 'Common',
|
||||
'system.menu.iconPicker.all': 'All icons',
|
||||
'system.menu.iconPicker.allHint': 'Or search by name',
|
||||
'system.menu.iconPicker.searchResult': 'Search results',
|
||||
'system.menu.iconPicker.empty': 'No matching icons',
|
||||
'system.menu.iconPicker.category.common': 'Common',
|
||||
'system.menu.iconPicker.category.nav': 'Navigation',
|
||||
'system.menu.iconPicker.category.user': 'Users',
|
||||
'system.menu.iconPicker.category.order': 'Orders / Warehouse',
|
||||
'system.menu.iconPicker.category.finance': 'Finance / Marketing',
|
||||
'system.menu.iconPicker.category.track': 'Tracking / Logistics',
|
||||
'system.menu.iconPicker.category.system': 'System / Dev',
|
||||
'system.menu.iconPicker.category.status': 'Status',
|
||||
|
||||
// System - Role Management (Permission Assignment)
|
||||
'system.role.permission.expandAll': 'Expand All',
|
||||
@ -6811,6 +6821,7 @@ export default {
|
||||
...localeWorkplace,
|
||||
|
||||
...localeMonitor,
|
||||
...localeEventBus,
|
||||
...marketingEn,
|
||||
...kbEn,
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import localeH5AdminCenter from '@/views/h5/admin-center/locale/zh-CN';
|
||||
import localeWorkplace from '@/views/home/workplace/locale/zh-CN';
|
||||
|
||||
import localeMonitor from '@/views/home/monitor/locale/zh-CN';
|
||||
import localeEventBus from '@/views/home/eventbus/locale/zh-CN';
|
||||
|
||||
import localeSettings from './zh-CN/settings';
|
||||
import marketingZh from './marketing/zh-CN';
|
||||
@ -3300,7 +3301,7 @@ export default {
|
||||
'system.menu.message.syncLevel3Skipped': '无需同步,已跳过 {skipped} 个已存在按钮',
|
||||
'system.menu.message.syncLevel3Failed': '同步三级按钮失败',
|
||||
'system.menu.message.syncModuleSuccess':
|
||||
'同步完成:二级菜单新增 {level2Created} 个、跳过 {level2Skipped} 个;三级按钮新增 {level3Created} 个、修改 {level3Updated} 个、删除 {level3Deleted} 个、跳过 {level3Skipped} 个',
|
||||
'同步完成:二级菜单新增 {level2Created} 个、更新 {level2Updated} 个、跳过 {level2Skipped} 个;三级按钮新增 {level3Created} 个、修改 {level3Updated} 个、删除 {level3Deleted} 个、跳过 {level3Skipped} 个',
|
||||
'system.menu.message.syncModuleSkipped':
|
||||
'无需同步,已跳过 {level2Skipped} 个二级菜单、{level3Skipped} 个三级按钮',
|
||||
'system.menu.message.syncModulePartialFailed': '部分页面三级按钮同步失败:{details}',
|
||||
@ -3503,12 +3504,21 @@ export default {
|
||||
'system.menu.iconPicker.choose': '选择图标',
|
||||
'system.menu.iconPicker.title': '选择菜单图标',
|
||||
'system.menu.iconPicker.placeholder': '请输入或选择图标,如 IconBook',
|
||||
'system.menu.iconPicker.searchPlaceholder': '搜索图标名称,如 Book、User、File',
|
||||
'system.menu.iconPicker.searchPlaceholder': '搜索图标名称,如 Book、User、File、Dashboard',
|
||||
'system.menu.iconPicker.current': '当前选中',
|
||||
'system.menu.iconPicker.common': '常用图标',
|
||||
'system.menu.iconPicker.all': '全部图标',
|
||||
'system.menu.iconPicker.allHint': '也可直接搜索名称',
|
||||
'system.menu.iconPicker.searchResult': '搜索结果',
|
||||
'system.menu.iconPicker.empty': '未找到匹配图标',
|
||||
'system.menu.iconPicker.category.common': '常用',
|
||||
'system.menu.iconPicker.category.nav': '导航布局',
|
||||
'system.menu.iconPicker.category.user': '用户组织',
|
||||
'system.menu.iconPicker.category.order': '订单仓储',
|
||||
'system.menu.iconPicker.category.finance': '财务营销',
|
||||
'system.menu.iconPicker.category.track': '轨迹物流',
|
||||
'system.menu.iconPicker.category.system': '系统开发',
|
||||
'system.menu.iconPicker.category.status': '状态提示',
|
||||
|
||||
// 系统 - 角色管理(权限分配相关)
|
||||
'system.role.permission.adminAllHint': 'Admin 角色默认拥有全部菜单与按钮权限,无需分配',
|
||||
@ -6820,6 +6830,7 @@ export default {
|
||||
...localeWorkplace,
|
||||
|
||||
...localeMonitor,
|
||||
...localeEventBus,
|
||||
...marketingZh,
|
||||
...kbZh,
|
||||
};
|
||||
|
||||
@ -32,6 +32,38 @@ const DASHBOARD: AppRouteRecordRaw = {
|
||||
permission: 'Home.Monitor',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'adminEventBus',
|
||||
name: 'AdminEventBus',
|
||||
component: () => import('@/views/home/eventbus/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.home.adminEventBus',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
adminPortalOnly: true,
|
||||
menuPermission: 'Home.EventBusAdmin',
|
||||
icon: 'icon-mind-mapping',
|
||||
order: 10,
|
||||
ignoreCache: true,
|
||||
frameSrc: 'https://api.test.ideship.com/admin/eventbus',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'trackEventBus',
|
||||
name: 'TrackEventBus',
|
||||
component: () => import('@/views/home/eventbus/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.home.trackEventBus',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
adminPortalOnly: true,
|
||||
menuPermission: 'Home.EventBusTrack',
|
||||
icon: 'icon-relation',
|
||||
order: 11,
|
||||
ignoreCache: true,
|
||||
frameSrc: 'https://api.test.ideship.com/track/eventbus',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
2
src/router/typings.d.ts
vendored
2
src/router/typings.d.ts
vendored
@ -18,5 +18,7 @@ declare module 'vue-router' {
|
||||
order?: number; // Sort routing menu items. If set key, the higher the value, the more forward it is
|
||||
noAffix?: boolean; // if set true, the tag will not affix in the tab-bar
|
||||
ignoreCache?: boolean; // if set true, the page will not be cached
|
||||
/** 外链嵌入页地址(iframe src),如 EventBus Dashboard */
|
||||
frameSrc?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,10 +95,17 @@ const ORDER_NUMBER_FIELD_MAP: Record<string, FieldMapEntry> = {
|
||||
|
||||
const ORDER_EXTRA_FIELD_MAP: Record<string, FieldMapEntry> = {
|
||||
orderStatus: { field: 'orderStatus', options: { multiAsIn: true } },
|
||||
previousOrderStatus: { field: 'previousOrderStatus', options: { multiAsIn: true } },
|
||||
handleStatus: { field: 'handleStatus', options: { multiAsIn: true } },
|
||||
fulfillmentType: { field: 'fulfillmentType', options: { multiAsIn: true } },
|
||||
stockBindStatus: { field: 'stockBindStatus', options: { multiAsIn: true } },
|
||||
fulfillProgress: { field: 'fulfillProgress', options: { multiAsIn: true } },
|
||||
shippingMethod: 'productCode',
|
||||
serviceChannel: 'channelCode',
|
||||
isPaid: { field: 'isPaid', options: { multiAsIn: true } },
|
||||
isIntercept: { field: 'isIntercept', options: { multiAsIn: true } },
|
||||
isRemote: { field: 'isRemote', options: { multiAsIn: true } },
|
||||
isCod: { field: 'isCod', options: { multiAsIn: true } },
|
||||
receivableAmount: { field: 'payableAmount', options: { asNumber: true } },
|
||||
payableAmount: { field: 'payableAmount', options: { asNumber: true } },
|
||||
predeclareFailReason: 'predeclareFailReason',
|
||||
|
||||
475
src/utils/menu-icon-catalog.ts
Normal file
475
src/utils/menu-icon-catalog.ts
Normal file
@ -0,0 +1,475 @@
|
||||
import { ARCO_ICON_NAMES } from '@/utils/arco-icon-list'
|
||||
|
||||
export interface MenuIconCategory {
|
||||
/** i18n key: system.menu.iconPicker.category.{key} */
|
||||
key: string
|
||||
icons: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单图标选择器分类(尽量多放业务可用的 Arco Icon;不存在的会自动过滤)。
|
||||
*/
|
||||
const RAW_CATEGORIES: MenuIconCategory[] = [
|
||||
{
|
||||
key: 'common',
|
||||
icons: [
|
||||
'IconHome',
|
||||
'IconDashboard',
|
||||
'IconApps',
|
||||
'IconMenu',
|
||||
'IconSettings',
|
||||
'IconList',
|
||||
'IconOrderedList',
|
||||
'IconUnorderedList',
|
||||
'IconFile',
|
||||
'IconFolder',
|
||||
'IconBook',
|
||||
'IconBookmark',
|
||||
'IconSearch',
|
||||
'IconFilter',
|
||||
'IconSort',
|
||||
'IconPlus',
|
||||
'IconPlusCircle',
|
||||
'IconEdit',
|
||||
'IconDelete',
|
||||
'IconSave',
|
||||
'IconCopy',
|
||||
'IconPaste',
|
||||
'IconRefresh',
|
||||
'IconSync',
|
||||
'IconLink',
|
||||
'IconLaunch',
|
||||
'IconShareInternal',
|
||||
'IconShareExternal',
|
||||
'IconEye',
|
||||
'IconEyeInvisible',
|
||||
'IconMore',
|
||||
'IconMoreVertical',
|
||||
'IconStar',
|
||||
'IconHeart',
|
||||
'IconFire',
|
||||
'IconBulb',
|
||||
'IconThunderbolt',
|
||||
'IconGift',
|
||||
'IconTrophy',
|
||||
'IconPushpin',
|
||||
'IconTag',
|
||||
'IconTags',
|
||||
'IconCalendar',
|
||||
'IconSchedule',
|
||||
'IconClockCircle',
|
||||
'IconHistory',
|
||||
'IconNotification',
|
||||
'IconMessage',
|
||||
'IconUser',
|
||||
'IconUserGroup',
|
||||
'IconSafe',
|
||||
'IconLock',
|
||||
'IconComputer',
|
||||
'IconStorage',
|
||||
'IconCloud',
|
||||
'IconSend',
|
||||
'IconDownload',
|
||||
'IconUpload',
|
||||
'IconPrinter',
|
||||
'IconScan',
|
||||
'IconQrcode',
|
||||
'IconLocation',
|
||||
'IconCompass',
|
||||
'IconPublic',
|
||||
'IconBarChart',
|
||||
'IconMindMapping',
|
||||
'IconRelation',
|
||||
'IconRobot',
|
||||
'IconTool',
|
||||
'IconCode',
|
||||
'IconBug',
|
||||
'IconExperiment',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'nav',
|
||||
icons: [
|
||||
'IconDashboard',
|
||||
'IconHome',
|
||||
'IconApps',
|
||||
'IconMenu',
|
||||
'IconMenuFold',
|
||||
'IconMenuUnfold',
|
||||
'IconNav',
|
||||
'IconLayout',
|
||||
'IconLayers',
|
||||
'IconCommon',
|
||||
'IconMindMapping',
|
||||
'IconRelation',
|
||||
'IconBranch',
|
||||
'IconSubscribe',
|
||||
'IconSubscribeAdd',
|
||||
'IconSubscribed',
|
||||
'IconPushpin',
|
||||
'IconBookmark',
|
||||
'IconCompass',
|
||||
'IconLocation',
|
||||
'IconPublic',
|
||||
'IconTranslate',
|
||||
'IconLanguage',
|
||||
'IconMoon',
|
||||
'IconSun',
|
||||
'IconSkin',
|
||||
'IconExpand',
|
||||
'IconShrink',
|
||||
'IconFullscreen',
|
||||
'IconFullscreenExit',
|
||||
'IconToTop',
|
||||
'IconToBottom',
|
||||
'IconToLeft',
|
||||
'IconToRight',
|
||||
'IconUp',
|
||||
'IconDown',
|
||||
'IconLeft',
|
||||
'IconRight',
|
||||
'IconArrowUp',
|
||||
'IconArrowDown',
|
||||
'IconArrowLeft',
|
||||
'IconArrowRight',
|
||||
'IconArrowRise',
|
||||
'IconArrowFall',
|
||||
'IconDoubleUp',
|
||||
'IconDoubleDown',
|
||||
'IconDoubleLeft',
|
||||
'IconDoubleRight',
|
||||
'IconCaretUp',
|
||||
'IconCaretDown',
|
||||
'IconCaretLeft',
|
||||
'IconCaretRight',
|
||||
'IconSwap',
|
||||
'IconLoop',
|
||||
'IconDragArrow',
|
||||
'IconDragDot',
|
||||
'IconDragDotVertical',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'user',
|
||||
icons: [
|
||||
'IconUser',
|
||||
'IconUserGroup',
|
||||
'IconUserAdd',
|
||||
'IconIdcard',
|
||||
'IconMan',
|
||||
'IconWoman',
|
||||
'IconSafe',
|
||||
'IconLock',
|
||||
'IconUnlock',
|
||||
'IconEmail',
|
||||
'IconAt',
|
||||
'IconPhone',
|
||||
'IconMobile',
|
||||
'IconMessage',
|
||||
'IconMessageBanned',
|
||||
'IconNotification',
|
||||
'IconNotificationClose',
|
||||
'IconCustomerService',
|
||||
'IconVoice',
|
||||
'IconRobot',
|
||||
'IconRobotAdd',
|
||||
'IconTrophy',
|
||||
'IconGift',
|
||||
'IconStamp',
|
||||
'IconHeart',
|
||||
'IconStar',
|
||||
'IconThumbUp',
|
||||
'IconThumbDown',
|
||||
'IconReply',
|
||||
'IconShareInternal',
|
||||
'IconShareExternal',
|
||||
'IconShareAlt',
|
||||
'IconShake',
|
||||
'IconEar',
|
||||
'IconDice',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'order',
|
||||
icons: [
|
||||
'IconFile',
|
||||
'IconDriveFile',
|
||||
'IconFileImage',
|
||||
'IconFilePdf',
|
||||
'IconFileAudio',
|
||||
'IconFileVideo',
|
||||
'IconArchive',
|
||||
'IconFolder',
|
||||
'IconFolderAdd',
|
||||
'IconFolderDelete',
|
||||
'IconStorage',
|
||||
'IconTags',
|
||||
'IconTag',
|
||||
'IconQrcode',
|
||||
'IconScan',
|
||||
'IconPrinter',
|
||||
'IconImport',
|
||||
'IconExport',
|
||||
'IconSend',
|
||||
'IconInteraction',
|
||||
'IconSwap',
|
||||
'IconSelectAll',
|
||||
'IconCheckSquare',
|
||||
'IconCheck',
|
||||
'IconCalendar',
|
||||
'IconCalendarClock',
|
||||
'IconSchedule',
|
||||
'IconClockCircle',
|
||||
'IconHistory',
|
||||
'IconAttachment',
|
||||
'IconCopy',
|
||||
'IconPaste',
|
||||
'IconSave',
|
||||
'IconScissor',
|
||||
'IconFilter',
|
||||
'IconFindReplace',
|
||||
'IconSort',
|
||||
'IconSortAscending',
|
||||
'IconSortDescending',
|
||||
'IconList',
|
||||
'IconOrderedList',
|
||||
'IconUnorderedList',
|
||||
'IconEmpty',
|
||||
'IconOriginalSize',
|
||||
'IconZoomIn',
|
||||
'IconZoomOut',
|
||||
'IconRotateLeft',
|
||||
'IconRotateRight',
|
||||
'IconImage',
|
||||
'IconImageClose',
|
||||
'IconCamera',
|
||||
'IconVideoCamera',
|
||||
'IconMusic',
|
||||
'IconRecord',
|
||||
'IconRecordStop',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'finance',
|
||||
icons: [
|
||||
'IconStorage',
|
||||
'IconGift',
|
||||
'IconTrophy',
|
||||
'IconStamp',
|
||||
'IconStar',
|
||||
'IconHeart',
|
||||
'IconFire',
|
||||
'IconBulb',
|
||||
'IconThunderbolt',
|
||||
'IconBarChart',
|
||||
'IconDashboard',
|
||||
'IconWechatpay',
|
||||
'IconAlipayCircle',
|
||||
'IconSwap',
|
||||
'IconList',
|
||||
'IconFile',
|
||||
'IconSafe',
|
||||
'IconLock',
|
||||
'IconUnlock',
|
||||
'IconIdcard',
|
||||
'IconCalendar',
|
||||
'IconSchedule',
|
||||
'IconHistory',
|
||||
'IconTag',
|
||||
'IconTags',
|
||||
'IconBookmark',
|
||||
'IconPushpin',
|
||||
'IconSubscribe',
|
||||
'IconSubscribed',
|
||||
'IconThumbUp',
|
||||
'IconThumbDown',
|
||||
'IconCopyright',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'track',
|
||||
icons: [
|
||||
'IconComputer',
|
||||
'IconLiveBroadcast',
|
||||
'IconSend',
|
||||
'IconCompass',
|
||||
'IconLocation',
|
||||
'IconPublic',
|
||||
'IconBookmark',
|
||||
'IconPushpin',
|
||||
'IconCheckCircle',
|
||||
'IconExclamationCircle',
|
||||
'IconCloseCircle',
|
||||
'IconInfoCircle',
|
||||
'IconQuestionCircle',
|
||||
'IconMessage',
|
||||
'IconNotification',
|
||||
'IconDownload',
|
||||
'IconUpload',
|
||||
'IconCloud',
|
||||
'IconCloudDownload',
|
||||
'IconSync',
|
||||
'IconRefresh',
|
||||
'IconLoop',
|
||||
'IconHistory',
|
||||
'IconSchedule',
|
||||
'IconCalendarClock',
|
||||
'IconClockCircle',
|
||||
'IconThunderbolt',
|
||||
'IconMindMapping',
|
||||
'IconRelation',
|
||||
'IconBranch',
|
||||
'IconInteraction',
|
||||
'IconLaunch',
|
||||
'IconShareExternal',
|
||||
'IconWifi',
|
||||
'IconMobile',
|
||||
'IconPhone',
|
||||
'IconScan',
|
||||
'IconQrcode',
|
||||
'IconPrinter',
|
||||
'IconImport',
|
||||
'IconExport',
|
||||
'IconLoading',
|
||||
'IconEmpty',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'system',
|
||||
icons: [
|
||||
'IconSettings',
|
||||
'IconTool',
|
||||
'IconCode',
|
||||
'IconCodeBlock',
|
||||
'IconCodeSquare',
|
||||
'IconCodeSandbox',
|
||||
'IconCodepen',
|
||||
'IconBug',
|
||||
'IconExperiment',
|
||||
'IconCommand',
|
||||
'IconDesktop',
|
||||
'IconComputer',
|
||||
'IconCloud',
|
||||
'IconCloudDownload',
|
||||
'IconApps',
|
||||
'IconCommon',
|
||||
'IconStorage',
|
||||
'IconDriveFile',
|
||||
'IconWifi',
|
||||
'IconPoweroff',
|
||||
'IconPen',
|
||||
'IconBrush',
|
||||
'IconEraser',
|
||||
'IconPalette',
|
||||
'IconBgColors',
|
||||
'IconFontColors',
|
||||
'IconHighlight',
|
||||
'IconCamera',
|
||||
'IconVideoCamera',
|
||||
'IconImage',
|
||||
'IconMusic',
|
||||
'IconPlayArrow',
|
||||
'IconPlayCircle',
|
||||
'IconPause',
|
||||
'IconPauseCircle',
|
||||
'IconStop',
|
||||
'IconSkipNext',
|
||||
'IconSkipPrevious',
|
||||
'IconForward',
|
||||
'IconBackward',
|
||||
'IconMute',
|
||||
'IconSound',
|
||||
'IconRobot',
|
||||
'IconRobotAdd',
|
||||
'IconFormula',
|
||||
'IconQuote',
|
||||
'IconUndo',
|
||||
'IconRedo',
|
||||
'IconMosaic',
|
||||
'IconScissor',
|
||||
'IconBold',
|
||||
'IconItalic',
|
||||
'IconUnderline',
|
||||
'IconStrikethrough',
|
||||
'IconAlignLeft',
|
||||
'IconAlignCenter',
|
||||
'IconAlignRight',
|
||||
'IconLineHeight',
|
||||
'IconH1',
|
||||
'IconH2',
|
||||
'IconH3',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
icons: [
|
||||
'IconCheck',
|
||||
'IconCheckCircle',
|
||||
'IconCheckSquare',
|
||||
'IconClose',
|
||||
'IconCloseCircle',
|
||||
'IconExclamation',
|
||||
'IconExclamationCircle',
|
||||
'IconInfo',
|
||||
'IconInfoCircle',
|
||||
'IconQuestion',
|
||||
'IconQuestionCircle',
|
||||
'IconPlus',
|
||||
'IconPlusCircle',
|
||||
'IconMinus',
|
||||
'IconMinusCircle',
|
||||
'IconClockCircle',
|
||||
'IconStop',
|
||||
'IconPause',
|
||||
'IconPauseCircle',
|
||||
'IconPlayArrow',
|
||||
'IconPlayCircle',
|
||||
'IconLoading',
|
||||
'IconSync',
|
||||
'IconRefresh',
|
||||
'IconLoop',
|
||||
'IconObliqueLine',
|
||||
'IconEmpty',
|
||||
'IconEye',
|
||||
'IconEyeInvisible',
|
||||
'IconLock',
|
||||
'IconUnlock',
|
||||
'IconSafe',
|
||||
'IconFire',
|
||||
'IconThunderbolt',
|
||||
'IconBulb',
|
||||
'IconStar',
|
||||
'IconHeart',
|
||||
'IconThumbUp',
|
||||
'IconThumbDown',
|
||||
'IconUpCircle',
|
||||
'IconDownCircle',
|
||||
'IconLeftCircle',
|
||||
'IconRightCircle',
|
||||
'IconArrowRise',
|
||||
'IconArrowFall',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function keepExisting(icons: string[]): string[] {
|
||||
const available = new Set(ARCO_ICON_NAMES)
|
||||
const seen = new Set<string>()
|
||||
const result: string[] = []
|
||||
for (const name of icons) {
|
||||
if (!available.has(name) || seen.has(name)) continue
|
||||
seen.add(name)
|
||||
result.push(name)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const MENU_ICON_CATEGORIES: MenuIconCategory[] = RAW_CATEGORIES.map((cat) => ({
|
||||
key: cat.key,
|
||||
icons: keepExisting(cat.icons),
|
||||
})).filter((cat) => cat.icons.length > 0)
|
||||
|
||||
/** 扁平常用图标(用于兼容旧逻辑) */
|
||||
export const MENU_COMMON_ICONS: string[] = keepExisting(
|
||||
MENU_ICON_CATEGORIES.flatMap((cat) => cat.icons),
|
||||
)
|
||||
@ -18,6 +18,10 @@ export const TOP_MENU_DEFAULT_ICONS: Record<string, string> = {
|
||||
/** 子菜单默认图标(按 locale key) */
|
||||
export const SUB_MENU_DEFAULT_ICONS: Record<string, string> = {
|
||||
'menu.home': 'IconHome',
|
||||
'menu.home.workplace': 'IconDashboard',
|
||||
'menu.home.monitor': 'IconLiveBroadcast',
|
||||
'menu.home.adminEventBus': 'IconMindMapping',
|
||||
'menu.home.trackEventBus': 'IconRelation',
|
||||
'menu.user.customer': 'IconUserGroup',
|
||||
'menu.user.profile': 'IconUser',
|
||||
'menu.user.recharge': 'IconGift',
|
||||
@ -45,7 +49,7 @@ export const SUB_MENU_DEFAULT_ICONS: Record<string, string> = {
|
||||
'menu.track.trackOrder': 'IconList',
|
||||
'menu.track.warningOrder': 'IconExclamationCircle',
|
||||
'menu.track.trackState': 'IconCheckCircle',
|
||||
'menu.track.trackFlag': 'IconFlag',
|
||||
'menu.track.trackFlag': 'IconBookmark',
|
||||
'menu.track.trackContent': 'IconMessage',
|
||||
'menu.track.trackPull': 'IconDownload',
|
||||
'menu.track.trackPush': 'IconUpload',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 列表状态标签:按信息强弱分档(低饱和墨色),对齐 OMS 主色 #007979。
|
||||
* 列表状态标签:软底圆角(无硬描边),对齐 OMS 主色。
|
||||
*
|
||||
* 强弱(弱 → 强):
|
||||
* - mute 弱信息:否 / 关闭 / 删除 / 无效
|
||||
@ -61,6 +61,13 @@ export function getStatusTagTone(tone: StatusTagTone): StatusTagColors {
|
||||
|
||||
export type StatusTagVariant = 'solid' | 'ghost'
|
||||
|
||||
function resolveTagColors(
|
||||
toneOrColors: StatusTagTone | StatusTagColors | null | undefined,
|
||||
): StatusTagColors {
|
||||
if (!toneOrColors) return STATUS_TAG_TONES.neutral
|
||||
return typeof toneOrColors === 'string' ? STATUS_TAG_TONES[toneOrColors] : toneOrColors
|
||||
}
|
||||
|
||||
function escapeAttr(text: string): string {
|
||||
return String(text)
|
||||
.replace(/&/g, '&')
|
||||
@ -69,52 +76,55 @@ function escapeAttr(text: string): string {
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
/** Vue / a-tag 内联样式 */
|
||||
/** Vue / a-tag 内联样式(软底 + 左侧色条,无硬框) */
|
||||
export function getStatusTagStyle(
|
||||
toneOrColors: StatusTagTone | StatusTagColors | null | undefined,
|
||||
variant: StatusTagVariant = 'solid',
|
||||
): Record<string, string> {
|
||||
const colors =
|
||||
!toneOrColors
|
||||
? STATUS_TAG_TONES.neutral
|
||||
: typeof toneOrColors === 'string'
|
||||
? STATUS_TAG_TONES[toneOrColors]
|
||||
: toneOrColors
|
||||
const colors = resolveTagColors(toneOrColors)
|
||||
if (variant === 'ghost') {
|
||||
return {
|
||||
color: colors.tagColor,
|
||||
background: 'transparent',
|
||||
border: `1px solid ${colors.tagBorder}`,
|
||||
borderRadius: '3px',
|
||||
borderRadius: '6px',
|
||||
fontWeight: '500',
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px',
|
||||
padding: '0 8px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: '6px',
|
||||
}
|
||||
}
|
||||
return {
|
||||
color: colors.tagColor,
|
||||
background: colors.tagBgColor,
|
||||
border: `1px solid ${colors.tagBorder}`,
|
||||
borderRadius: '3px',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
boxShadow: `inset 3px 0 0 ${colors.tagColor}`,
|
||||
fontWeight: '500',
|
||||
fontSize: '12px',
|
||||
lineHeight: '22px',
|
||||
padding: '0 10px 0 12px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
letterSpacing: '0.02em',
|
||||
}
|
||||
}
|
||||
|
||||
/** Handsontable 单元格 HTML */
|
||||
/** Handsontable 单元格 HTML:软底圆角标签(无色点) */
|
||||
export function buildStatusTagHtml(
|
||||
text: string,
|
||||
toneOrColors: StatusTagTone | StatusTagColors | null | undefined = 'neutral',
|
||||
variant: StatusTagVariant = 'solid',
|
||||
): string {
|
||||
const colors =
|
||||
!toneOrColors
|
||||
? STATUS_TAG_TONES.neutral
|
||||
: typeof toneOrColors === 'string'
|
||||
? STATUS_TAG_TONES[toneOrColors]
|
||||
: toneOrColors
|
||||
const colors = resolveTagColors(toneOrColors)
|
||||
const safe = escapeAttr(text)
|
||||
if (variant === 'ghost') {
|
||||
return `<span style="display:inline-flex;align-items:center;height:20px;padding:0 6px;border-radius:3px;font-size:11px;font-weight:500;line-height:18px;color:${colors.tagColor};background:transparent;border:1px solid ${colors.tagBorder};white-space:nowrap;">${safe}</span>`
|
||||
return `<span style="display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:6px;font-size:11px;font-weight:500;line-height:18px;color:${colors.tagColor};background:transparent;border:1px solid ${colors.tagBorder};white-space:nowrap;">${safe}</span>`
|
||||
}
|
||||
return `<span style="display:inline-flex;align-items:center;height:22px;padding:0 8px;border-radius:3px;font-size:12px;font-weight:500;line-height:20px;letter-spacing:0.02em;color:${colors.tagColor};background:${colors.tagBgColor};border:1px solid ${colors.tagBorder};white-space:nowrap;">${safe}</span>`
|
||||
return `<span style="display:inline-flex;align-items:center;height:22px;padding:0 10px;border-radius:6px;font-size:12px;font-weight:500;line-height:20px;letter-spacing:0.02em;color:${colors.tagColor};background:${colors.tagBgColor};border:none;white-space:nowrap;">${safe}</span>`
|
||||
}
|
||||
|
||||
/** 布尔「是」专用高亮绿(比状态档 ok 更醒目) */
|
||||
|
||||
@ -41,6 +41,8 @@ export interface SyncLevel3Options {
|
||||
export interface SyncMenuTreeResult {
|
||||
mode: 'module' | 'page';
|
||||
level2Created: number;
|
||||
/** 已有二级菜单补齐图标/权限码等字段 */
|
||||
level2Updated: number;
|
||||
level2Skipped: number;
|
||||
level3Created: number;
|
||||
level3Updated: number;
|
||||
@ -187,39 +189,53 @@ async function reloadMenuNode(menuId: string): Promise<MenuTreeDto | null> {
|
||||
async function ensurePageMenu(
|
||||
parentMenu: MenuTreeDto,
|
||||
pageDef: RoutePageMenuDef,
|
||||
): Promise<{ menu: MenuTreeDto; created: boolean }> {
|
||||
): Promise<{ menu: MenuTreeDto; created: boolean; updated: boolean }> {
|
||||
const nextIcon = pageDef.icon?.trim() || undefined;
|
||||
const existing = findPageChildMenu(parentMenu, pageDef.route, pageDef.permissionCode);
|
||||
if (existing?.id) {
|
||||
// 已有二级菜单:补齐或纠正 permissionCode(Xxx.Query → Xxx),便于同步「查询」等三级按钮
|
||||
// 已有二级菜单:补齐或纠正 permissionCode(Xxx.Query → Xxx);图标仅补空,不覆盖人工更换
|
||||
const existingCode = existing.permissionCode?.trim() || '';
|
||||
const nextCode = pageDef.permissionCode?.trim() || '';
|
||||
const shouldPatchCode =
|
||||
!!nextCode &&
|
||||
(!existingCode ||
|
||||
existingCode === nextCode ||
|
||||
normalizeResourcePermissionCode(existingCode) === nextCode);
|
||||
if (shouldPatchCode && existingCode !== nextCode) {
|
||||
existingCode !== nextCode &&
|
||||
(!existingCode || normalizeResourcePermissionCode(existingCode) === nextCode);
|
||||
const existingIcon = existing.icon?.trim() || '';
|
||||
const shouldPatchIcon = !existingIcon && !!nextIcon;
|
||||
|
||||
if (shouldPatchCode || shouldPatchIcon) {
|
||||
const permissionCode = shouldPatchCode ? nextCode : existing.permissionCode;
|
||||
const icon = existingIcon || nextIcon;
|
||||
try {
|
||||
await MenuService.putRbacMenuUpdate(existing.id, {
|
||||
name: existing.name || pageDef.name,
|
||||
permissionCode: nextCode,
|
||||
icon: existing.icon || pageDef.icon,
|
||||
permissionCode,
|
||||
icon,
|
||||
route: existing.route || pageDef.route,
|
||||
component: existing.component || pageDef.component,
|
||||
order: existing.order ?? pageDef.order,
|
||||
isHidden: existing.isHidden ?? pageDef.isHidden,
|
||||
isCached: existing.isCached ?? false,
|
||||
description: existing.description || `同步二级菜单权限码: ${nextCode}`,
|
||||
description:
|
||||
existing.description ||
|
||||
(shouldPatchIcon
|
||||
? `同步二级菜单图标: ${icon}`
|
||||
: `同步二级菜单权限码: ${permissionCode}`),
|
||||
});
|
||||
return {
|
||||
menu: { ...existing, permissionCode: nextCode },
|
||||
menu: {
|
||||
...existing,
|
||||
permissionCode: permissionCode || existing.permissionCode,
|
||||
icon: icon || existing.icon,
|
||||
},
|
||||
created: false,
|
||||
updated: true,
|
||||
};
|
||||
} catch {
|
||||
// 更新失败时仍返回原节点,后续三级同步可能因无权限码跳过
|
||||
}
|
||||
}
|
||||
return { menu: existing, created: false };
|
||||
return { menu: existing, created: false, updated: false };
|
||||
}
|
||||
|
||||
const createdMenu = await MenuService.postRbacMenuCreate({
|
||||
@ -227,7 +243,7 @@ async function ensurePageMenu(
|
||||
name: pageDef.name,
|
||||
type: 1,
|
||||
permissionCode: pageDef.permissionCode,
|
||||
icon: pageDef.icon,
|
||||
icon: nextIcon,
|
||||
route: pageDef.route,
|
||||
component: pageDef.component,
|
||||
order: pageDef.order,
|
||||
@ -243,7 +259,7 @@ async function ensurePageMenu(
|
||||
name: pageDef.name,
|
||||
type: 1,
|
||||
permissionCode: pageDef.permissionCode,
|
||||
icon: pageDef.icon,
|
||||
icon: nextIcon,
|
||||
route: pageDef.route,
|
||||
component: pageDef.component,
|
||||
order: pageDef.order,
|
||||
@ -251,6 +267,7 @@ async function ensurePageMenu(
|
||||
children: [],
|
||||
},
|
||||
created: true,
|
||||
updated: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -428,6 +445,7 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
||||
return {
|
||||
mode: 'page',
|
||||
level2Created: 0,
|
||||
level2Updated: 0,
|
||||
level2Skipped: 0,
|
||||
level3Created: result.created,
|
||||
level3Updated: result.updated,
|
||||
@ -448,6 +466,7 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
||||
}
|
||||
|
||||
let level2Created = 0;
|
||||
let level2Updated = 0;
|
||||
let level2Skipped = 0;
|
||||
let level3Created = 0;
|
||||
let level3Updated = 0;
|
||||
@ -460,10 +479,12 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
||||
for (const pageDef of pageDefs) {
|
||||
let pageMenu: MenuTreeDto;
|
||||
let created = false;
|
||||
let updated = false;
|
||||
try {
|
||||
const ensured = await ensurePageMenu(currentParent, pageDef);
|
||||
pageMenu = ensured.menu;
|
||||
created = ensured.created;
|
||||
updated = ensured.updated;
|
||||
} catch (error: unknown) {
|
||||
level3FailedPages.push(`${pageDef.name}: ${readSyncError(error)}`);
|
||||
continue;
|
||||
@ -474,6 +495,12 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
||||
if (refreshedParent) {
|
||||
currentParent = refreshedParent;
|
||||
}
|
||||
} else if (updated) {
|
||||
level2Updated += 1;
|
||||
const refreshedParent = await reloadMenuNode(menu.id);
|
||||
if (refreshedParent) {
|
||||
currentParent = refreshedParent;
|
||||
}
|
||||
} else {
|
||||
level2Skipped += 1;
|
||||
}
|
||||
@ -502,6 +529,7 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
||||
return {
|
||||
mode: 'module',
|
||||
level2Created,
|
||||
level2Updated,
|
||||
level2Skipped,
|
||||
level3Created,
|
||||
level3Updated,
|
||||
|
||||
@ -3,6 +3,11 @@ import { appRoutes } from '@/router/routes';
|
||||
import type { AppRouteRecordRaw } from '@/router/routes/types';
|
||||
import type { MenuTreeDto } from '@/api/rbac';
|
||||
import { normalizeResourcePermissionCode } from '@/constants/pageButtonCatalog';
|
||||
import {
|
||||
PATH_TO_LOCALE_KEY,
|
||||
SUB_MENU_DEFAULT_ICONS,
|
||||
toPascalIconName,
|
||||
} from '@/utils/menu-icon';
|
||||
|
||||
export interface RoutePageMenuDef {
|
||||
name: string;
|
||||
@ -52,6 +57,30 @@ export function resolveRouteMenuTitle(route: AppRouteRecordRaw): string {
|
||||
return String(route.name || route.path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析二级菜单同步图标:路由 meta.icon → locale 默认表;统一存 PascalCase(如 IconBook)。
|
||||
*/
|
||||
export function resolveRouteMenuIcon(
|
||||
route: AppRouteRecordRaw,
|
||||
routePath: string,
|
||||
): string | undefined {
|
||||
const fromMeta = (route.meta?.icon as string | undefined)?.trim();
|
||||
if (fromMeta) {
|
||||
const normalized = toPascalIconName(fromMeta);
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
const localeKey =
|
||||
PATH_TO_LOCALE_KEY[normalizeRoutePath(routePath)] ||
|
||||
(route.meta?.locale as string | undefined)?.trim();
|
||||
if (!localeKey) return undefined;
|
||||
|
||||
const fromDefault = SUB_MENU_DEFAULT_ICONS[localeKey];
|
||||
if (!fromDefault) return undefined;
|
||||
const normalized = toPascalIconName(fromDefault);
|
||||
return normalized || undefined;
|
||||
}
|
||||
|
||||
function toRouteRecord(route: AppRouteRecordRaw): AppRouteRecordRaw {
|
||||
return route;
|
||||
}
|
||||
@ -105,7 +134,7 @@ export function getPageRoutesFromModule(moduleRoute: AppRouteRecordRaw): RoutePa
|
||||
permissionCode: rawPermission
|
||||
? normalizeResourcePermissionCode(rawPermission) || rawPermission
|
||||
: undefined,
|
||||
icon: (child.meta.icon as string | undefined)?.trim() || undefined,
|
||||
icon: resolveRouteMenuIcon(child, routePath),
|
||||
order: (child.meta.order as number | undefined) ?? 0,
|
||||
isHidden: Boolean(child.meta.hideInMenu),
|
||||
});
|
||||
|
||||
@ -8,6 +8,7 @@ export default {
|
||||
'h5Admin.login.title': 'Staff login',
|
||||
'h5Admin.login.brandName': 'Ops Admin',
|
||||
'h5Admin.login.brandHint': 'Sign in with your staff account',
|
||||
'h5Admin.login.backToSite': 'Back to website',
|
||||
'h5Admin.login.accountPlaceholder': 'Account',
|
||||
'h5Admin.login.passwordPlaceholder': 'Password',
|
||||
'h5Admin.login.submit': 'Sign in',
|
||||
|
||||
@ -8,6 +8,7 @@ export default {
|
||||
'h5Admin.login.title': '员工登录',
|
||||
'h5Admin.login.brandName': '运营管理',
|
||||
'h5Admin.login.brandHint': '使用员工账号登录,处理待办与运单',
|
||||
'h5Admin.login.backToSite': '返回官网',
|
||||
'h5Admin.login.accountPlaceholder': '账号',
|
||||
'h5Admin.login.passwordPlaceholder': '密码',
|
||||
'h5Admin.login.submit': '登录',
|
||||
|
||||
@ -9,10 +9,15 @@
|
||||
<main class="h5-login-main">
|
||||
<section class="h5-login-card">
|
||||
<header class="h5-login-card__brand">
|
||||
<div class="h5-login-card__brand-row">
|
||||
<button
|
||||
type="button"
|
||||
class="h5-login-card__brand-row"
|
||||
:aria-label="t('h5Admin.login.backToSite')"
|
||||
@click="goOfficialSite"
|
||||
>
|
||||
<img :src="logoImg" alt="" class="h5-login-card__logo" />
|
||||
<h2 class="h5-login-card__title">{{ t('h5Admin.login.brandName') }}</h2>
|
||||
</div>
|
||||
</button>
|
||||
<p class="h5-login-card__hint">{{ t('h5Admin.login.brandHint') }}</p>
|
||||
</header>
|
||||
|
||||
@ -78,6 +83,10 @@ const rbacUserStore = useRbacUserStore();
|
||||
|
||||
const form = reactive({ account: '', password: '' });
|
||||
const loading = ref(false);
|
||||
|
||||
function goOfficialSite() {
|
||||
router.push('/');
|
||||
}
|
||||
const errorBox = ref('');
|
||||
|
||||
function resolveRedirectPath() {
|
||||
@ -226,6 +235,12 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.h5-login-card__logo {
|
||||
|
||||
@ -315,6 +315,7 @@ export default {
|
||||
'h5.login.brand': 'IDE Customer Center',
|
||||
'h5.login.brandName': 'IDE',
|
||||
'h5.login.brandHint': 'Welcome to your IDE portal',
|
||||
'h5.login.backToSite': 'Back to website',
|
||||
'h5.login.tab.wechat': 'WeChat',
|
||||
'h5.login.tab.password': 'Account',
|
||||
'h5.login.switchPassword': 'Sign in with password',
|
||||
|
||||
@ -314,6 +314,7 @@ export default {
|
||||
'h5.login.brand': 'IDE客户中心',
|
||||
'h5.login.brandName': 'IDE',
|
||||
'h5.login.brandHint': '欢迎登录 IDE 客户门户',
|
||||
'h5.login.backToSite': '返回官网',
|
||||
'h5.login.tab.wechat': '微信登录',
|
||||
'h5.login.tab.password': '账号登录',
|
||||
'h5.login.switchPassword': '账号密码登录',
|
||||
|
||||
@ -24,10 +24,15 @@
|
||||
<main class="h5-login-main">
|
||||
<section class="h5-login-card">
|
||||
<header class="h5-login-card__brand">
|
||||
<div class="h5-login-card__brand-row">
|
||||
<button
|
||||
type="button"
|
||||
class="h5-login-card__brand-row"
|
||||
:aria-label="t('h5.login.backToSite')"
|
||||
@click="goOfficialSite"
|
||||
>
|
||||
<img :src="logoImg" alt="" class="h5-login-card__logo" />
|
||||
<h2 class="h5-login-card__title">{{ t('h5.login.brandName') }}</h2>
|
||||
</div>
|
||||
</button>
|
||||
<p class="h5-login-card__hint">{{ t('h5.login.brandHint') }}</p>
|
||||
</header>
|
||||
|
||||
@ -175,6 +180,10 @@ function goBack() {
|
||||
router.replace('/h5/orders');
|
||||
}
|
||||
|
||||
function goOfficialSite() {
|
||||
router.push('/');
|
||||
}
|
||||
|
||||
function goRegister() {
|
||||
if (!isWeChatBrowser()) {
|
||||
Message.warning(t('h5.login.openInWechat'));
|
||||
@ -463,6 +472,12 @@ onMounted(async () => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.h5-login-card__logo {
|
||||
|
||||
130
src/views/home/eventbus/index.vue
Normal file
130
src/views/home/eventbus/index.vue
Normal file
@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<div class="eventbus-embed-container">
|
||||
<div class="table-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<span class="page-title">{{ pageTitle }}</span>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<a-button class="app-btn-search" @click="reloadFrame">
|
||||
{{ t('home.eventbus.reload') }}
|
||||
</a-button>
|
||||
<a-button type="primary" class="app-btn-primary-soft" @click="openExternal">
|
||||
{{ t('home.eventbus.openNew') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="frame-wrap">
|
||||
<iframe
|
||||
v-if="frameSrc"
|
||||
:key="frameKey"
|
||||
class="eventbus-frame"
|
||||
:src="frameSrc"
|
||||
:title="pageTitle"
|
||||
referrerpolicy="no-referrer-when-downgrade"
|
||||
/>
|
||||
<div v-else class="frame-empty">{{ t('home.eventbus.missingUrl') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useMenuDisplay } from '@/composables/useMenuDisplay';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { currentTitle } = useMenuDisplay();
|
||||
const frameKey = ref(0);
|
||||
|
||||
const frameSrc = computed(() => {
|
||||
const fromMeta = String(route.meta.frameSrc || '').trim();
|
||||
if (fromMeta) return fromMeta;
|
||||
return String(route.query.url || '').trim();
|
||||
});
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
const fromMenu = currentTitle.value?.trim();
|
||||
if (fromMenu) return fromMenu;
|
||||
const localeKey = String(route.meta.locale || '').trim();
|
||||
return localeKey ? t(localeKey) : t('home.eventbus.title');
|
||||
});
|
||||
|
||||
function reloadFrame() {
|
||||
frameKey.value += 1;
|
||||
}
|
||||
|
||||
function openExternal() {
|
||||
if (!frameSrc.value) return;
|
||||
window.open(frameSrc.value, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
/* 与 appList 等全高页一致:用视口高度,避免父级高度链未就绪时 iframe 塌成 0(开 F12 重排才显示) */
|
||||
.eventbus-embed-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 86px);
|
||||
max-height: calc(100vh - 86px);
|
||||
min-height: 0;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
background: #f0f3f4;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table-toolbar {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 16px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.toolbar-left,
|
||||
.toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
|
||||
.frame-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.eventbus-frame {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
display: block;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.frame-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
</style>
|
||||
8
src/views/home/eventbus/locale/en-US.ts
Normal file
8
src/views/home/eventbus/locale/en-US.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
'menu.home.adminEventBus': 'OMS EventBus',
|
||||
'menu.home.trackEventBus': 'Track EventBus',
|
||||
'home.eventbus.title': 'EventBus Dashboard',
|
||||
'home.eventbus.reload': 'Reload',
|
||||
'home.eventbus.openNew': 'Open in new tab',
|
||||
'home.eventbus.missingUrl': 'Embed URL is not configured',
|
||||
};
|
||||
8
src/views/home/eventbus/locale/zh-CN.ts
Normal file
8
src/views/home/eventbus/locale/zh-CN.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export default {
|
||||
'menu.home.adminEventBus': 'OMS EventBus',
|
||||
'menu.home.trackEventBus': '轨迹 EventBus',
|
||||
'home.eventbus.title': 'EventBus Dashboard',
|
||||
'home.eventbus.reload': '刷新',
|
||||
'home.eventbus.openNew': '新窗口打开',
|
||||
'home.eventbus.missingUrl': '未配置嵌入地址',
|
||||
};
|
||||
@ -587,7 +587,7 @@ onBeforeUnmount(() => {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
transform-origin: 50% 50%;
|
||||
transform: translate3d(100px, 96px, 0) scale(4.1);
|
||||
transform: translate3d(100px, 96px, 0) scale(2.73);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@ -761,8 +761,16 @@
|
||||
'clientNo',
|
||||
'trackNo',
|
||||
'orderStatus',
|
||||
'previousOrderStatus',
|
||||
'handleStatus',
|
||||
'fulfillmentType',
|
||||
'stockBindStatus',
|
||||
'fulfillProgress',
|
||||
'shippingMethod',
|
||||
'isPaid',
|
||||
'isIntercept',
|
||||
'isRemote',
|
||||
'isCod',
|
||||
'receivableAmount',
|
||||
'predeclareFailReason',
|
||||
'payTime',
|
||||
@ -1621,10 +1629,30 @@
|
||||
const listColumnCatalog = computed(() => filterMyOrdersColumns(MY_ORDERS_COLUMN_CATALOG));
|
||||
|
||||
const hotColumns = computed(() => {
|
||||
const boolOptions = [
|
||||
{ label: t('order.myOrders.drawer.yes'), value: '1' },
|
||||
{ label: t('order.myOrders.drawer.no'), value: '0' },
|
||||
];
|
||||
const statusOptions = ORDER_STATUS_LIST.map((meta) => ({
|
||||
label: t(meta.labelKey),
|
||||
value: meta.value,
|
||||
}));
|
||||
const handleStatusOptions = ORDER_HANDLE_STATUS_LIST.filter((m) => m.value !== 'all').map((meta) => ({
|
||||
label: t(meta.labelKey),
|
||||
value: meta.value,
|
||||
}));
|
||||
const fulfillmentTypeOptions = [
|
||||
{ label: t('order.waybillManage.filter.fulfillmentDirect'), value: 'direct' },
|
||||
{ label: t('order.waybillManage.filter.fulfillmentWarehouse'), value: 'warehouse' },
|
||||
];
|
||||
const stockBindStatusOptions = ['none', 'bound', 'deducted', 'released'].map((code) => ({
|
||||
label: t(`order.myOrders.stockBind.status.${code}`),
|
||||
value: code,
|
||||
}));
|
||||
const fulfillProgressOptions = FULFILL_PROGRESS_TABS.filter((m) => m.value !== 'all').map((meta) => ({
|
||||
label: t(meta.labelKey),
|
||||
value: meta.value,
|
||||
}));
|
||||
const numberKeys = new Set([
|
||||
'waybillNo',
|
||||
'customerOrderNo',
|
||||
@ -1633,6 +1661,11 @@
|
||||
'clientNo',
|
||||
'trackNo',
|
||||
]);
|
||||
const enumFilter = (options: Array<{ label: string; value: string }>) => ({
|
||||
filterType: 'enum' as const,
|
||||
headerMode: 'icon' as const,
|
||||
filterOptions: options,
|
||||
});
|
||||
const base = withServerFilterColumns(
|
||||
buildHotColumns(listColumnCatalog.value, activeColumnKeys.value, t, te),
|
||||
ORDER_SERVER_FILTER_KEYS,
|
||||
@ -1648,22 +1681,23 @@
|
||||
|| col.key === 'receiveTime' || col.key === 'signTime') {
|
||||
return { filterType: 'date' as const, headerMode: 'icon' as const };
|
||||
}
|
||||
if (col.key === 'orderStatus') {
|
||||
return {
|
||||
filterType: 'enum' as const,
|
||||
headerMode: 'icon' as const,
|
||||
filterOptions: statusOptions,
|
||||
};
|
||||
if (col.key === 'orderStatus' || col.key === 'previousOrderStatus') {
|
||||
return enumFilter(statusOptions);
|
||||
}
|
||||
if (col.key === 'isPaid') {
|
||||
return {
|
||||
filterType: 'enum' as const,
|
||||
headerMode: 'icon' as const,
|
||||
filterOptions: [
|
||||
{ label: '已支付', value: '1' },
|
||||
{ label: '已支付', value: '1' },
|
||||
],
|
||||
};
|
||||
if (col.key === 'handleStatus') {
|
||||
return enumFilter(handleStatusOptions);
|
||||
}
|
||||
if (col.key === 'fulfillmentType') {
|
||||
return enumFilter(fulfillmentTypeOptions);
|
||||
}
|
||||
if (col.key === 'stockBindStatus') {
|
||||
return enumFilter(stockBindStatusOptions);
|
||||
}
|
||||
if (col.key === 'fulfillProgress') {
|
||||
return enumFilter(fulfillProgressOptions);
|
||||
}
|
||||
if (['isPaid', 'isIntercept', 'isRemote', 'isCod'].includes(col.key)) {
|
||||
return enumFilter(boolOptions);
|
||||
}
|
||||
if (col.key === 'receivableAmount') {
|
||||
return { filterType: 'number' as const, headerMode: 'icon' as const };
|
||||
|
||||
@ -82,6 +82,7 @@
|
||||
import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState';
|
||||
import {
|
||||
ORDER_STATUS_TABS,
|
||||
ORDER_STATUS_LIST,
|
||||
ORDER_STATUS_ALL,
|
||||
resolveOrderStatusCode,
|
||||
isOrderDeletable,
|
||||
@ -101,6 +102,7 @@
|
||||
getOrderHandleStatusMeta,
|
||||
type OrderHandleStatusCode,
|
||||
} from '@/constants/orderHandleStatus';
|
||||
import { FULFILL_PROGRESS_TABS } from '@/constants/fulfillProgress';
|
||||
import { hasPermission } from '@/utils/rbac-permission';
|
||||
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer';
|
||||
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer';
|
||||
@ -709,10 +711,17 @@
|
||||
'clientNo',
|
||||
'trackNo',
|
||||
'orderStatus',
|
||||
'previousOrderStatus',
|
||||
'handleStatus',
|
||||
'fulfillmentType',
|
||||
'stockBindStatus',
|
||||
'fulfillProgress',
|
||||
'shippingMethod',
|
||||
'serviceChannel',
|
||||
'isPaid',
|
||||
'isIntercept',
|
||||
'isRemote',
|
||||
'isCod',
|
||||
'receivableAmount',
|
||||
'predeclareFailReason',
|
||||
'allocationPendingReason',
|
||||
@ -1864,10 +1873,30 @@
|
||||
const activeColumnKeys = computed(() => getActiveScheme(columnConfigState.value).keys);
|
||||
|
||||
const hotColumns = computed(() => {
|
||||
const boolOptions = [
|
||||
{ label: t('order.myOrders.drawer.yes'), value: '1' },
|
||||
{ label: t('order.myOrders.drawer.no'), value: '0' },
|
||||
];
|
||||
const statusOptions = ORDER_STATUS_LIST.map((meta) => ({
|
||||
label: t(meta.labelKey),
|
||||
value: meta.value,
|
||||
}));
|
||||
const handleStatusOptions = ORDER_HANDLE_STATUS_LIST.filter((m) => m.value !== 'all').map((meta) => ({
|
||||
label: t(meta.labelKey),
|
||||
value: meta.value,
|
||||
}));
|
||||
const fulfillmentTypeOptions = [
|
||||
{ label: t('order.waybillManage.filter.fulfillmentDirect'), value: 'direct' },
|
||||
{ label: t('order.waybillManage.filter.fulfillmentWarehouse'), value: 'warehouse' },
|
||||
];
|
||||
const stockBindStatusOptions = ['none', 'bound', 'deducted', 'released'].map((code) => ({
|
||||
label: t(`order.myOrders.stockBind.status.${code}`),
|
||||
value: code,
|
||||
}));
|
||||
const fulfillProgressOptions = FULFILL_PROGRESS_TABS.filter((m) => m.value !== 'all').map((meta) => ({
|
||||
label: t(meta.labelKey),
|
||||
value: meta.value,
|
||||
}));
|
||||
const numberKeys = new Set([
|
||||
'waybillNo',
|
||||
'customerOrderNo',
|
||||
@ -1877,6 +1906,11 @@
|
||||
'clientNo',
|
||||
'trackNo',
|
||||
]);
|
||||
const enumFilter = (options: Array<{ label: string; value: string }>) => ({
|
||||
filterType: 'enum' as const,
|
||||
headerMode: 'icon' as const,
|
||||
filterOptions: options,
|
||||
});
|
||||
return withSortableColumns(
|
||||
withServerFilterColumns(
|
||||
buildHotColumns(columnCatalog.value, activeColumnKeys.value, t, te),
|
||||
@ -1901,12 +1935,23 @@
|
||||
if (col.key === 'receivableAmount') {
|
||||
return { filterType: 'number' as const, headerMode: 'icon' as const };
|
||||
}
|
||||
if (col.key === 'orderStatus' || col.key === 'previousOrderStatus') {
|
||||
return enumFilter(statusOptions);
|
||||
}
|
||||
if (col.key === 'handleStatus') {
|
||||
return enumFilter(handleStatusOptions);
|
||||
}
|
||||
if (col.key === 'fulfillmentType') {
|
||||
return {
|
||||
filterType: 'enum' as const,
|
||||
headerMode: 'icon' as const,
|
||||
filterOptions: fulfillmentTypeOptions,
|
||||
};
|
||||
return enumFilter(fulfillmentTypeOptions);
|
||||
}
|
||||
if (col.key === 'stockBindStatus') {
|
||||
return enumFilter(stockBindStatusOptions);
|
||||
}
|
||||
if (col.key === 'fulfillProgress') {
|
||||
return enumFilter(fulfillProgressOptions);
|
||||
}
|
||||
if (['isPaid', 'isIntercept', 'isRemote', 'isCod'].includes(col.key)) {
|
||||
return enumFilter(boolOptions);
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
@ -61,6 +61,7 @@
|
||||
</span>
|
||||
<TextType
|
||||
v-if="titleAnimate"
|
||||
:key="shortTitle ? 'title-short' : 'title-full'"
|
||||
as="span"
|
||||
class-name="ai-welcome-title__type"
|
||||
:text="shortTitle ? copy.titleRestShort : copy.titleRest"
|
||||
@ -337,8 +338,13 @@
|
||||
const homeDests = ROUTE_DEST_PRESETS.filter((item) => item.id !== 'all');
|
||||
const primaryDoors = HOME_DOORS.filter((d) => d.primary);
|
||||
const growthDoors = HOME_DOORS.filter((d) => !d.primary);
|
||||
const shortTitle = ref(false);
|
||||
const titleAnimate = ref(true);
|
||||
// 首屏即按视口决定短标题,避免挂载后再切导致 TextType 卡住只剩「IDE.」
|
||||
const shortTitle = ref(
|
||||
typeof window !== 'undefined' && window.matchMedia('(max-width: 640px)').matches,
|
||||
);
|
||||
const titleAnimate = ref(
|
||||
typeof window === 'undefined' || !window.matchMedia('(prefers-reduced-motion: reduce)').matches,
|
||||
);
|
||||
const draft = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
:disabled="busy"
|
||||
:value="modelValue"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@keydown="onKeydown"
|
||||
></textarea>
|
||||
<button type="submit" class="ai-composer-send" :class="{ 'is-empty': !modelValue.trim() }" :disabled="busy" :aria-label="sendLabel">
|
||||
@ -56,6 +57,15 @@
|
||||
resize();
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
const el = inputRef.value;
|
||||
if (!el) return;
|
||||
// iOS 键盘顶起后,把输入框滚进可视区
|
||||
window.setTimeout(() => {
|
||||
el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||
}, 80);
|
||||
}
|
||||
|
||||
function onKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter' && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
|
||||
@ -9,8 +9,22 @@
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<i class="mobile-nav-toggle mobile-nav-show si si-list" aria-hidden="true" @click="$emit('open-nav')"></i>
|
||||
<i class="mobile-nav-toggle mobile-nav-hide d-none si si-x" aria-hidden="true" @click="$emit('close-nav')"></i>
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-nav-toggle mobile-nav-show"
|
||||
:aria-label="isZh ? '打开菜单' : 'Open menu'"
|
||||
@click="$emit('open-nav')"
|
||||
>
|
||||
<i class="si si-list" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-nav-toggle mobile-nav-hide d-none"
|
||||
:aria-label="isZh ? '关闭菜单' : 'Close menu'"
|
||||
@click="$emit('close-nav')"
|
||||
>
|
||||
<i class="si si-x" aria-hidden="true"></i>
|
||||
</button>
|
||||
<nav id="navbar" class="navbar">
|
||||
<ul>
|
||||
<li ref="dockRef" class="site-nav-dock">
|
||||
|
||||
@ -129,11 +129,11 @@
|
||||
<article v-for="item in pageItems" :key="String(item.id)" class="ai-route-card">
|
||||
<div class="ai-route-card__head">
|
||||
<div class="ai-route-card__titles">
|
||||
<h4 class="ai-route-card__name">{{ item.productName }}</h4>
|
||||
<span
|
||||
v-if="item.productGroupName || item.productGroup"
|
||||
class="ai-route-card__group"
|
||||
>{{ item.productGroupName || item.productGroup }}</span>
|
||||
<h4 class="ai-route-card__name">{{ item.productName }}</h4>
|
||||
</div>
|
||||
<p v-if="formatRouteTimeliness(item)" class="ai-route-card__eta">
|
||||
{{ formatRouteTimeliness(item) }} {{ copy.days }}
|
||||
@ -145,21 +145,9 @@
|
||||
<span v-if="item.tradeTerms === 'DDP'"> · {{ copy.ddpHint }}</span>
|
||||
<span v-else-if="item.tradeTerms === 'DDU'"> · {{ copy.dduHint }}</span>
|
||||
</p>
|
||||
<p
|
||||
v-if="item.productDescription"
|
||||
class="ai-route-card__desc"
|
||||
:class="{ 'is-expanded': expanded[String(item.id)] }"
|
||||
>
|
||||
<p v-if="item.productDescription" class="ai-route-card__desc">
|
||||
{{ item.productDescription }}
|
||||
</p>
|
||||
<button
|
||||
v-if="item.productDescription.length > 72"
|
||||
type="button"
|
||||
class="ai-route-card__more"
|
||||
@click="toggleExpand(item.id)"
|
||||
>
|
||||
{{ expanded[String(item.id)] ? copy.collapse : copy.expand }}
|
||||
</button>
|
||||
<div class="ai-route-card__actions">
|
||||
<a class="ai-route-card__reg" :href="officialLoginUrl(destLabel(destFilter, lang) || undefined)">
|
||||
{{ copy.register }}
|
||||
@ -237,7 +225,6 @@
|
||||
const search = ref('');
|
||||
const currentPage = ref(1);
|
||||
const bodyRef = ref<HTMLElement | null>(null);
|
||||
const expanded = ref<Record<string, boolean>>({});
|
||||
|
||||
const groupChips = computed(() => {
|
||||
const groups = (catalog.value?.groups || []).map((g) => ({ code: g.code, name: g.name || g.code }));
|
||||
@ -324,11 +311,6 @@
|
||||
resetPage();
|
||||
}
|
||||
|
||||
function toggleExpand(id: string | number) {
|
||||
const key = String(id);
|
||||
expanded.value = { ...expanded.value, [key]: !expanded.value[key] };
|
||||
}
|
||||
|
||||
function askItem(item: RouteItem) {
|
||||
emit('ask', { text: buildRouteAskText(item, destFilter.value, lang.value), intentId: 'quote' });
|
||||
}
|
||||
|
||||
@ -123,7 +123,13 @@
|
||||
watch(
|
||||
() => props.text,
|
||||
() => {
|
||||
// text 变化时必须主动重启;若此时 displayedText 已是空串,依赖状态 watch 不会再触发
|
||||
resetAnimation();
|
||||
if (!isVisible.value) return;
|
||||
clearTimeoutIfNeeded();
|
||||
timeout = setTimeout(() => {
|
||||
executeTypingAnimation();
|
||||
}, props.initialDelay);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@ -208,16 +208,24 @@
|
||||
ensureOg('og:type', 'website');
|
||||
}
|
||||
|
||||
function onMobileNavBackdrop(e: MouseEvent) {
|
||||
const navEl = document.getElementById('navbar');
|
||||
if (!navEl || e.target !== navEl) return;
|
||||
closeMobileNav();
|
||||
}
|
||||
|
||||
function openMobileNav() {
|
||||
document.body.classList.add('mobile-nav-active');
|
||||
document.querySelectorAll('.mobile-nav-show').forEach((n) => n.classList.add('d-none'));
|
||||
document.querySelectorAll('.mobile-nav-hide').forEach((n) => n.classList.remove('d-none'));
|
||||
document.getElementById('navbar')?.addEventListener('click', onMobileNavBackdrop);
|
||||
}
|
||||
|
||||
function closeMobileNav() {
|
||||
document.body.classList.remove('mobile-nav-active');
|
||||
document.querySelectorAll('.mobile-nav-show').forEach((n) => n.classList.remove('d-none'));
|
||||
document.querySelectorAll('.mobile-nav-hide').forEach((n) => n.classList.add('d-none'));
|
||||
document.getElementById('navbar')?.removeEventListener('click', onMobileNavBackdrop);
|
||||
}
|
||||
|
||||
function syncHash(id: string) {
|
||||
@ -324,6 +332,10 @@
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
if (document.body.classList.contains('mobile-nav-active')) {
|
||||
closeMobileNav();
|
||||
return;
|
||||
}
|
||||
closePanel();
|
||||
historyOpen.value = false;
|
||||
wecomOpen.value = false;
|
||||
@ -336,6 +348,14 @@
|
||||
document.documentElement.style.setProperty('--ai-header-h', `${height}px`);
|
||||
}
|
||||
|
||||
function syncVisualViewport() {
|
||||
const vv = window.visualViewport;
|
||||
const height = Math.round(vv?.height ?? window.innerHeight);
|
||||
const offsetTop = Math.round(vv?.offsetTop ?? 0);
|
||||
document.documentElement.style.setProperty('--ai-vv-height', `${height}px`);
|
||||
document.documentElement.style.setProperty('--ai-vv-offset-top', `${offsetTop}px`);
|
||||
}
|
||||
|
||||
watch(panel, (id) => {
|
||||
document.body.classList.toggle('site-panel-routes', id === 'routes');
|
||||
document.body.classList.toggle('site-panel-services', id === 'services');
|
||||
@ -349,7 +369,11 @@
|
||||
document.body.classList.add('command-home');
|
||||
window.addEventListener('keydown', onKeydown);
|
||||
window.addEventListener('resize', syncHeaderOffset);
|
||||
window.addEventListener('resize', syncVisualViewport);
|
||||
window.visualViewport?.addEventListener('resize', syncVisualViewport);
|
||||
window.visualViewport?.addEventListener('scroll', syncVisualViewport);
|
||||
syncHeaderOffset();
|
||||
syncVisualViewport();
|
||||
const hash = (window.location.hash || '').replace('#', '');
|
||||
if (hash === '9610' || hash === 'services') openPanel('services');
|
||||
else if (NAV_PANELS.includes(hash as PanelId)) openPanel(hash as Exclude<PanelId, null>);
|
||||
@ -359,6 +383,9 @@
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', onKeydown);
|
||||
window.removeEventListener('resize', syncHeaderOffset);
|
||||
window.removeEventListener('resize', syncVisualViewport);
|
||||
window.visualViewport?.removeEventListener('resize', syncVisualViewport);
|
||||
window.visualViewport?.removeEventListener('scroll', syncVisualViewport);
|
||||
document.body.classList.remove('site-panel-routes', 'site-panel-services');
|
||||
document.documentElement.classList.remove('command-home', 'app-boot-site');
|
||||
closeMobileNav();
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
--ai-glass: var(--ai-surface);
|
||||
--ai-content-max: 1200px;
|
||||
--ai-guide-panel-w: 760px;
|
||||
--ai-routes-panel-w: 1080px;
|
||||
--ai-routes-panel-w: 1280px;
|
||||
--ai-acrylic-bg: rgba(255, 255, 255, 0.32);
|
||||
--ai-acrylic-bg-primary: color-mix(in srgb, var(--ai-gold-soft) 42%, rgba(255, 255, 255, 0.38));
|
||||
/* Soft fills ? cool tint aligned with GradientWaves */
|
||||
@ -169,6 +169,27 @@ body.command-home #navbar .site-nav-dock a {
|
||||
}
|
||||
|
||||
@media (max-width: 1279px) {
|
||||
/* 手机/平板抽屉:纵向列表,浅色底保证对比度 */
|
||||
body.command-home #navbar,
|
||||
body.command-home #navbar.navbar {
|
||||
width: min(100%, 380px);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
body.command-home #navbar > ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 4px;
|
||||
padding: calc(56px + env(safe-area-inset-top, 0px)) 18px
|
||||
calc(24px + env(safe-area-inset-bottom, 0px));
|
||||
background: #f4f7fa !important;
|
||||
background-image: linear-gradient(180deg, #f8fafc 0%, #eef3f7 100%) !important;
|
||||
border-top-left-radius: 24px;
|
||||
border-bottom-left-radius: 24px;
|
||||
box-shadow: -12px 0 40px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
body.command-home .site-nav-dock {
|
||||
display: contents;
|
||||
}
|
||||
@ -176,22 +197,112 @@ body.command-home #navbar .site-nav-dock a {
|
||||
body.command-home .site-nav-dock__glass,
|
||||
body.command-home .site-nav-dock__slider,
|
||||
body.command-home .site-nav-tools__glass {
|
||||
display: none;
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body.command-home #navbar .site-nav-dock__item {
|
||||
body.command-home #navbar > ul {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
body.command-home #navbar .site-nav-dock__item,
|
||||
body.command-home #navbar .site-nav-dock a,
|
||||
body.command-home #navbar .site-nav-dock a:focus {
|
||||
display: flex !important;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
min-height: 52px;
|
||||
padding: 14px 16px !important;
|
||||
border-radius: 14px;
|
||||
color: var(--ai-text) !important;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body.command-home #navbar .site-nav-dock a:hover,
|
||||
body.command-home #navbar .site-nav-dock a.active,
|
||||
body.command-home #navbar .site-nav-dock a.active:focus,
|
||||
body.command-home #navbar .site-nav-dock a.is-active,
|
||||
body.command-home #navbar .site-nav-dock a.is-active:focus {
|
||||
color: var(--ai-action) !important;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
body.command-home #navbar .site-nav-dock__icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
flex-shrink: 0;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
body.command-home #navbar .site-nav-dock__label {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
body.command-home #navbar > ul > li.site-nav-tools {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
margin-top: 14px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid rgba(18, 24, 32, 0.08);
|
||||
}
|
||||
|
||||
body.command-home #navbar .site-nav-tools .site-nav-login,
|
||||
body.command-home #navbar .site-nav-tools .site-nav-login:focus,
|
||||
body.command-home #navbar .site-nav-tools .site-nav-login:hover,
|
||||
body.command-home #navbar .site-nav-tools .site-nav-login:visited,
|
||||
body.command-home #navbar .site-nav-tools .language-toggle-link,
|
||||
body.command-home #navbar .site-nav-tools .language-toggle-link:focus,
|
||||
body.command-home #navbar .site-nav-tools .language-toggle-link:hover,
|
||||
body.command-home #navbar .site-nav-tools .language-toggle-link.active,
|
||||
body.command-home #navbar .site-nav-tools .language-toggle-link.active:focus {
|
||||
display: inline-flex !important;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 46px;
|
||||
padding: 0 16px !important;
|
||||
border-radius: 14px;
|
||||
background: #fff !important;
|
||||
color: var(--ai-action) !important;
|
||||
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
body.command-home .mobile-nav-show,
|
||||
body.command-home .mobile-nav-hide {
|
||||
color: var(--ai-action);
|
||||
}
|
||||
|
||||
body.command-home .mobile-nav-show,
|
||||
body.command-home .mobile-nav-hide {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
body.command-home .mobile-nav-hide {
|
||||
color: var(--ai-text);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.12);
|
||||
}
|
||||
|
||||
body.command-home.mobile-nav-active .mobile-nav-hide {
|
||||
color: var(--ai-text);
|
||||
}
|
||||
|
||||
/* 点遮罩关闭:伪元素承接点击,落到 #navbar 本身 */
|
||||
body.command-home.mobile-nav-active #navbar {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
body.command-home.mobile-nav-active #navbar > ul {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
@ -676,10 +787,11 @@ body.command-home #navbar a.language-toggle-link:hover .language-toggle-icon {
|
||||
body.command-home #ai-advisor-shell {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
top: var(--ai-vv-offset-top, 0px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
bottom: auto;
|
||||
height: var(--ai-vv-height, 100%);
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -1987,7 +2099,7 @@ body.command-home .ai-stage-map--eu {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 180px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
border: none;
|
||||
@ -2009,7 +2121,7 @@ body.command-home .ai-stage-map--eu {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 0;
|
||||
gap: 6px 8px;
|
||||
}
|
||||
|
||||
.ai-route-card__group {
|
||||
@ -2021,13 +2133,6 @@ body.command-home .ai-stage-map--eu {
|
||||
color: var(--ai-muted);
|
||||
}
|
||||
|
||||
.ai-route-card__group::after {
|
||||
content: '?';
|
||||
margin: 0 6px;
|
||||
font-weight: 500;
|
||||
color: color-mix(in srgb, var(--ai-muted) 55%, transparent);
|
||||
}
|
||||
|
||||
.ai-route-card__name {
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
@ -2086,32 +2191,14 @@ body.command-home .ai-stage-map--eu {
|
||||
}
|
||||
|
||||
.ai-route-card__desc {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.45;
|
||||
color: var(--ai-text-3);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ai-route-card__desc.is-expanded {
|
||||
display: block;
|
||||
-webkit-line-clamp: unset;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.ai-route-card__more {
|
||||
align-self: flex-start;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
color: var(--ai-action);
|
||||
font-size: 0.76rem;
|
||||
font-weight: 650;
|
||||
cursor: pointer;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.ai-route-card__actions {
|
||||
@ -4310,8 +4397,19 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
:root {
|
||||
--ai-header-h: 64px;
|
||||
}
|
||||
|
||||
body.command-home #header {
|
||||
padding-top: env(safe-area-inset-top, 0px);
|
||||
}
|
||||
|
||||
.ai-advisor-layout {
|
||||
padding: calc(var(--ai-header-h) + 6px) 14px 0;
|
||||
padding: calc(var(--ai-header-h) + env(safe-area-inset-top, 0px) + 4px)
|
||||
max(14px, env(safe-area-inset-left, 0px))
|
||||
0
|
||||
max(14px, env(safe-area-inset-right, 0px));
|
||||
}
|
||||
|
||||
.ai-routes-panel {
|
||||
@ -4364,27 +4462,136 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
}
|
||||
|
||||
.ai-welcome__stack {
|
||||
margin-top: 4px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.ai-welcome-stats--compact {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ai-welcome-stats--compact .ai-welcome-stats__item {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ai-welcome-stats--compact .ai-welcome-stats__item:nth-child(n + 3) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ai-welcome-stats__value {
|
||||
font-size: clamp(1.35rem, 6vw, 1.7rem);
|
||||
}
|
||||
|
||||
.ai-welcome-stats__label {
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.ai-intent-grid--primary {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ai-intent-grid--primary .ai-intent-door:last-child {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.ai-partner-marquee {
|
||||
min-width: 0;
|
||||
/* 权益:横向滑动,首屏露出首卡 + 右缘提示 */
|
||||
.ai-intent-grid--growth {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: 0 2px 8px;
|
||||
scroll-snap-type: x proximity;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
touch-action: pan-x;
|
||||
mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 28px), transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 28px), transparent 100%);
|
||||
}
|
||||
|
||||
.ai-partner-marquee__viewport {
|
||||
flex: 0 1 auto;
|
||||
.ai-intent-grid--growth::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ai-intent-grid--growth .ai-intent-door {
|
||||
flex: 0 0 min(72vw, 240px);
|
||||
width: auto;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-door,
|
||||
.ai-welcome__command .ai-intent-door.is-primary {
|
||||
min-height: 52px;
|
||||
padding: 9px 12px 9px 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ai-intent-grid--primary {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-door__icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-door__title,
|
||||
.ai-welcome__command .ai-intent-door.is-primary .ai-intent-door__title {
|
||||
font-size: 0.98rem;
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-door__hint,
|
||||
.ai-welcome__command .ai-intent-door.is-primary .ai-intent-door__hint {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.35;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
}
|
||||
|
||||
.ai-partner-marquee {
|
||||
min-width: 0;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.ai-partner-marquee__viewport,
|
||||
.ai-partner-marquee__viewport.is-static {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
touch-action: pan-x;
|
||||
mask-image: linear-gradient(90deg, transparent, #000 18px, #000 calc(100% - 18px), transparent);
|
||||
-webkit-mask-image: linear-gradient(90deg, transparent, #000 18px, #000 calc(100% - 18px), transparent);
|
||||
}
|
||||
|
||||
.ai-partner-marquee__viewport::-webkit-scrollbar,
|
||||
.ai-partner-marquee__viewport.is-static::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ai-partner-marquee__viewport.is-static .ai-partner-marquee__track {
|
||||
justify-content: flex-start;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.ai-partner-marquee__viewport.is-static .ai-partner-marquee__strip {
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ai-trust-bar {
|
||||
--ai-trust-bar-h: 76px;
|
||||
gap: 8px 14px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
.ai-trust-icp {
|
||||
@ -4392,33 +4599,125 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
font-size: 0.66rem;
|
||||
}
|
||||
|
||||
/* 首页可滚:底部淡出提示还有内容(对话页不套 mask) */
|
||||
.ai-app-main:has(.ai-welcome) {
|
||||
-webkit-mask-image: linear-gradient(
|
||||
to bottom,
|
||||
#000 0%,
|
||||
#000 calc(100% - 22px),
|
||||
transparent 100%
|
||||
);
|
||||
mask-image: linear-gradient(
|
||||
to bottom,
|
||||
#000 0%,
|
||||
#000 calc(100% - 22px),
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
.ai-welcome__meta {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.ai-welcome__deck {
|
||||
padding: 14px 8px 12px;
|
||||
padding: 8px 4px 6px;
|
||||
}
|
||||
|
||||
.ai-welcome__command {
|
||||
padding: 0 0 4px;
|
||||
gap: 14px;
|
||||
padding: 0 0 calc(12px + env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
|
||||
.ai-intent-section {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ai-intent-group__label {
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--ai-text-2);
|
||||
}
|
||||
|
||||
.ai-welcome__command .ai-intent-door__hint,
|
||||
.ai-welcome__command .ai-intent-door.is-primary .ai-intent-door__hint {
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
}
|
||||
|
||||
.ai-welcome-title {
|
||||
font-size: clamp(1.25rem, 5vw, 1.75rem);
|
||||
white-space: normal;
|
||||
font-size: clamp(1.45rem, 6.2vw, 1.85rem);
|
||||
line-height: 1.28;
|
||||
}
|
||||
|
||||
.ai-welcome-sub {
|
||||
font-size: 0.84rem;
|
||||
white-space: nowrap;
|
||||
.ai-welcome-compose-panel {
|
||||
padding: 12px 12px 10px;
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.ai-welcome-compose-panel .ai-welcome-cta-link {
|
||||
position: static;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
margin: 0 0 10px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--ai-action) 12%, #fff);
|
||||
color: var(--ai-action);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ai-welcome-compose-panel .ai-welcome-cta-link:hover,
|
||||
.ai-welcome-compose-panel .ai-welcome-cta-link:focus {
|
||||
background: color-mix(in srgb, var(--ai-action) 18%, #fff);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ai-welcome-compose-panel .ai-welcome-compose__lead {
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.ai-home-dests {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
overflow-x: auto;
|
||||
max-width: 94vw;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-bottom: 2px;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-x;
|
||||
mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 20px), transparent 100%);
|
||||
-webkit-mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 20px), transparent 100%);
|
||||
}
|
||||
|
||||
.ai-welcome-sub::-webkit-scrollbar {
|
||||
.ai-home-dests::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ai-trust-bar {
|
||||
gap: 8px 14px;
|
||||
.ai-home-dest {
|
||||
flex: 0 0 auto;
|
||||
min-height: 40px;
|
||||
padding: 0 14px;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.ai-welcome-sub {
|
||||
font-size: 0.88rem;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ai-new-chat-tip {
|
||||
@ -4428,20 +4727,25 @@ body.command-home #navbar .site-nav-avatar:hover {
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.ai-composer {
|
||||
min-height: 76px;
|
||||
padding: 12px 12px 12px 18px;
|
||||
border-radius: 26px;
|
||||
.ai-composer,
|
||||
.ai-composer-slot--welcome .ai-composer {
|
||||
min-height: 56px;
|
||||
padding: 10px 10px 10px 16px;
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.ai-composer-input {
|
||||
min-height: 48px;
|
||||
font-size: 1rem;
|
||||
min-height: 44px;
|
||||
font-size: 16px; /* iOS 避免自动放大 */
|
||||
}
|
||||
|
||||
.ai-composer-send {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.ai-composer-dock {
|
||||
padding-bottom: max(10px, env(safe-area-inset-bottom, 0px));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -184,24 +184,37 @@ body.command-home {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mobile-nav-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.mobile-nav-toggle .si {
|
||||
font-size: 26px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.mobile-nav-show {
|
||||
z-index: 9999;
|
||||
margin-right: 10px;
|
||||
margin-right: 2px;
|
||||
color: #fff;
|
||||
font-size: 28px;
|
||||
line-height: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mobile-nav-hide {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
top: max(16px, env(safe-area-inset-top, 0px));
|
||||
right: max(14px, env(safe-area-inset-right, 0px));
|
||||
z-index: 9999;
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
line-height: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mobile-nav-active {
|
||||
|
||||
@ -474,6 +474,7 @@ function handleSyncMenu(record: MenuTreeDto) {
|
||||
const result = await syncMenuTree(record)
|
||||
const hasChanges =
|
||||
result.level2Created > 0 ||
|
||||
result.level2Updated > 0 ||
|
||||
result.level3Created > 0 ||
|
||||
result.level3Updated > 0 ||
|
||||
result.level3Deleted > 0
|
||||
@ -483,6 +484,7 @@ function handleSyncMenu(record: MenuTreeDto) {
|
||||
Message.success(
|
||||
t('system.menu.message.syncModuleSuccess', {
|
||||
level2Created: result.level2Created,
|
||||
level2Updated: result.level2Updated,
|
||||
level2Skipped: result.level2Skipped,
|
||||
level3Created: result.level3Created,
|
||||
level3Updated: result.level3Updated,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user