20261027 #13
@ -1,17 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { getWarehouseList } from '@/api/system/warehouse'
|
import { getWarehouseList } from '@/api/system/warehouse'
|
||||||
import { useCurrentWarehouseStore } from '@/store'
|
import { useCurrentWarehouseStore } from '@/store'
|
||||||
|
import WarehouseAddressPeekModal from '@/components/customer-side-rail/WarehouseAddressPeekModal.vue'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
variant?: 'navbar' | 'login' | 'sider'
|
variant?: 'navbar' | 'login' | 'sider'
|
||||||
/** 下拉位置;侧栏沉底用 rt */
|
/** 下拉位置;侧栏沉底用 rt(login 下拉保留) */
|
||||||
dropdownPosition?: 'br' | 'bl' | 'tr' | 'tl' | 'rt' | 'rb'
|
dropdownPosition?: 'br' | 'bl' | 'tr' | 'tl' | 'rt' | 'rb'
|
||||||
/** 侧栏收起:窄宽展示缩略文案(无图标) */
|
/** 侧栏收起:窄宽展示缩略文案(无图标) */
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
/** 下拉触发;侧栏默认 click,顶栏 hover */
|
/** 下拉触发;侧栏默认 click,顶栏 hover(已改为弹窗,仅 login 相关遗留) */
|
||||||
trigger?: 'hover' | 'click'
|
trigger?: 'hover' | 'click'
|
||||||
/** 侧栏展开:显示「发货仓库」语义前缀 */
|
/** 侧栏展开:显示「发货仓库」语义前缀 */
|
||||||
showCaption?: boolean
|
showCaption?: boolean
|
||||||
@ -29,6 +30,7 @@ const props = withDefaults(
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const warehouseStore = useCurrentWarehouseStore()
|
const warehouseStore = useCurrentWarehouseStore()
|
||||||
|
const peekVisible = ref(false)
|
||||||
|
|
||||||
const displayName = computed(
|
const displayName = computed(
|
||||||
() =>
|
() =>
|
||||||
@ -45,24 +47,13 @@ const labelText = computed(() => {
|
|||||||
return name
|
return name
|
||||||
})
|
})
|
||||||
|
|
||||||
const useDropdown = computed(() => props.variant === 'navbar' || props.variant === 'sider')
|
/** 顶栏 / 侧栏:点击打开仓库地址卡片弹窗(与客户侧栏一致) */
|
||||||
|
const usePeekModal = computed(() => props.variant === 'navbar' || props.variant === 'sider')
|
||||||
const dropdownTrigger = computed(() => {
|
|
||||||
if (props.trigger) return props.trigger
|
|
||||||
return props.variant === 'sider' ? 'click' : 'hover'
|
|
||||||
})
|
|
||||||
|
|
||||||
const showSiderCaption = computed(
|
const showSiderCaption = computed(
|
||||||
() => props.showCaption && props.variant === 'sider' && !props.compact,
|
() => 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() {
|
async function loadServiceDataList() {
|
||||||
try {
|
try {
|
||||||
const { items } = await getWarehouseList({
|
const { items } = await getWarehouseList({
|
||||||
@ -82,8 +73,8 @@ async function loadServiceDataList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSelectWarehouse(id: number) {
|
function openPeek() {
|
||||||
warehouseStore.setWarehouseId(id)
|
peekVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -99,37 +90,19 @@ onMounted(() => {
|
|||||||
{ 'is-compact': compact },
|
{ 'is-compact': compact },
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<a-dropdown
|
<button
|
||||||
v-if="useDropdown"
|
v-if="usePeekModal"
|
||||||
:trigger="dropdownTrigger"
|
type="button"
|
||||||
:position="dropdownPosition"
|
class="contact-service__label"
|
||||||
:content-class="contentClass"
|
:title="displayName"
|
||||||
:popup-translate="popupTranslate"
|
:aria-label="`${t('nav.contactService.shipWarehouse')}: ${displayName}`"
|
||||||
:popup-max-height="320"
|
@click="openPeek"
|
||||||
>
|
>
|
||||||
<button
|
<span v-if="showSiderCaption" class="contact-service__caption">
|
||||||
type="button"
|
{{ t('nav.contactService.shipWarehouse') }}
|
||||||
class="contact-service__label"
|
</span>
|
||||||
:title="displayName"
|
<span class="contact-service__text">{{ labelText }}</span>
|
||||||
:aria-label="`${t('nav.contactService.shipWarehouse')}: ${displayName}`"
|
</button>
|
||||||
>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<a-select
|
<a-select
|
||||||
v-else
|
v-else
|
||||||
@ -147,6 +120,11 @@ onMounted(() => {
|
|||||||
{{ opt.label }}
|
{{ opt.label }}
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|
||||||
|
<WarehouseAddressPeekModal
|
||||||
|
v-if="usePeekModal"
|
||||||
|
v-model:visible="peekVisible"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
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';
|
import { resolveArcoIcon, toPascalIconName } from '@/utils/menu-icon';
|
||||||
|
|
||||||
defineOptions({ name: 'MenuIconPicker' });
|
defineOptions({ name: 'MenuIconPicker' });
|
||||||
@ -28,60 +29,33 @@
|
|||||||
const pickerVisible = ref(false);
|
const pickerVisible = ref(false);
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const draftIcon = ref('');
|
const draftIcon = ref('');
|
||||||
|
const activeCategory = ref(MENU_ICON_CATEGORIES[0]?.key || 'common');
|
||||||
|
|
||||||
const normalizedValue = computed(() => toPascalIconName(props.modelValue || ''));
|
const normalizedValue = computed(() => toPascalIconName(props.modelValue || ''));
|
||||||
const previewIcon = computed(() => resolveArcoIcon(normalizedValue.value));
|
const previewIcon = computed(() => resolveArcoIcon(normalizedValue.value));
|
||||||
|
|
||||||
const filteredIcons = computed(() => {
|
const categoryTabs = computed(() =>
|
||||||
const list = filterArcoIconNames(keyword.value)
|
MENU_ICON_CATEGORIES.map((cat) => ({
|
||||||
if (keyword.value.trim()) return list
|
key: cat.key,
|
||||||
const commonSet = new Set(commonIcons)
|
label: t(`system.menu.iconPicker.category.${cat.key}`),
|
||||||
return list.filter((name) => !commonSet.has(name))
|
count: cat.icons.length,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const categoryIcons = computed(() => {
|
||||||
|
const cat = MENU_ICON_CATEGORIES.find((item) => item.key === activeCategory.value);
|
||||||
|
return cat?.icons || [];
|
||||||
});
|
});
|
||||||
|
|
||||||
const commonIcons = [
|
const searchIcons = computed(() => filterArcoIconNames(keyword.value));
|
||||||
'IconHome',
|
|
||||||
'IconMenu',
|
const isSearching = computed(() => Boolean(keyword.value.trim()));
|
||||||
'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));
|
|
||||||
|
|
||||||
watch(pickerVisible, (visible) => {
|
watch(pickerVisible, (visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
draftIcon.value = normalizedValue.value;
|
draftIcon.value = normalizedValue.value;
|
||||||
keyword.value = '';
|
keyword.value = '';
|
||||||
|
activeCategory.value = MENU_ICON_CATEGORIES[0]?.key || 'common';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,7 +107,7 @@
|
|||||||
v-model:visible="pickerVisible"
|
v-model:visible="pickerVisible"
|
||||||
:title="t('system.menu.iconPicker.title')"
|
:title="t('system.menu.iconPicker.title')"
|
||||||
:footer="false"
|
:footer="false"
|
||||||
width="720px"
|
width="840px"
|
||||||
unmount-on-close
|
unmount-on-close
|
||||||
class="menu-icon-picker-modal"
|
class="menu-icon-picker-modal"
|
||||||
>
|
>
|
||||||
@ -150,33 +124,74 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!keyword.trim()" class="picker-section">
|
<template v-if="!isSearching">
|
||||||
<div class="picker-section-title">{{ t('system.menu.iconPicker.common') }}</div>
|
<div class="picker-category-tabs">
|
||||||
<div class="icon-grid">
|
|
||||||
<button
|
<button
|
||||||
v-for="name in commonIcons"
|
v-for="tab in categoryTabs"
|
||||||
:key="`common-${name}`"
|
:key="tab.key"
|
||||||
type="button"
|
type="button"
|
||||||
class="icon-item"
|
class="category-tab"
|
||||||
:class="{ active: draftIcon === name }"
|
:class="{ active: activeCategory === tab.key }"
|
||||||
:title="name"
|
@click="activeCategory = tab.key"
|
||||||
@click="selectIcon(name)"
|
|
||||||
>
|
>
|
||||||
<component :is="resolveArcoIcon(name)" />
|
<span>{{ tab.label }}</span>
|
||||||
<span class="icon-name">{{ name.replace(/^Icon/, '') }}</span>
|
<em>{{ tab.count }}</em>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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">
|
<div class="picker-section-title">
|
||||||
{{ keyword.trim() ? t('system.menu.iconPicker.searchResult') : t('system.menu.iconPicker.all') }}
|
{{ t('system.menu.iconPicker.searchResult') }}
|
||||||
<span class="picker-count">({{ filteredIcons.length }})</span>
|
<span class="picker-count">({{ searchIcons.length }})</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="icon-grid">
|
<div class="icon-grid">
|
||||||
<button
|
<button
|
||||||
v-for="name in filteredIcons"
|
v-for="name in searchIcons"
|
||||||
:key="name"
|
:key="`search-${name}`"
|
||||||
type="button"
|
type="button"
|
||||||
class="icon-item"
|
class="icon-item"
|
||||||
:class="{ active: draftIcon === name }"
|
:class="{ active: draftIcon === name }"
|
||||||
@ -188,7 +203,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<a-empty
|
<a-empty
|
||||||
v-if="filteredIcons.length === 0"
|
v-if="searchIcons.length === 0"
|
||||||
:description="t('system.menu.iconPicker.empty')"
|
:description="t('system.menu.iconPicker.empty')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -236,7 +251,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-current {
|
.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 {
|
||||||
& + .picker-section {
|
& + .picker-section {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
@ -279,9 +336,16 @@
|
|||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.picker-hint {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--color-text-4);
|
||||||
|
}
|
||||||
|
|
||||||
.icon-grid {
|
.icon-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* 订单状态(与 oms0414-back OrderStatusCodes、设计文档 §10.3.1 一致)
|
* 订单状态(与 oms0414-back OrderStatusCodes、设计文档 §10.3.1 一致)
|
||||||
*
|
*
|
||||||
* 标签色系:复用 utils/statusTag 信息强弱分档。
|
* 标签色系:按生命周期语义分色;渲染仍走 utils/statusTag。
|
||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
STATUS_TAG_TONES,
|
STATUS_TAG_TONES,
|
||||||
buildBooleanTagHtml,
|
buildBooleanTagHtml,
|
||||||
buildStatusTagHtml,
|
buildStatusTagHtml,
|
||||||
getStatusTagStyle,
|
getStatusTagStyle,
|
||||||
type StatusTagColors,
|
|
||||||
type StatusTagTone,
|
type StatusTagTone,
|
||||||
} from '@/utils/statusTag'
|
} from '@/utils/statusTag'
|
||||||
|
|
||||||
@ -46,10 +45,6 @@ export function isOrderDeletable(status: unknown): boolean {
|
|||||||
return ORDER_DELETABLE_STATUSES.includes(code as OrderStatusCode)
|
return ORDER_DELETABLE_STATUSES.includes(code as OrderStatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
function tone(t: StatusTagTone): StatusTagColors {
|
|
||||||
return STATUS_TAG_TONES[t]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OrderStatusMeta {
|
export interface OrderStatusMeta {
|
||||||
value: OrderStatusCode
|
value: OrderStatusCode
|
||||||
/** i18n key:tabs.* 与 status.* 共用同一套文案 */
|
/** i18n key:tabs.* 与 status.* 共用同一套文案 */
|
||||||
@ -87,62 +82,106 @@ export const ORDER_STATUS_NAME_TO_CODE: Record<string, OrderStatusCode> = {
|
|||||||
已认领: 'claimed',
|
已认领: 'claimed',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单状态标签色:按生命周期语义分色(软底标签),便于列表一眼区分。
|
||||||
|
* 待办暖色 / 在途品牌色 / 成功绿 / 失败红 / 问题橙 / 理赔紫。
|
||||||
|
*/
|
||||||
export const ORDER_STATUS_LIST: OrderStatusMeta[] = [
|
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',
|
value: 'pendingAllocation',
|
||||||
labelKey: 'order.myOrders.status.pendingAllocation',
|
labelKey: 'order.myOrders.status.pendingAllocation',
|
||||||
...tone('wait'),
|
tagColor: '#8a6d3b',
|
||||||
|
tagBgColor: '#f7f2e8',
|
||||||
|
tagBorder: '#e8dcc6',
|
||||||
|
rowBg: '#faf8f5',
|
||||||
showTabCount: true,
|
showTabCount: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'pendingPayment',
|
value: 'pendingPayment',
|
||||||
labelKey: 'order.myOrders.status.pendingPayment',
|
labelKey: 'order.myOrders.status.pendingPayment',
|
||||||
tagColor: '#f53f3f',
|
tagColor: '#ea580c',
|
||||||
tagBgColor: '#ffece8',
|
tagBgColor: '#fff4e5',
|
||||||
tagBorder: '#fbaca3',
|
tagBorder: '#fb923c',
|
||||||
rowBg: '#ffece8',
|
rowBg: '#fff7ed',
|
||||||
showTabCount: true,
|
showTabCount: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'pendingShip',
|
value: 'pendingShip',
|
||||||
labelKey: 'order.myOrders.status.pendingShip',
|
labelKey: 'order.myOrders.status.pendingShip',
|
||||||
...tone('wait'),
|
tagColor: '#0f766e',
|
||||||
|
tagBgColor: '#f0fdfa',
|
||||||
|
tagBorder: '#99f6e4',
|
||||||
showTabCount: true,
|
showTabCount: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'pendingOnline',
|
value: 'pendingOnline',
|
||||||
labelKey: 'order.myOrders.status.pendingOnline',
|
labelKey: 'order.myOrders.status.pendingOnline',
|
||||||
...tone('brand'),
|
tagColor: '#1d4ed8',
|
||||||
|
tagBgColor: '#eff6ff',
|
||||||
|
tagBorder: '#bfdbfe',
|
||||||
showTabCount: true,
|
showTabCount: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'shipping',
|
value: 'shipping',
|
||||||
labelKey: 'order.myOrders.status.shipping',
|
labelKey: 'order.myOrders.status.shipping',
|
||||||
...tone('brand'),
|
tagColor: '#054f74',
|
||||||
|
tagBgColor: '#eef3f6',
|
||||||
|
tagBorder: '#b6cdd9',
|
||||||
showTabCount: true,
|
showTabCount: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'deliverySuccess',
|
value: 'deliverySuccess',
|
||||||
labelKey: 'order.myOrders.status.deliverySuccess',
|
labelKey: 'order.myOrders.status.deliverySuccess',
|
||||||
...tone('ok'),
|
tagColor: '#15803d',
|
||||||
|
tagBgColor: '#f0fdf4',
|
||||||
|
tagBorder: '#bbf7d0',
|
||||||
showTabCount: true,
|
showTabCount: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'deliveryFailed',
|
value: 'deliveryFailed',
|
||||||
labelKey: 'order.myOrders.status.deliveryFailed',
|
labelKey: 'order.myOrders.status.deliveryFailed',
|
||||||
...tone('alert'),
|
tagColor: '#b91c1c',
|
||||||
|
tagBgColor: '#fef2f2',
|
||||||
|
tagBorder: '#fecaca',
|
||||||
showTabCount: true,
|
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',
|
value: 'problematic',
|
||||||
labelKey: 'order.myOrders.status.problematic',
|
labelKey: 'order.myOrders.status.problematic',
|
||||||
...tone('alert'),
|
tagColor: '#c2410c',
|
||||||
|
tagBgColor: '#fff7ed',
|
||||||
|
tagBorder: '#fdba74',
|
||||||
|
rowBg: '#fff7ed',
|
||||||
showTabCount: true,
|
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: 'Query', name: '查询' },
|
||||||
{ code: 'View', 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 ──
|
// ── API ──
|
||||||
B('Api.UpstreamInterface', [
|
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 localeWorkplace from '@/views/home/workplace/locale/en-US';
|
||||||
|
|
||||||
import localeMonitor from '@/views/home/monitor/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 localeSettings from './en-US/settings';
|
||||||
import marketingEn from './marketing/en-US';
|
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.syncLevel3Skipped': 'Nothing to sync, {skipped} existing button(s) skipped',
|
||||||
'system.menu.message.syncLevel3Failed': 'Failed to sync level-3 buttons',
|
'system.menu.message.syncLevel3Failed': 'Failed to sync level-3 buttons',
|
||||||
'system.menu.message.syncModuleSuccess':
|
'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':
|
'system.menu.message.syncModuleSkipped':
|
||||||
'Nothing to sync, skipped {level2Skipped} level-2 menu(s) and {level3Skipped} level-3 button(s)',
|
'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}',
|
'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.choose': 'Choose icon',
|
||||||
'system.menu.iconPicker.title': 'Choose menu icon',
|
'system.menu.iconPicker.title': 'Choose menu icon',
|
||||||
'system.menu.iconPicker.placeholder': 'Enter or pick an icon, e.g. IconBook',
|
'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.current': 'Selected',
|
||||||
'system.menu.iconPicker.common': 'Common',
|
'system.menu.iconPicker.common': 'Common',
|
||||||
'system.menu.iconPicker.all': 'All icons',
|
'system.menu.iconPicker.all': 'All icons',
|
||||||
|
'system.menu.iconPicker.allHint': 'Or search by name',
|
||||||
'system.menu.iconPicker.searchResult': 'Search results',
|
'system.menu.iconPicker.searchResult': 'Search results',
|
||||||
'system.menu.iconPicker.empty': 'No matching icons',
|
'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 Management (Permission Assignment)
|
||||||
'system.role.permission.expandAll': 'Expand All',
|
'system.role.permission.expandAll': 'Expand All',
|
||||||
@ -6811,6 +6821,7 @@ export default {
|
|||||||
...localeWorkplace,
|
...localeWorkplace,
|
||||||
|
|
||||||
...localeMonitor,
|
...localeMonitor,
|
||||||
|
...localeEventBus,
|
||||||
...marketingEn,
|
...marketingEn,
|
||||||
...kbEn,
|
...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 localeWorkplace from '@/views/home/workplace/locale/zh-CN';
|
||||||
|
|
||||||
import localeMonitor from '@/views/home/monitor/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 localeSettings from './zh-CN/settings';
|
||||||
import marketingZh from './marketing/zh-CN';
|
import marketingZh from './marketing/zh-CN';
|
||||||
@ -3300,7 +3301,7 @@ export default {
|
|||||||
'system.menu.message.syncLevel3Skipped': '无需同步,已跳过 {skipped} 个已存在按钮',
|
'system.menu.message.syncLevel3Skipped': '无需同步,已跳过 {skipped} 个已存在按钮',
|
||||||
'system.menu.message.syncLevel3Failed': '同步三级按钮失败',
|
'system.menu.message.syncLevel3Failed': '同步三级按钮失败',
|
||||||
'system.menu.message.syncModuleSuccess':
|
'system.menu.message.syncModuleSuccess':
|
||||||
'同步完成:二级菜单新增 {level2Created} 个、跳过 {level2Skipped} 个;三级按钮新增 {level3Created} 个、修改 {level3Updated} 个、删除 {level3Deleted} 个、跳过 {level3Skipped} 个',
|
'同步完成:二级菜单新增 {level2Created} 个、更新 {level2Updated} 个、跳过 {level2Skipped} 个;三级按钮新增 {level3Created} 个、修改 {level3Updated} 个、删除 {level3Deleted} 个、跳过 {level3Skipped} 个',
|
||||||
'system.menu.message.syncModuleSkipped':
|
'system.menu.message.syncModuleSkipped':
|
||||||
'无需同步,已跳过 {level2Skipped} 个二级菜单、{level3Skipped} 个三级按钮',
|
'无需同步,已跳过 {level2Skipped} 个二级菜单、{level3Skipped} 个三级按钮',
|
||||||
'system.menu.message.syncModulePartialFailed': '部分页面三级按钮同步失败:{details}',
|
'system.menu.message.syncModulePartialFailed': '部分页面三级按钮同步失败:{details}',
|
||||||
@ -3503,12 +3504,21 @@ export default {
|
|||||||
'system.menu.iconPicker.choose': '选择图标',
|
'system.menu.iconPicker.choose': '选择图标',
|
||||||
'system.menu.iconPicker.title': '选择菜单图标',
|
'system.menu.iconPicker.title': '选择菜单图标',
|
||||||
'system.menu.iconPicker.placeholder': '请输入或选择图标,如 IconBook',
|
'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.current': '当前选中',
|
||||||
'system.menu.iconPicker.common': '常用图标',
|
'system.menu.iconPicker.common': '常用图标',
|
||||||
'system.menu.iconPicker.all': '全部图标',
|
'system.menu.iconPicker.all': '全部图标',
|
||||||
|
'system.menu.iconPicker.allHint': '也可直接搜索名称',
|
||||||
'system.menu.iconPicker.searchResult': '搜索结果',
|
'system.menu.iconPicker.searchResult': '搜索结果',
|
||||||
'system.menu.iconPicker.empty': '未找到匹配图标',
|
'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 角色默认拥有全部菜单与按钮权限,无需分配',
|
'system.role.permission.adminAllHint': 'Admin 角色默认拥有全部菜单与按钮权限,无需分配',
|
||||||
@ -6820,6 +6830,7 @@ export default {
|
|||||||
...localeWorkplace,
|
...localeWorkplace,
|
||||||
|
|
||||||
...localeMonitor,
|
...localeMonitor,
|
||||||
|
...localeEventBus,
|
||||||
...marketingZh,
|
...marketingZh,
|
||||||
...kbZh,
|
...kbZh,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -32,6 +32,38 @@ const DASHBOARD: AppRouteRecordRaw = {
|
|||||||
permission: 'Home.Monitor',
|
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
|
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
|
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
|
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> = {
|
const ORDER_EXTRA_FIELD_MAP: Record<string, FieldMapEntry> = {
|
||||||
orderStatus: { field: 'orderStatus', options: { multiAsIn: true } },
|
orderStatus: { field: 'orderStatus', options: { multiAsIn: true } },
|
||||||
|
previousOrderStatus: { field: 'previousOrderStatus', options: { multiAsIn: true } },
|
||||||
|
handleStatus: { field: 'handleStatus', options: { multiAsIn: true } },
|
||||||
fulfillmentType: { field: 'fulfillmentType', options: { multiAsIn: true } },
|
fulfillmentType: { field: 'fulfillmentType', options: { multiAsIn: true } },
|
||||||
|
stockBindStatus: { field: 'stockBindStatus', options: { multiAsIn: true } },
|
||||||
|
fulfillProgress: { field: 'fulfillProgress', options: { multiAsIn: true } },
|
||||||
shippingMethod: 'productCode',
|
shippingMethod: 'productCode',
|
||||||
serviceChannel: 'channelCode',
|
serviceChannel: 'channelCode',
|
||||||
isPaid: { field: 'isPaid', options: { multiAsIn: true } },
|
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 } },
|
receivableAmount: { field: 'payableAmount', options: { asNumber: true } },
|
||||||
payableAmount: { field: 'payableAmount', options: { asNumber: true } },
|
payableAmount: { field: 'payableAmount', options: { asNumber: true } },
|
||||||
predeclareFailReason: 'predeclareFailReason',
|
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) */
|
/** 子菜单默认图标(按 locale key) */
|
||||||
export const SUB_MENU_DEFAULT_ICONS: Record<string, string> = {
|
export const SUB_MENU_DEFAULT_ICONS: Record<string, string> = {
|
||||||
'menu.home': 'IconHome',
|
'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.customer': 'IconUserGroup',
|
||||||
'menu.user.profile': 'IconUser',
|
'menu.user.profile': 'IconUser',
|
||||||
'menu.user.recharge': 'IconGift',
|
'menu.user.recharge': 'IconGift',
|
||||||
@ -45,7 +49,7 @@ export const SUB_MENU_DEFAULT_ICONS: Record<string, string> = {
|
|||||||
'menu.track.trackOrder': 'IconList',
|
'menu.track.trackOrder': 'IconList',
|
||||||
'menu.track.warningOrder': 'IconExclamationCircle',
|
'menu.track.warningOrder': 'IconExclamationCircle',
|
||||||
'menu.track.trackState': 'IconCheckCircle',
|
'menu.track.trackState': 'IconCheckCircle',
|
||||||
'menu.track.trackFlag': 'IconFlag',
|
'menu.track.trackFlag': 'IconBookmark',
|
||||||
'menu.track.trackContent': 'IconMessage',
|
'menu.track.trackContent': 'IconMessage',
|
||||||
'menu.track.trackPull': 'IconDownload',
|
'menu.track.trackPull': 'IconDownload',
|
||||||
'menu.track.trackPush': 'IconUpload',
|
'menu.track.trackPush': 'IconUpload',
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* 列表状态标签:按信息强弱分档(低饱和墨色),对齐 OMS 主色 #007979。
|
* 列表状态标签:软底圆角(无硬描边),对齐 OMS 主色。
|
||||||
*
|
*
|
||||||
* 强弱(弱 → 强):
|
* 强弱(弱 → 强):
|
||||||
* - mute 弱信息:否 / 关闭 / 删除 / 无效
|
* - mute 弱信息:否 / 关闭 / 删除 / 无效
|
||||||
@ -61,6 +61,13 @@ export function getStatusTagTone(tone: StatusTagTone): StatusTagColors {
|
|||||||
|
|
||||||
export type StatusTagVariant = 'solid' | 'ghost'
|
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 {
|
function escapeAttr(text: string): string {
|
||||||
return String(text)
|
return String(text)
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@ -69,52 +76,55 @@ function escapeAttr(text: string): string {
|
|||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Vue / a-tag 内联样式 */
|
/** Vue / a-tag 内联样式(软底 + 左侧色条,无硬框) */
|
||||||
export function getStatusTagStyle(
|
export function getStatusTagStyle(
|
||||||
toneOrColors: StatusTagTone | StatusTagColors | null | undefined,
|
toneOrColors: StatusTagTone | StatusTagColors | null | undefined,
|
||||||
variant: StatusTagVariant = 'solid',
|
variant: StatusTagVariant = 'solid',
|
||||||
): Record<string, string> {
|
): Record<string, string> {
|
||||||
const colors =
|
const colors = resolveTagColors(toneOrColors)
|
||||||
!toneOrColors
|
|
||||||
? STATUS_TAG_TONES.neutral
|
|
||||||
: typeof toneOrColors === 'string'
|
|
||||||
? STATUS_TAG_TONES[toneOrColors]
|
|
||||||
: toneOrColors
|
|
||||||
if (variant === 'ghost') {
|
if (variant === 'ghost') {
|
||||||
return {
|
return {
|
||||||
color: colors.tagColor,
|
color: colors.tagColor,
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
border: `1px solid ${colors.tagBorder}`,
|
border: `1px solid ${colors.tagBorder}`,
|
||||||
borderRadius: '3px',
|
borderRadius: '6px',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
|
fontSize: '12px',
|
||||||
|
lineHeight: '20px',
|
||||||
|
padding: '0 8px',
|
||||||
|
display: 'inline-flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '6px',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
color: colors.tagColor,
|
color: colors.tagColor,
|
||||||
background: colors.tagBgColor,
|
background: colors.tagBgColor,
|
||||||
border: `1px solid ${colors.tagBorder}`,
|
border: 'none',
|
||||||
borderRadius: '3px',
|
borderRadius: '6px',
|
||||||
|
boxShadow: `inset 3px 0 0 ${colors.tagColor}`,
|
||||||
fontWeight: '500',
|
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(
|
export function buildStatusTagHtml(
|
||||||
text: string,
|
text: string,
|
||||||
toneOrColors: StatusTagTone | StatusTagColors | null | undefined = 'neutral',
|
toneOrColors: StatusTagTone | StatusTagColors | null | undefined = 'neutral',
|
||||||
variant: StatusTagVariant = 'solid',
|
variant: StatusTagVariant = 'solid',
|
||||||
): string {
|
): string {
|
||||||
const colors =
|
const colors = resolveTagColors(toneOrColors)
|
||||||
!toneOrColors
|
|
||||||
? STATUS_TAG_TONES.neutral
|
|
||||||
: typeof toneOrColors === 'string'
|
|
||||||
? STATUS_TAG_TONES[toneOrColors]
|
|
||||||
: toneOrColors
|
|
||||||
const safe = escapeAttr(text)
|
const safe = escapeAttr(text)
|
||||||
if (variant === 'ghost') {
|
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 更醒目) */
|
/** 布尔「是」专用高亮绿(比状态档 ok 更醒目) */
|
||||||
|
|||||||
@ -41,6 +41,8 @@ export interface SyncLevel3Options {
|
|||||||
export interface SyncMenuTreeResult {
|
export interface SyncMenuTreeResult {
|
||||||
mode: 'module' | 'page';
|
mode: 'module' | 'page';
|
||||||
level2Created: number;
|
level2Created: number;
|
||||||
|
/** 已有二级菜单补齐图标/权限码等字段 */
|
||||||
|
level2Updated: number;
|
||||||
level2Skipped: number;
|
level2Skipped: number;
|
||||||
level3Created: number;
|
level3Created: number;
|
||||||
level3Updated: number;
|
level3Updated: number;
|
||||||
@ -187,39 +189,53 @@ async function reloadMenuNode(menuId: string): Promise<MenuTreeDto | null> {
|
|||||||
async function ensurePageMenu(
|
async function ensurePageMenu(
|
||||||
parentMenu: MenuTreeDto,
|
parentMenu: MenuTreeDto,
|
||||||
pageDef: RoutePageMenuDef,
|
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);
|
const existing = findPageChildMenu(parentMenu, pageDef.route, pageDef.permissionCode);
|
||||||
if (existing?.id) {
|
if (existing?.id) {
|
||||||
// 已有二级菜单:补齐或纠正 permissionCode(Xxx.Query → Xxx),便于同步「查询」等三级按钮
|
// 已有二级菜单:补齐或纠正 permissionCode(Xxx.Query → Xxx);图标仅补空,不覆盖人工更换
|
||||||
const existingCode = existing.permissionCode?.trim() || '';
|
const existingCode = existing.permissionCode?.trim() || '';
|
||||||
const nextCode = pageDef.permissionCode?.trim() || '';
|
const nextCode = pageDef.permissionCode?.trim() || '';
|
||||||
const shouldPatchCode =
|
const shouldPatchCode =
|
||||||
!!nextCode &&
|
!!nextCode &&
|
||||||
(!existingCode ||
|
existingCode !== nextCode &&
|
||||||
existingCode === nextCode ||
|
(!existingCode || normalizeResourcePermissionCode(existingCode) === nextCode);
|
||||||
normalizeResourcePermissionCode(existingCode) === nextCode);
|
const existingIcon = existing.icon?.trim() || '';
|
||||||
if (shouldPatchCode && existingCode !== nextCode) {
|
const shouldPatchIcon = !existingIcon && !!nextIcon;
|
||||||
|
|
||||||
|
if (shouldPatchCode || shouldPatchIcon) {
|
||||||
|
const permissionCode = shouldPatchCode ? nextCode : existing.permissionCode;
|
||||||
|
const icon = existingIcon || nextIcon;
|
||||||
try {
|
try {
|
||||||
await MenuService.putRbacMenuUpdate(existing.id, {
|
await MenuService.putRbacMenuUpdate(existing.id, {
|
||||||
name: existing.name || pageDef.name,
|
name: existing.name || pageDef.name,
|
||||||
permissionCode: nextCode,
|
permissionCode,
|
||||||
icon: existing.icon || pageDef.icon,
|
icon,
|
||||||
route: existing.route || pageDef.route,
|
route: existing.route || pageDef.route,
|
||||||
component: existing.component || pageDef.component,
|
component: existing.component || pageDef.component,
|
||||||
order: existing.order ?? pageDef.order,
|
order: existing.order ?? pageDef.order,
|
||||||
isHidden: existing.isHidden ?? pageDef.isHidden,
|
isHidden: existing.isHidden ?? pageDef.isHidden,
|
||||||
isCached: existing.isCached ?? false,
|
isCached: existing.isCached ?? false,
|
||||||
description: existing.description || `同步二级菜单权限码: ${nextCode}`,
|
description:
|
||||||
|
existing.description ||
|
||||||
|
(shouldPatchIcon
|
||||||
|
? `同步二级菜单图标: ${icon}`
|
||||||
|
: `同步二级菜单权限码: ${permissionCode}`),
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
menu: { ...existing, permissionCode: nextCode },
|
menu: {
|
||||||
|
...existing,
|
||||||
|
permissionCode: permissionCode || existing.permissionCode,
|
||||||
|
icon: icon || existing.icon,
|
||||||
|
},
|
||||||
created: false,
|
created: false,
|
||||||
|
updated: true,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
// 更新失败时仍返回原节点,后续三级同步可能因无权限码跳过
|
// 更新失败时仍返回原节点,后续三级同步可能因无权限码跳过
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { menu: existing, created: false };
|
return { menu: existing, created: false, updated: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdMenu = await MenuService.postRbacMenuCreate({
|
const createdMenu = await MenuService.postRbacMenuCreate({
|
||||||
@ -227,7 +243,7 @@ async function ensurePageMenu(
|
|||||||
name: pageDef.name,
|
name: pageDef.name,
|
||||||
type: 1,
|
type: 1,
|
||||||
permissionCode: pageDef.permissionCode,
|
permissionCode: pageDef.permissionCode,
|
||||||
icon: pageDef.icon,
|
icon: nextIcon,
|
||||||
route: pageDef.route,
|
route: pageDef.route,
|
||||||
component: pageDef.component,
|
component: pageDef.component,
|
||||||
order: pageDef.order,
|
order: pageDef.order,
|
||||||
@ -243,7 +259,7 @@ async function ensurePageMenu(
|
|||||||
name: pageDef.name,
|
name: pageDef.name,
|
||||||
type: 1,
|
type: 1,
|
||||||
permissionCode: pageDef.permissionCode,
|
permissionCode: pageDef.permissionCode,
|
||||||
icon: pageDef.icon,
|
icon: nextIcon,
|
||||||
route: pageDef.route,
|
route: pageDef.route,
|
||||||
component: pageDef.component,
|
component: pageDef.component,
|
||||||
order: pageDef.order,
|
order: pageDef.order,
|
||||||
@ -251,6 +267,7 @@ async function ensurePageMenu(
|
|||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
created: true,
|
created: true,
|
||||||
|
updated: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -428,6 +445,7 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
|||||||
return {
|
return {
|
||||||
mode: 'page',
|
mode: 'page',
|
||||||
level2Created: 0,
|
level2Created: 0,
|
||||||
|
level2Updated: 0,
|
||||||
level2Skipped: 0,
|
level2Skipped: 0,
|
||||||
level3Created: result.created,
|
level3Created: result.created,
|
||||||
level3Updated: result.updated,
|
level3Updated: result.updated,
|
||||||
@ -448,6 +466,7 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
|||||||
}
|
}
|
||||||
|
|
||||||
let level2Created = 0;
|
let level2Created = 0;
|
||||||
|
let level2Updated = 0;
|
||||||
let level2Skipped = 0;
|
let level2Skipped = 0;
|
||||||
let level3Created = 0;
|
let level3Created = 0;
|
||||||
let level3Updated = 0;
|
let level3Updated = 0;
|
||||||
@ -460,10 +479,12 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
|||||||
for (const pageDef of pageDefs) {
|
for (const pageDef of pageDefs) {
|
||||||
let pageMenu: MenuTreeDto;
|
let pageMenu: MenuTreeDto;
|
||||||
let created = false;
|
let created = false;
|
||||||
|
let updated = false;
|
||||||
try {
|
try {
|
||||||
const ensured = await ensurePageMenu(currentParent, pageDef);
|
const ensured = await ensurePageMenu(currentParent, pageDef);
|
||||||
pageMenu = ensured.menu;
|
pageMenu = ensured.menu;
|
||||||
created = ensured.created;
|
created = ensured.created;
|
||||||
|
updated = ensured.updated;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
level3FailedPages.push(`${pageDef.name}: ${readSyncError(error)}`);
|
level3FailedPages.push(`${pageDef.name}: ${readSyncError(error)}`);
|
||||||
continue;
|
continue;
|
||||||
@ -474,6 +495,12 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
|||||||
if (refreshedParent) {
|
if (refreshedParent) {
|
||||||
currentParent = refreshedParent;
|
currentParent = refreshedParent;
|
||||||
}
|
}
|
||||||
|
} else if (updated) {
|
||||||
|
level2Updated += 1;
|
||||||
|
const refreshedParent = await reloadMenuNode(menu.id);
|
||||||
|
if (refreshedParent) {
|
||||||
|
currentParent = refreshedParent;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
level2Skipped += 1;
|
level2Skipped += 1;
|
||||||
}
|
}
|
||||||
@ -502,6 +529,7 @@ export async function syncMenuTree(menu: MenuTreeDto): Promise<SyncMenuTreeResul
|
|||||||
return {
|
return {
|
||||||
mode: 'module',
|
mode: 'module',
|
||||||
level2Created,
|
level2Created,
|
||||||
|
level2Updated,
|
||||||
level2Skipped,
|
level2Skipped,
|
||||||
level3Created,
|
level3Created,
|
||||||
level3Updated,
|
level3Updated,
|
||||||
|
|||||||
@ -3,6 +3,11 @@ import { appRoutes } from '@/router/routes';
|
|||||||
import type { AppRouteRecordRaw } from '@/router/routes/types';
|
import type { AppRouteRecordRaw } from '@/router/routes/types';
|
||||||
import type { MenuTreeDto } from '@/api/rbac';
|
import type { MenuTreeDto } from '@/api/rbac';
|
||||||
import { normalizeResourcePermissionCode } from '@/constants/pageButtonCatalog';
|
import { normalizeResourcePermissionCode } from '@/constants/pageButtonCatalog';
|
||||||
|
import {
|
||||||
|
PATH_TO_LOCALE_KEY,
|
||||||
|
SUB_MENU_DEFAULT_ICONS,
|
||||||
|
toPascalIconName,
|
||||||
|
} from '@/utils/menu-icon';
|
||||||
|
|
||||||
export interface RoutePageMenuDef {
|
export interface RoutePageMenuDef {
|
||||||
name: string;
|
name: string;
|
||||||
@ -52,6 +57,30 @@ export function resolveRouteMenuTitle(route: AppRouteRecordRaw): string {
|
|||||||
return String(route.name || route.path);
|
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 {
|
function toRouteRecord(route: AppRouteRecordRaw): AppRouteRecordRaw {
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
@ -105,7 +134,7 @@ export function getPageRoutesFromModule(moduleRoute: AppRouteRecordRaw): RoutePa
|
|||||||
permissionCode: rawPermission
|
permissionCode: rawPermission
|
||||||
? normalizeResourcePermissionCode(rawPermission) || rawPermission
|
? normalizeResourcePermissionCode(rawPermission) || rawPermission
|
||||||
: undefined,
|
: undefined,
|
||||||
icon: (child.meta.icon as string | undefined)?.trim() || undefined,
|
icon: resolveRouteMenuIcon(child, routePath),
|
||||||
order: (child.meta.order as number | undefined) ?? 0,
|
order: (child.meta.order as number | undefined) ?? 0,
|
||||||
isHidden: Boolean(child.meta.hideInMenu),
|
isHidden: Boolean(child.meta.hideInMenu),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export default {
|
|||||||
'h5Admin.login.title': 'Staff login',
|
'h5Admin.login.title': 'Staff login',
|
||||||
'h5Admin.login.brandName': 'Ops Admin',
|
'h5Admin.login.brandName': 'Ops Admin',
|
||||||
'h5Admin.login.brandHint': 'Sign in with your staff account',
|
'h5Admin.login.brandHint': 'Sign in with your staff account',
|
||||||
|
'h5Admin.login.backToSite': 'Back to website',
|
||||||
'h5Admin.login.accountPlaceholder': 'Account',
|
'h5Admin.login.accountPlaceholder': 'Account',
|
||||||
'h5Admin.login.passwordPlaceholder': 'Password',
|
'h5Admin.login.passwordPlaceholder': 'Password',
|
||||||
'h5Admin.login.submit': 'Sign in',
|
'h5Admin.login.submit': 'Sign in',
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export default {
|
|||||||
'h5Admin.login.title': '员工登录',
|
'h5Admin.login.title': '员工登录',
|
||||||
'h5Admin.login.brandName': '运营管理',
|
'h5Admin.login.brandName': '运营管理',
|
||||||
'h5Admin.login.brandHint': '使用员工账号登录,处理待办与运单',
|
'h5Admin.login.brandHint': '使用员工账号登录,处理待办与运单',
|
||||||
|
'h5Admin.login.backToSite': '返回官网',
|
||||||
'h5Admin.login.accountPlaceholder': '账号',
|
'h5Admin.login.accountPlaceholder': '账号',
|
||||||
'h5Admin.login.passwordPlaceholder': '密码',
|
'h5Admin.login.passwordPlaceholder': '密码',
|
||||||
'h5Admin.login.submit': '登录',
|
'h5Admin.login.submit': '登录',
|
||||||
|
|||||||
@ -9,10 +9,15 @@
|
|||||||
<main class="h5-login-main">
|
<main class="h5-login-main">
|
||||||
<section class="h5-login-card">
|
<section class="h5-login-card">
|
||||||
<header class="h5-login-card__brand">
|
<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" />
|
<img :src="logoImg" alt="" class="h5-login-card__logo" />
|
||||||
<h2 class="h5-login-card__title">{{ t('h5Admin.login.brandName') }}</h2>
|
<h2 class="h5-login-card__title">{{ t('h5Admin.login.brandName') }}</h2>
|
||||||
</div>
|
</button>
|
||||||
<p class="h5-login-card__hint">{{ t('h5Admin.login.brandHint') }}</p>
|
<p class="h5-login-card__hint">{{ t('h5Admin.login.brandHint') }}</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -78,6 +83,10 @@ const rbacUserStore = useRbacUserStore();
|
|||||||
|
|
||||||
const form = reactive({ account: '', password: '' });
|
const form = reactive({ account: '', password: '' });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
|
function goOfficialSite() {
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
const errorBox = ref('');
|
const errorBox = ref('');
|
||||||
|
|
||||||
function resolveRedirectPath() {
|
function resolveRedirectPath() {
|
||||||
@ -226,6 +235,12 @@ onMounted(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h5-login-card__logo {
|
.h5-login-card__logo {
|
||||||
|
|||||||
@ -315,6 +315,7 @@ export default {
|
|||||||
'h5.login.brand': 'IDE Customer Center',
|
'h5.login.brand': 'IDE Customer Center',
|
||||||
'h5.login.brandName': 'IDE',
|
'h5.login.brandName': 'IDE',
|
||||||
'h5.login.brandHint': 'Welcome to your IDE portal',
|
'h5.login.brandHint': 'Welcome to your IDE portal',
|
||||||
|
'h5.login.backToSite': 'Back to website',
|
||||||
'h5.login.tab.wechat': 'WeChat',
|
'h5.login.tab.wechat': 'WeChat',
|
||||||
'h5.login.tab.password': 'Account',
|
'h5.login.tab.password': 'Account',
|
||||||
'h5.login.switchPassword': 'Sign in with password',
|
'h5.login.switchPassword': 'Sign in with password',
|
||||||
|
|||||||
@ -314,6 +314,7 @@ export default {
|
|||||||
'h5.login.brand': 'IDE客户中心',
|
'h5.login.brand': 'IDE客户中心',
|
||||||
'h5.login.brandName': 'IDE',
|
'h5.login.brandName': 'IDE',
|
||||||
'h5.login.brandHint': '欢迎登录 IDE 客户门户',
|
'h5.login.brandHint': '欢迎登录 IDE 客户门户',
|
||||||
|
'h5.login.backToSite': '返回官网',
|
||||||
'h5.login.tab.wechat': '微信登录',
|
'h5.login.tab.wechat': '微信登录',
|
||||||
'h5.login.tab.password': '账号登录',
|
'h5.login.tab.password': '账号登录',
|
||||||
'h5.login.switchPassword': '账号密码登录',
|
'h5.login.switchPassword': '账号密码登录',
|
||||||
|
|||||||
@ -24,10 +24,15 @@
|
|||||||
<main class="h5-login-main">
|
<main class="h5-login-main">
|
||||||
<section class="h5-login-card">
|
<section class="h5-login-card">
|
||||||
<header class="h5-login-card__brand">
|
<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" />
|
<img :src="logoImg" alt="" class="h5-login-card__logo" />
|
||||||
<h2 class="h5-login-card__title">{{ t('h5.login.brandName') }}</h2>
|
<h2 class="h5-login-card__title">{{ t('h5.login.brandName') }}</h2>
|
||||||
</div>
|
</button>
|
||||||
<p class="h5-login-card__hint">{{ t('h5.login.brandHint') }}</p>
|
<p class="h5-login-card__hint">{{ t('h5.login.brandHint') }}</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@ -175,6 +180,10 @@ function goBack() {
|
|||||||
router.replace('/h5/orders');
|
router.replace('/h5/orders');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goOfficialSite() {
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
function goRegister() {
|
function goRegister() {
|
||||||
if (!isWeChatBrowser()) {
|
if (!isWeChatBrowser()) {
|
||||||
Message.warning(t('h5.login.openInWechat'));
|
Message.warning(t('h5.login.openInWechat'));
|
||||||
@ -463,6 +472,12 @@ onMounted(async () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h5-login-card__logo {
|
.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;
|
display: block;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform-origin: 50% 50%;
|
transform-origin: 50% 50%;
|
||||||
transform: translate3d(100px, 96px, 0) scale(4.1);
|
transform: translate3d(100px, 96px, 0) scale(2.73);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -761,8 +761,16 @@
|
|||||||
'clientNo',
|
'clientNo',
|
||||||
'trackNo',
|
'trackNo',
|
||||||
'orderStatus',
|
'orderStatus',
|
||||||
|
'previousOrderStatus',
|
||||||
|
'handleStatus',
|
||||||
|
'fulfillmentType',
|
||||||
|
'stockBindStatus',
|
||||||
|
'fulfillProgress',
|
||||||
'shippingMethod',
|
'shippingMethod',
|
||||||
'isPaid',
|
'isPaid',
|
||||||
|
'isIntercept',
|
||||||
|
'isRemote',
|
||||||
|
'isCod',
|
||||||
'receivableAmount',
|
'receivableAmount',
|
||||||
'predeclareFailReason',
|
'predeclareFailReason',
|
||||||
'payTime',
|
'payTime',
|
||||||
@ -1621,10 +1629,30 @@
|
|||||||
const listColumnCatalog = computed(() => filterMyOrdersColumns(MY_ORDERS_COLUMN_CATALOG));
|
const listColumnCatalog = computed(() => filterMyOrdersColumns(MY_ORDERS_COLUMN_CATALOG));
|
||||||
|
|
||||||
const hotColumns = computed(() => {
|
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) => ({
|
const statusOptions = ORDER_STATUS_LIST.map((meta) => ({
|
||||||
label: t(meta.labelKey),
|
label: t(meta.labelKey),
|
||||||
value: meta.value,
|
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([
|
const numberKeys = new Set([
|
||||||
'waybillNo',
|
'waybillNo',
|
||||||
'customerOrderNo',
|
'customerOrderNo',
|
||||||
@ -1633,6 +1661,11 @@
|
|||||||
'clientNo',
|
'clientNo',
|
||||||
'trackNo',
|
'trackNo',
|
||||||
]);
|
]);
|
||||||
|
const enumFilter = (options: Array<{ label: string; value: string }>) => ({
|
||||||
|
filterType: 'enum' as const,
|
||||||
|
headerMode: 'icon' as const,
|
||||||
|
filterOptions: options,
|
||||||
|
});
|
||||||
const base = withServerFilterColumns(
|
const base = withServerFilterColumns(
|
||||||
buildHotColumns(listColumnCatalog.value, activeColumnKeys.value, t, te),
|
buildHotColumns(listColumnCatalog.value, activeColumnKeys.value, t, te),
|
||||||
ORDER_SERVER_FILTER_KEYS,
|
ORDER_SERVER_FILTER_KEYS,
|
||||||
@ -1648,22 +1681,23 @@
|
|||||||
|| col.key === 'receiveTime' || col.key === 'signTime') {
|
|| col.key === 'receiveTime' || col.key === 'signTime') {
|
||||||
return { filterType: 'date' as const, headerMode: 'icon' as const };
|
return { filterType: 'date' as const, headerMode: 'icon' as const };
|
||||||
}
|
}
|
||||||
if (col.key === 'orderStatus') {
|
if (col.key === 'orderStatus' || col.key === 'previousOrderStatus') {
|
||||||
return {
|
return enumFilter(statusOptions);
|
||||||
filterType: 'enum' as const,
|
|
||||||
headerMode: 'icon' as const,
|
|
||||||
filterOptions: statusOptions,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if (col.key === 'isPaid') {
|
if (col.key === 'handleStatus') {
|
||||||
return {
|
return enumFilter(handleStatusOptions);
|
||||||
filterType: 'enum' as const,
|
}
|
||||||
headerMode: 'icon' as const,
|
if (col.key === 'fulfillmentType') {
|
||||||
filterOptions: [
|
return enumFilter(fulfillmentTypeOptions);
|
||||||
{ label: '已支付', value: '1' },
|
}
|
||||||
{ label: '已支付', value: '1' },
|
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') {
|
if (col.key === 'receivableAmount') {
|
||||||
return { filterType: 'number' as const, headerMode: 'icon' as const };
|
return { filterType: 'number' as const, headerMode: 'icon' as const };
|
||||||
|
|||||||
@ -82,6 +82,7 @@
|
|||||||
import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState';
|
import { useHiprintClientState } from '@/utils/hiprint/hiprintClientState';
|
||||||
import {
|
import {
|
||||||
ORDER_STATUS_TABS,
|
ORDER_STATUS_TABS,
|
||||||
|
ORDER_STATUS_LIST,
|
||||||
ORDER_STATUS_ALL,
|
ORDER_STATUS_ALL,
|
||||||
resolveOrderStatusCode,
|
resolveOrderStatusCode,
|
||||||
isOrderDeletable,
|
isOrderDeletable,
|
||||||
@ -101,6 +102,7 @@
|
|||||||
getOrderHandleStatusMeta,
|
getOrderHandleStatusMeta,
|
||||||
type OrderHandleStatusCode,
|
type OrderHandleStatusCode,
|
||||||
} from '@/constants/orderHandleStatus';
|
} from '@/constants/orderHandleStatus';
|
||||||
|
import { FULFILL_PROGRESS_TABS } from '@/constants/fulfillProgress';
|
||||||
import { hasPermission } from '@/utils/rbac-permission';
|
import { hasPermission } from '@/utils/rbac-permission';
|
||||||
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer';
|
import { useWarehouseAdminCustomer } from '@/composables/useWarehouseAdminCustomer';
|
||||||
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer';
|
import { renderWarehouseActionBtn, wrapWarehouseActions } from '@/utils/warehouseActionRenderer';
|
||||||
@ -709,10 +711,17 @@
|
|||||||
'clientNo',
|
'clientNo',
|
||||||
'trackNo',
|
'trackNo',
|
||||||
'orderStatus',
|
'orderStatus',
|
||||||
|
'previousOrderStatus',
|
||||||
|
'handleStatus',
|
||||||
'fulfillmentType',
|
'fulfillmentType',
|
||||||
|
'stockBindStatus',
|
||||||
|
'fulfillProgress',
|
||||||
'shippingMethod',
|
'shippingMethod',
|
||||||
'serviceChannel',
|
'serviceChannel',
|
||||||
'isPaid',
|
'isPaid',
|
||||||
|
'isIntercept',
|
||||||
|
'isRemote',
|
||||||
|
'isCod',
|
||||||
'receivableAmount',
|
'receivableAmount',
|
||||||
'predeclareFailReason',
|
'predeclareFailReason',
|
||||||
'allocationPendingReason',
|
'allocationPendingReason',
|
||||||
@ -1864,10 +1873,30 @@
|
|||||||
const activeColumnKeys = computed(() => getActiveScheme(columnConfigState.value).keys);
|
const activeColumnKeys = computed(() => getActiveScheme(columnConfigState.value).keys);
|
||||||
|
|
||||||
const hotColumns = computed(() => {
|
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 = [
|
const fulfillmentTypeOptions = [
|
||||||
{ label: t('order.waybillManage.filter.fulfillmentDirect'), value: 'direct' },
|
{ label: t('order.waybillManage.filter.fulfillmentDirect'), value: 'direct' },
|
||||||
{ label: t('order.waybillManage.filter.fulfillmentWarehouse'), value: 'warehouse' },
|
{ 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([
|
const numberKeys = new Set([
|
||||||
'waybillNo',
|
'waybillNo',
|
||||||
'customerOrderNo',
|
'customerOrderNo',
|
||||||
@ -1877,6 +1906,11 @@
|
|||||||
'clientNo',
|
'clientNo',
|
||||||
'trackNo',
|
'trackNo',
|
||||||
]);
|
]);
|
||||||
|
const enumFilter = (options: Array<{ label: string; value: string }>) => ({
|
||||||
|
filterType: 'enum' as const,
|
||||||
|
headerMode: 'icon' as const,
|
||||||
|
filterOptions: options,
|
||||||
|
});
|
||||||
return withSortableColumns(
|
return withSortableColumns(
|
||||||
withServerFilterColumns(
|
withServerFilterColumns(
|
||||||
buildHotColumns(columnCatalog.value, activeColumnKeys.value, t, te),
|
buildHotColumns(columnCatalog.value, activeColumnKeys.value, t, te),
|
||||||
@ -1901,12 +1935,23 @@
|
|||||||
if (col.key === 'receivableAmount') {
|
if (col.key === 'receivableAmount') {
|
||||||
return { filterType: 'number' as const, headerMode: 'icon' as const };
|
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') {
|
if (col.key === 'fulfillmentType') {
|
||||||
return {
|
return enumFilter(fulfillmentTypeOptions);
|
||||||
filterType: 'enum' as const,
|
}
|
||||||
headerMode: 'icon' as const,
|
if (col.key === 'stockBindStatus') {
|
||||||
filterOptions: fulfillmentTypeOptions,
|
return enumFilter(stockBindStatusOptions);
|
||||||
};
|
}
|
||||||
|
if (col.key === 'fulfillProgress') {
|
||||||
|
return enumFilter(fulfillProgressOptions);
|
||||||
|
}
|
||||||
|
if (['isPaid', 'isIntercept', 'isRemote', 'isCod'].includes(col.key)) {
|
||||||
|
return enumFilter(boolOptions);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -61,6 +61,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<TextType
|
<TextType
|
||||||
v-if="titleAnimate"
|
v-if="titleAnimate"
|
||||||
|
:key="shortTitle ? 'title-short' : 'title-full'"
|
||||||
as="span"
|
as="span"
|
||||||
class-name="ai-welcome-title__type"
|
class-name="ai-welcome-title__type"
|
||||||
:text="shortTitle ? copy.titleRestShort : copy.titleRest"
|
:text="shortTitle ? copy.titleRestShort : copy.titleRest"
|
||||||
@ -337,8 +338,13 @@
|
|||||||
const homeDests = ROUTE_DEST_PRESETS.filter((item) => item.id !== 'all');
|
const homeDests = ROUTE_DEST_PRESETS.filter((item) => item.id !== 'all');
|
||||||
const primaryDoors = HOME_DOORS.filter((d) => d.primary);
|
const primaryDoors = HOME_DOORS.filter((d) => d.primary);
|
||||||
const growthDoors = HOME_DOORS.filter((d) => !d.primary);
|
const growthDoors = HOME_DOORS.filter((d) => !d.primary);
|
||||||
const shortTitle = ref(false);
|
// 首屏即按视口决定短标题,避免挂载后再切导致 TextType 卡住只剩「IDE.」
|
||||||
const titleAnimate = ref(true);
|
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({
|
const draft = computed({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (value) => emit('update:modelValue', value),
|
set: (value) => emit('update:modelValue', value),
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
:disabled="busy"
|
:disabled="busy"
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
|
@focus="onFocus"
|
||||||
@keydown="onKeydown"
|
@keydown="onKeydown"
|
||||||
></textarea>
|
></textarea>
|
||||||
<button type="submit" class="ai-composer-send" :class="{ 'is-empty': !modelValue.trim() }" :disabled="busy" :aria-label="sendLabel">
|
<button type="submit" class="ai-composer-send" :class="{ 'is-empty': !modelValue.trim() }" :disabled="busy" :aria-label="sendLabel">
|
||||||
@ -56,6 +57,15 @@
|
|||||||
resize();
|
resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onFocus() {
|
||||||
|
const el = inputRef.value;
|
||||||
|
if (!el) return;
|
||||||
|
// iOS 键盘顶起后,把输入框滚进可视区
|
||||||
|
window.setTimeout(() => {
|
||||||
|
el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||||
|
}, 80);
|
||||||
|
}
|
||||||
|
|
||||||
function onKeydown(event: KeyboardEvent) {
|
function onKeydown(event: KeyboardEvent) {
|
||||||
if (event.key === 'Enter' && !event.shiftKey) {
|
if (event.key === 'Enter' && !event.shiftKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@ -9,8 +9,22 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<i class="mobile-nav-toggle mobile-nav-show si si-list" aria-hidden="true" @click="$emit('open-nav')"></i>
|
<button
|
||||||
<i class="mobile-nav-toggle mobile-nav-hide d-none si si-x" aria-hidden="true" @click="$emit('close-nav')"></i>
|
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">
|
<nav id="navbar" class="navbar">
|
||||||
<ul>
|
<ul>
|
||||||
<li ref="dockRef" class="site-nav-dock">
|
<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">
|
<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__head">
|
||||||
<div class="ai-route-card__titles">
|
<div class="ai-route-card__titles">
|
||||||
|
<h4 class="ai-route-card__name">{{ item.productName }}</h4>
|
||||||
<span
|
<span
|
||||||
v-if="item.productGroupName || item.productGroup"
|
v-if="item.productGroupName || item.productGroup"
|
||||||
class="ai-route-card__group"
|
class="ai-route-card__group"
|
||||||
>{{ item.productGroupName || item.productGroup }}</span>
|
>{{ item.productGroupName || item.productGroup }}</span>
|
||||||
<h4 class="ai-route-card__name">{{ item.productName }}</h4>
|
|
||||||
</div>
|
</div>
|
||||||
<p v-if="formatRouteTimeliness(item)" class="ai-route-card__eta">
|
<p v-if="formatRouteTimeliness(item)" class="ai-route-card__eta">
|
||||||
{{ formatRouteTimeliness(item) }} {{ copy.days }}
|
{{ formatRouteTimeliness(item) }} {{ copy.days }}
|
||||||
@ -145,21 +145,9 @@
|
|||||||
<span v-if="item.tradeTerms === 'DDP'"> · {{ copy.ddpHint }}</span>
|
<span v-if="item.tradeTerms === 'DDP'"> · {{ copy.ddpHint }}</span>
|
||||||
<span v-else-if="item.tradeTerms === 'DDU'"> · {{ copy.dduHint }}</span>
|
<span v-else-if="item.tradeTerms === 'DDU'"> · {{ copy.dduHint }}</span>
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p v-if="item.productDescription" class="ai-route-card__desc">
|
||||||
v-if="item.productDescription"
|
|
||||||
class="ai-route-card__desc"
|
|
||||||
:class="{ 'is-expanded': expanded[String(item.id)] }"
|
|
||||||
>
|
|
||||||
{{ item.productDescription }}
|
{{ item.productDescription }}
|
||||||
</p>
|
</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">
|
<div class="ai-route-card__actions">
|
||||||
<a class="ai-route-card__reg" :href="officialLoginUrl(destLabel(destFilter, lang) || undefined)">
|
<a class="ai-route-card__reg" :href="officialLoginUrl(destLabel(destFilter, lang) || undefined)">
|
||||||
{{ copy.register }}
|
{{ copy.register }}
|
||||||
@ -237,7 +225,6 @@
|
|||||||
const search = ref('');
|
const search = ref('');
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const bodyRef = ref<HTMLElement | null>(null);
|
const bodyRef = ref<HTMLElement | null>(null);
|
||||||
const expanded = ref<Record<string, boolean>>({});
|
|
||||||
|
|
||||||
const groupChips = computed(() => {
|
const groupChips = computed(() => {
|
||||||
const groups = (catalog.value?.groups || []).map((g) => ({ code: g.code, name: g.name || g.code }));
|
const groups = (catalog.value?.groups || []).map((g) => ({ code: g.code, name: g.name || g.code }));
|
||||||
@ -324,11 +311,6 @@
|
|||||||
resetPage();
|
resetPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleExpand(id: string | number) {
|
|
||||||
const key = String(id);
|
|
||||||
expanded.value = { ...expanded.value, [key]: !expanded.value[key] };
|
|
||||||
}
|
|
||||||
|
|
||||||
function askItem(item: RouteItem) {
|
function askItem(item: RouteItem) {
|
||||||
emit('ask', { text: buildRouteAskText(item, destFilter.value, lang.value), intentId: 'quote' });
|
emit('ask', { text: buildRouteAskText(item, destFilter.value, lang.value), intentId: 'quote' });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -123,7 +123,13 @@
|
|||||||
watch(
|
watch(
|
||||||
() => props.text,
|
() => props.text,
|
||||||
() => {
|
() => {
|
||||||
|
// text 变化时必须主动重启;若此时 displayedText 已是空串,依赖状态 watch 不会再触发
|
||||||
resetAnimation();
|
resetAnimation();
|
||||||
|
if (!isVisible.value) return;
|
||||||
|
clearTimeoutIfNeeded();
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
executeTypingAnimation();
|
||||||
|
}, props.initialDelay);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -208,16 +208,24 @@
|
|||||||
ensureOg('og:type', 'website');
|
ensureOg('og:type', 'website');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onMobileNavBackdrop(e: MouseEvent) {
|
||||||
|
const navEl = document.getElementById('navbar');
|
||||||
|
if (!navEl || e.target !== navEl) return;
|
||||||
|
closeMobileNav();
|
||||||
|
}
|
||||||
|
|
||||||
function openMobileNav() {
|
function openMobileNav() {
|
||||||
document.body.classList.add('mobile-nav-active');
|
document.body.classList.add('mobile-nav-active');
|
||||||
document.querySelectorAll('.mobile-nav-show').forEach((n) => n.classList.add('d-none'));
|
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.querySelectorAll('.mobile-nav-hide').forEach((n) => n.classList.remove('d-none'));
|
||||||
|
document.getElementById('navbar')?.addEventListener('click', onMobileNavBackdrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeMobileNav() {
|
function closeMobileNav() {
|
||||||
document.body.classList.remove('mobile-nav-active');
|
document.body.classList.remove('mobile-nav-active');
|
||||||
document.querySelectorAll('.mobile-nav-show').forEach((n) => n.classList.remove('d-none'));
|
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.querySelectorAll('.mobile-nav-hide').forEach((n) => n.classList.add('d-none'));
|
||||||
|
document.getElementById('navbar')?.removeEventListener('click', onMobileNavBackdrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncHash(id: string) {
|
function syncHash(id: string) {
|
||||||
@ -324,6 +332,10 @@
|
|||||||
|
|
||||||
function onKeydown(e: KeyboardEvent) {
|
function onKeydown(e: KeyboardEvent) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
if (document.body.classList.contains('mobile-nav-active')) {
|
||||||
|
closeMobileNav();
|
||||||
|
return;
|
||||||
|
}
|
||||||
closePanel();
|
closePanel();
|
||||||
historyOpen.value = false;
|
historyOpen.value = false;
|
||||||
wecomOpen.value = false;
|
wecomOpen.value = false;
|
||||||
@ -336,6 +348,14 @@
|
|||||||
document.documentElement.style.setProperty('--ai-header-h', `${height}px`);
|
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) => {
|
watch(panel, (id) => {
|
||||||
document.body.classList.toggle('site-panel-routes', id === 'routes');
|
document.body.classList.toggle('site-panel-routes', id === 'routes');
|
||||||
document.body.classList.toggle('site-panel-services', id === 'services');
|
document.body.classList.toggle('site-panel-services', id === 'services');
|
||||||
@ -349,7 +369,11 @@
|
|||||||
document.body.classList.add('command-home');
|
document.body.classList.add('command-home');
|
||||||
window.addEventListener('keydown', onKeydown);
|
window.addEventListener('keydown', onKeydown);
|
||||||
window.addEventListener('resize', syncHeaderOffset);
|
window.addEventListener('resize', syncHeaderOffset);
|
||||||
|
window.addEventListener('resize', syncVisualViewport);
|
||||||
|
window.visualViewport?.addEventListener('resize', syncVisualViewport);
|
||||||
|
window.visualViewport?.addEventListener('scroll', syncVisualViewport);
|
||||||
syncHeaderOffset();
|
syncHeaderOffset();
|
||||||
|
syncVisualViewport();
|
||||||
const hash = (window.location.hash || '').replace('#', '');
|
const hash = (window.location.hash || '').replace('#', '');
|
||||||
if (hash === '9610' || hash === 'services') openPanel('services');
|
if (hash === '9610' || hash === 'services') openPanel('services');
|
||||||
else if (NAV_PANELS.includes(hash as PanelId)) openPanel(hash as Exclude<PanelId, null>);
|
else if (NAV_PANELS.includes(hash as PanelId)) openPanel(hash as Exclude<PanelId, null>);
|
||||||
@ -359,6 +383,9 @@
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('keydown', onKeydown);
|
window.removeEventListener('keydown', onKeydown);
|
||||||
window.removeEventListener('resize', syncHeaderOffset);
|
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.body.classList.remove('site-panel-routes', 'site-panel-services');
|
||||||
document.documentElement.classList.remove('command-home', 'app-boot-site');
|
document.documentElement.classList.remove('command-home', 'app-boot-site');
|
||||||
closeMobileNav();
|
closeMobileNav();
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
--ai-glass: var(--ai-surface);
|
--ai-glass: var(--ai-surface);
|
||||||
--ai-content-max: 1200px;
|
--ai-content-max: 1200px;
|
||||||
--ai-guide-panel-w: 760px;
|
--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: 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));
|
--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 */
|
/* Soft fills ? cool tint aligned with GradientWaves */
|
||||||
@ -169,6 +169,27 @@ body.command-home #navbar .site-nav-dock a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1279px) {
|
@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 {
|
body.command-home .site-nav-dock {
|
||||||
display: contents;
|
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__glass,
|
||||||
body.command-home .site-nav-dock__slider,
|
body.command-home .site-nav-dock__slider,
|
||||||
body.command-home .site-nav-tools__glass {
|
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;
|
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 {
|
body.command-home #navbar .site-nav-dock__icon {
|
||||||
width: 20px;
|
width: 22px;
|
||||||
height: 20px;
|
height: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.command-home #navbar .site-nav-dock__label {
|
body.command-home #navbar .site-nav-dock__label {
|
||||||
font-size: 1rem;
|
font-size: inherit;
|
||||||
font-weight: 700;
|
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 {
|
body.command-home #ai-advisor-shell {
|
||||||
display: block;
|
display: block;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: var(--ai-vv-offset-top, 0px);
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: auto;
|
||||||
|
height: var(--ai-vv-height, 100%);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -1987,7 +2099,7 @@ body.command-home .ai-stage-map--eu {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
height: 180px;
|
||||||
padding: 14px 16px;
|
padding: 14px 16px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
@ -2009,7 +2121,7 @@ body.command-home .ai-stage-map--eu {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: 0;
|
gap: 6px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-route-card__group {
|
.ai-route-card__group {
|
||||||
@ -2021,13 +2133,6 @@ body.command-home .ai-stage-map--eu {
|
|||||||
color: var(--ai-muted);
|
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 {
|
.ai-route-card__name {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@ -2086,32 +2191,14 @@ body.command-home .ai-stage-map--eu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ai-route-card__desc {
|
.ai-route-card__desc {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
color: var(--ai-text-3);
|
color: var(--ai-text-3);
|
||||||
display: -webkit-box;
|
overflow-y: auto;
|
||||||
-webkit-line-clamp: 2;
|
scrollbar-width: thin;
|
||||||
-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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-route-card__actions {
|
.ai-route-card__actions {
|
||||||
@ -4310,8 +4397,19 @@ body.command-home #navbar .site-nav-avatar:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
:root {
|
||||||
|
--ai-header-h: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.command-home #header {
|
||||||
|
padding-top: env(safe-area-inset-top, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
.ai-advisor-layout {
|
.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 {
|
.ai-routes-panel {
|
||||||
@ -4364,27 +4462,136 @@ body.command-home #navbar .site-nav-avatar:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ai-welcome__stack {
|
.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) {
|
.ai-welcome-stats--compact .ai-welcome-stats__item:nth-child(n + 3) {
|
||||||
display: none;
|
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 {
|
.ai-intent-grid--primary {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-intent-grid--primary .ai-intent-door:last-child {
|
.ai-intent-grid--primary .ai-intent-door:last-child {
|
||||||
grid-column: auto;
|
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 {
|
.ai-intent-grid--growth::-webkit-scrollbar {
|
||||||
flex: 0 1 auto;
|
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 {
|
.ai-trust-icp {
|
||||||
@ -4392,33 +4599,125 @@ body.command-home #navbar .site-nav-avatar:hover {
|
|||||||
font-size: 0.66rem;
|
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 {
|
.ai-welcome__deck {
|
||||||
padding: 14px 8px 12px;
|
padding: 8px 4px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-welcome__command {
|
.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 {
|
.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 {
|
.ai-welcome-compose-panel {
|
||||||
font-size: 0.84rem;
|
padding: 12px 12px 10px;
|
||||||
white-space: nowrap;
|
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;
|
overflow-x: auto;
|
||||||
max-width: 94vw;
|
padding-bottom: 2px;
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
scrollbar-width: none;
|
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;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-trust-bar {
|
.ai-home-dest {
|
||||||
gap: 8px 14px;
|
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 {
|
.ai-new-chat-tip {
|
||||||
@ -4428,20 +4727,25 @@ body.command-home #navbar .site-nav-avatar:hover {
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-composer {
|
.ai-composer,
|
||||||
min-height: 76px;
|
.ai-composer-slot--welcome .ai-composer {
|
||||||
padding: 12px 12px 12px 18px;
|
min-height: 56px;
|
||||||
border-radius: 26px;
|
padding: 10px 10px 10px 16px;
|
||||||
|
border-radius: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-composer-input {
|
.ai-composer-input {
|
||||||
min-height: 48px;
|
min-height: 44px;
|
||||||
font-size: 1rem;
|
font-size: 16px; /* iOS 避免自动放大 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-composer-send {
|
.ai-composer-send {
|
||||||
width: 46px;
|
width: 44px;
|
||||||
height: 46px;
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-composer-dock {
|
||||||
|
padding-bottom: max(10px, env(safe-area-inset-bottom, 0px));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -184,24 +184,37 @@ body.command-home {
|
|||||||
color: #fff;
|
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 {
|
.mobile-nav-show {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
margin-right: 10px;
|
margin-right: 2px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 28px;
|
|
||||||
line-height: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-nav-hide {
|
.mobile-nav-hide {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: max(16px, env(safe-area-inset-top, 0px));
|
||||||
right: 20px;
|
right: max(14px, env(safe-area-inset-right, 0px));
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 32px;
|
|
||||||
line-height: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-nav-active {
|
.mobile-nav-active {
|
||||||
|
|||||||
@ -474,6 +474,7 @@ function handleSyncMenu(record: MenuTreeDto) {
|
|||||||
const result = await syncMenuTree(record)
|
const result = await syncMenuTree(record)
|
||||||
const hasChanges =
|
const hasChanges =
|
||||||
result.level2Created > 0 ||
|
result.level2Created > 0 ||
|
||||||
|
result.level2Updated > 0 ||
|
||||||
result.level3Created > 0 ||
|
result.level3Created > 0 ||
|
||||||
result.level3Updated > 0 ||
|
result.level3Updated > 0 ||
|
||||||
result.level3Deleted > 0
|
result.level3Deleted > 0
|
||||||
@ -483,6 +484,7 @@ function handleSyncMenu(record: MenuTreeDto) {
|
|||||||
Message.success(
|
Message.success(
|
||||||
t('system.menu.message.syncModuleSuccess', {
|
t('system.menu.message.syncModuleSuccess', {
|
||||||
level2Created: result.level2Created,
|
level2Created: result.level2Created,
|
||||||
|
level2Updated: result.level2Updated,
|
||||||
level2Skipped: result.level2Skipped,
|
level2Skipped: result.level2Skipped,
|
||||||
level3Created: result.level3Created,
|
level3Created: result.level3Created,
|
||||||
level3Updated: result.level3Updated,
|
level3Updated: result.level3Updated,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user