front/src/components/ContactService/index.vue
zzh 3642d650db
All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m59s
feat: 客户侧栏账户坞与运单发货后状态变更
侧栏改为 Cursor 式账户面板(仓库眼标预览、语言切换、假邮箱过滤),运单「更多」支持发货后四态变更且妥投成功仅可进入。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-26 11:46:32 +08:00

280 lines
6.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { computed, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { getWarehouseList } from '@/api/system/warehouse'
import { useCurrentWarehouseStore } from '@/store'
const props = withDefaults(
defineProps<{
variant?: 'navbar' | 'login' | 'sider'
/** 下拉位置;侧栏沉底用 rt */
dropdownPosition?: 'br' | 'bl' | 'tr' | 'tl' | 'rt' | 'rb'
/** 侧栏收起:窄宽展示缩略文案(无图标) */
compact?: boolean
/** 下拉触发;侧栏默认 click,顶栏 hover */
trigger?: 'hover' | 'click'
/** 侧栏展开:显示「发货仓库」语义前缀 */
showCaption?: boolean
contentClass?: string
}>(),
{
variant: 'navbar',
dropdownPosition: 'br',
compact: false,
trigger: undefined,
showCaption: false,
contentClass: '',
},
)
const { t } = useI18n()
const warehouseStore = useCurrentWarehouseStore()
const displayName = computed(
() =>
warehouseStore.warehouseName ||
t('nav.contactService.selectWarehouse'),
)
/** 收起时只露一字,避免无图标时空块 */
const labelText = computed(() => {
const name = displayName.value
if (props.variant === 'sider' && props.compact) {
return name.slice(0, 1) || '仓'
}
return name
})
const useDropdown = computed(() => props.variant === 'navbar' || props.variant === 'sider')
const dropdownTrigger = computed(() => {
if (props.trigger) return props.trigger
return props.variant === 'sider' ? 'click' : 'hover'
})
const showSiderCaption = computed(
() => props.showCaption && props.variant === 'sider' && !props.compact,
)
const contentClass = computed(() => props.contentClass || undefined)
const popupTranslate = computed<[number, number] | undefined>(() => {
const pos = props.dropdownPosition
return pos === 'tl' || pos === 'tr' ? [0, -6] : undefined
})
async function loadServiceDataList() {
try {
const { items } = await getWarehouseList({
page: 1,
pageSize: 100,
})
const list = (items ?? [])
.filter((row) => row.id != null)
.map((row) => ({
label: row.warehouseName || String(row.id),
value: Number(row.id),
}))
warehouseStore.setOptions(list)
} catch (error) {
console.error('Failed to load service warehouse list', error)
warehouseStore.setOptions([])
}
}
function onSelectWarehouse(id: number) {
warehouseStore.setWarehouseId(id)
}
onMounted(() => {
void loadServiceDataList()
})
</script>
<template>
<div
class="contact-service"
:class="[
`contact-service--${variant}`,
{ 'is-compact': compact },
]"
>
<a-dropdown
v-if="useDropdown"
:trigger="dropdownTrigger"
:position="dropdownPosition"
:content-class="contentClass"
:popup-translate="popupTranslate"
:popup-max-height="320"
>
<button
type="button"
class="contact-service__label"
:title="displayName"
:aria-label="`${t('nav.contactService.shipWarehouse')}: ${displayName}`"
>
<span v-if="showSiderCaption" class="contact-service__caption">
{{ t('nav.contactService.shipWarehouse') }}
</span>
<span class="contact-service__text">{{ labelText }}</span>
</button>
<template #content>
<a-doption
v-for="opt in warehouseStore.options"
:key="String(opt.value)"
:value="opt.value"
:selected="opt.value === warehouseStore.warehouseId"
@click="onSelectWarehouse(opt.value)"
>
{{ opt.label }}
</a-doption>
</template>
</a-dropdown>
<a-select
v-else
:model-value="warehouseStore.warehouseId"
class="contact-service__select"
:placeholder="t('nav.contactService.selectWarehouse')"
popup-container="body"
@change="(v: any) => warehouseStore.setWarehouseId(v)"
>
<a-option
v-for="opt in warehouseStore.options"
:key="String(opt.value)"
:value="opt.value"
>
{{ opt.label }}
</a-option>
</a-select>
</div>
</template>
<style lang="less" scoped>
.contact-service {
display: inline-flex;
align-items: center;
flex-shrink: 0;
min-width: 0;
&__select {
min-width: 120px;
}
&__text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&--navbar {
color: #fff;
.contact-service__label {
display: inline-flex;
align-items: center;
max-width: 180px;
height: 28px;
padding: 0 8px;
border: none;
border-radius: 6px;
background: transparent;
font-size: 13px;
line-height: 28px;
color: #fff;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
transition: background-color 0.15s ease, opacity 0.15s ease;
&:hover {
background-color: rgba(255, 255, 255, 0.12);
}
}
}
&--sider {
width: 100%;
.contact-service__label {
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 4px;
width: 100%;
height: 30px;
min-height: 30px;
padding: 0 8px;
border: none;
border-radius: 6px;
background: rgba(255, 255, 255, 0.55);
font-size: 12px;
font-weight: 400;
color: var(--color-text-1);
cursor: pointer;
text-align: left;
transition: background-color 0.14s ease, color 0.14s ease;
&:hover {
background: rgba(255, 255, 255, 0.92);
color: var(--app-primary-color, rgb(var(--primary-6)));
}
}
.contact-service__caption {
flex-shrink: 0;
font-size: 11px;
font-weight: 500;
color: var(--color-text-3);
&::after {
content: '·';
margin-left: 4px;
color: var(--color-text-4, #c9cdd4);
}
}
.contact-service__text {
min-width: 0;
flex: 1;
line-height: 1.2;
}
&.is-compact .contact-service__label {
justify-content: center;
align-items: center;
gap: 0;
padding: 0;
min-width: 36px;
width: 36px;
height: 28px;
min-height: 28px;
background: transparent;
font-size: 14px;
font-weight: 400;
text-align: center;
&:hover {
background: rgba(255, 255, 255, 0.85);
}
}
&.is-compact .contact-service__text {
flex: none;
width: 100%;
text-align: center;
line-height: 1;
}
}
&--login {
:deep(.arco-select-view) {
min-width: 120px;
height: 32px;
border-radius: 16px;
}
}
}
</style>