??????????????????????????????? H5/????? Co-authored-by: Cursor <cursoragent@cursor.com>
839 lines
24 KiB
Vue
839 lines
24 KiB
Vue
<template>
|
|
<MarketingPageShell :tip="t('marketing.memberLevel.pageTip')">
|
|
<div class="table-toolbar">
|
|
<div class="toolbar-left">
|
|
<a-input
|
|
v-model="keyword"
|
|
allow-clear
|
|
class="mkt-filter-search"
|
|
:placeholder="t('marketing.memberLevel.searchPlaceholder')"
|
|
@press-enter="handleSearch"
|
|
@clear="handleSearch"
|
|
/>
|
|
<a-button type="outline" @click="handleSearch">
|
|
<template #icon><icon-search /></template>
|
|
{{ t('common.search') }}
|
|
</a-button>
|
|
<a-button type="primary" @click="openDrawer()">
|
|
<template #icon><icon-plus /></template>
|
|
{{ t('common.add') }}
|
|
</a-button>
|
|
</div>
|
|
<div class="toolbar-right">
|
|
<a-space :size="8">
|
|
<a-button @click="loadList">
|
|
<template #icon><icon-refresh /></template>
|
|
</a-button>
|
|
<PaginationPro
|
|
:total-count="totalCount"
|
|
v-model:current-page="currentPage"
|
|
v-model:page-size="pageSize"
|
|
@page-change="loadList"
|
|
@page-size-change="loadList"
|
|
/>
|
|
</a-space>
|
|
</div>
|
|
</div>
|
|
|
|
<a-spin :loading="listLoading" class="list-spin">
|
|
<div ref="tableContainerRef" class="list-wrap">
|
|
<HandsontablePro
|
|
:columns="hotColumns"
|
|
:data="tableRows"
|
|
:total="totalCount"
|
|
:current-page="currentPage"
|
|
:page-size="pageSize"
|
|
:show-action-column="true"
|
|
:action-column-width="100"
|
|
:action-renderer="actionRenderer"
|
|
:stretch-columns="true"
|
|
:height="handsontableHeight"
|
|
:read-only="true"
|
|
@action-click="handleActionClick"
|
|
/>
|
|
</div>
|
|
</a-spin>
|
|
|
|
<a-drawer
|
|
v-model:visible="drawerVisible"
|
|
:width="560"
|
|
unmount-on-close
|
|
:esc-to-close="true"
|
|
:mask-closable="!drawerLoading"
|
|
class="member-level-drawer"
|
|
:footer="false"
|
|
@cancel="handleDrawerCancel"
|
|
>
|
|
<div class="ml-drawer">
|
|
<header class="ml-drawer__header">
|
|
<div class="ml-drawer__title-row">
|
|
<div>
|
|
<p class="ml-drawer__eyebrow">{{ t('menu.marketing.memberLevel') }}</p>
|
|
<h3 class="ml-drawer__title">
|
|
{{
|
|
editingId
|
|
? t('marketing.memberLevel.modal.edit')
|
|
: t('marketing.memberLevel.modal.add')
|
|
}}
|
|
</h3>
|
|
</div>
|
|
<button type="button" class="ml-drawer__close" @click="handleDrawerCancel">
|
|
<icon-close />
|
|
</button>
|
|
</div>
|
|
<p class="ml-drawer__desc">{{ t('marketing.memberLevel.modalTip') }}</p>
|
|
</header>
|
|
|
|
<div class="ml-drawer__body">
|
|
<section class="ml-preview" :class="{ 'is-disabled': formState.status === 1 }">
|
|
<div class="ml-preview__badge">
|
|
<span class="ml-preview__dot" />
|
|
{{
|
|
formState.status === 2
|
|
? t('common.enabled')
|
|
: t('common.disabled')
|
|
}}
|
|
</div>
|
|
<div class="ml-preview__main">
|
|
<div class="ml-preview__icon" aria-hidden="true">
|
|
<img v-if="formState.iconUrl" :src="formState.iconUrl" alt="" />
|
|
<span v-else>{{ previewInitial }}</span>
|
|
</div>
|
|
<div class="ml-preview__meta">
|
|
<h4 class="ml-preview__name">
|
|
{{ formState.levelName || t('marketing.memberLevel.preview.nameFallback') }}
|
|
</h4>
|
|
<p class="ml-preview__code">
|
|
{{ formState.levelCode || t('marketing.memberLevel.preview.codeFallback') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="ml-preview__stats">
|
|
<div class="ml-preview__stat">
|
|
<span class="ml-preview__stat-label">{{ t('marketing.memberLevel.form.minPoints') }}</span>
|
|
<span class="ml-preview__stat-value">{{ formState.minPoints ?? 0 }}</span>
|
|
</div>
|
|
<div class="ml-preview__stat">
|
|
<span class="ml-preview__stat-label">{{ t('marketing.memberLevel.form.extraDiscountRate') }}</span>
|
|
<span class="ml-preview__stat-value">{{ previewDiscountText }}</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<a-form
|
|
ref="formRef"
|
|
class="ml-form"
|
|
:model="formState"
|
|
:rules="formRules"
|
|
layout="vertical"
|
|
:label-col-props="{ span: 24 }"
|
|
:wrapper-col-props="{ span: 24 }"
|
|
>
|
|
<section class="ml-section">
|
|
<div class="ml-section__head">
|
|
<span class="ml-section__index">01</span>
|
|
<div>
|
|
<h5 class="ml-section__title">{{ t('marketing.memberLevel.section.basic') }}</h5>
|
|
<p class="ml-section__hint">{{ t('marketing.memberLevel.section.basicHint') }}</p>
|
|
</div>
|
|
</div>
|
|
<a-row :gutter="16">
|
|
<a-col :span="12">
|
|
<a-form-item
|
|
:label="t('marketing.memberLevel.form.levelCode')"
|
|
field="levelCode"
|
|
:tooltip="t('marketing.memberLevel.form.levelCodeExtra')"
|
|
>
|
|
<a-input
|
|
v-model="formState.levelCode"
|
|
:placeholder="t('marketing.memberLevel.form.levelCodePlaceholder')"
|
|
:max-length="32"
|
|
:disabled="!!editingId"
|
|
allow-clear
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-form-item :label="t('marketing.memberLevel.form.levelName')" field="levelName">
|
|
<a-input
|
|
v-model="formState.levelName"
|
|
:placeholder="t('marketing.memberLevel.form.levelNamePlaceholder')"
|
|
:max-length="64"
|
|
allow-clear
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-form-item :label="t('marketing.memberLevel.form.status')" field="status">
|
|
<a-radio-group v-model="formState.status" type="button" class="ml-status-group">
|
|
<a-radio :value="2">{{ t('common.enabled') }}</a-radio>
|
|
<a-radio :value="1">{{ t('common.disabled') }}</a-radio>
|
|
</a-radio-group>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-form-item
|
|
:label="t('marketing.memberLevel.form.sort')"
|
|
field="sort"
|
|
:tooltip="t('marketing.memberLevel.form.sortExtra')"
|
|
>
|
|
<a-input-number
|
|
v-model="formState.sort"
|
|
:min="0"
|
|
:precision="0"
|
|
style="width: 100%"
|
|
placeholder="0"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
</a-row>
|
|
</section>
|
|
|
|
<section class="ml-section">
|
|
<div class="ml-section__head">
|
|
<span class="ml-section__index">02</span>
|
|
<div>
|
|
<h5 class="ml-section__title">{{ t('marketing.memberLevel.section.pricing') }}</h5>
|
|
<p class="ml-section__hint">{{ t('marketing.memberLevel.section.pricingHint') }}</p>
|
|
</div>
|
|
</div>
|
|
<a-row :gutter="16">
|
|
<a-col :span="12">
|
|
<a-form-item
|
|
:label="t('marketing.memberLevel.form.minPoints')"
|
|
field="minPoints"
|
|
:tooltip="t('marketing.memberLevel.form.minPointsExtra')"
|
|
>
|
|
<a-input-number
|
|
v-model="formState.minPoints"
|
|
:min="0"
|
|
:precision="0"
|
|
style="width: 100%"
|
|
placeholder="0"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
<a-col :span="12">
|
|
<a-form-item
|
|
:label="t('marketing.memberLevel.form.extraDiscountRate')"
|
|
field="extraDiscountRate"
|
|
:tooltip="t('marketing.memberLevel.form.extraDiscountRateExtra')"
|
|
>
|
|
<a-input-number
|
|
v-model="formState.extraDiscountRate"
|
|
:min="0"
|
|
:max="0.99"
|
|
:step="0.01"
|
|
:precision="4"
|
|
style="width: 100%"
|
|
placeholder="0"
|
|
/>
|
|
</a-form-item>
|
|
</a-col>
|
|
</a-row>
|
|
</section>
|
|
|
|
<section class="ml-section">
|
|
<div class="ml-section__head">
|
|
<span class="ml-section__index">03</span>
|
|
<div>
|
|
<h5 class="ml-section__title">{{ t('marketing.memberLevel.section.display') }}</h5>
|
|
<p class="ml-section__hint">{{ t('marketing.memberLevel.section.displayHint') }}</p>
|
|
</div>
|
|
</div>
|
|
<a-form-item
|
|
:label="t('marketing.memberLevel.form.iconUrl')"
|
|
field="iconUrl"
|
|
:tooltip="t('marketing.memberLevel.form.iconUrlExtra')"
|
|
>
|
|
<a-input
|
|
v-model="formState.iconUrl"
|
|
:placeholder="t('marketing.memberLevel.form.iconUrlPlaceholder')"
|
|
:max-length="256"
|
|
allow-clear
|
|
/>
|
|
</a-form-item>
|
|
<a-form-item :label="t('marketing.memberLevel.form.benefitDesc')" field="benefitDesc">
|
|
<a-textarea
|
|
v-model="formState.benefitDesc"
|
|
:auto-size="{ minRows: 3, maxRows: 5 }"
|
|
:max-length="500"
|
|
show-word-limit
|
|
:placeholder="t('marketing.memberLevel.form.benefitDescPlaceholder')"
|
|
/>
|
|
</a-form-item>
|
|
</section>
|
|
</a-form>
|
|
</div>
|
|
|
|
<footer class="ml-drawer__footer">
|
|
<a-button :disabled="drawerLoading" @click="handleDrawerCancel">
|
|
{{ t('common.cancel') }}
|
|
</a-button>
|
|
<a-button type="primary" :loading="drawerLoading" @click="handleDrawerOk">
|
|
{{ t('common.save') }}
|
|
</a-button>
|
|
</footer>
|
|
</div>
|
|
</a-drawer>
|
|
</MarketingPageShell>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, reactive, ref, onMounted } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { Message, Modal } from '@arco-design/web-vue'
|
|
import HandsontablePro from '@/components/HandsontablePro/index.vue'
|
|
import PaginationPro from '@/components/PaginationPro/index.vue'
|
|
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
|
|
import { isEnableStatusEnabled, toEnableStatus } from '@/utils/enableStatus'
|
|
import {
|
|
createMemberLevel,
|
|
deleteMemberLevel,
|
|
getMemberLevelList,
|
|
updateMemberLevel,
|
|
} from '@/api/marketing/memberLevel'
|
|
import MarketingPageShell from '../components/MarketingPageShell.vue'
|
|
|
|
defineOptions({ name: 'MarketingMemberLevel' })
|
|
|
|
const { t } = useI18n()
|
|
const { tableContainerRef, tableHeight } = useTableContainerHeight()
|
|
const handsontableHeight = computed(() => Math.max(tableHeight.value || 0, 400))
|
|
|
|
interface MemberLevelRow {
|
|
id: number
|
|
levelCode: string
|
|
levelName: string
|
|
minPoints: number
|
|
extraDiscountRate: number
|
|
sort: number
|
|
iconUrl: string
|
|
status: boolean
|
|
statusName: string
|
|
customerCount: number
|
|
benefitDesc?: string
|
|
}
|
|
|
|
interface MemberLevelFormState {
|
|
levelCode: string
|
|
levelName: string
|
|
minPoints: number
|
|
extraDiscountRate: number
|
|
sort: number
|
|
iconUrl: string
|
|
status: number
|
|
benefitDesc: string
|
|
}
|
|
|
|
const tableRows = ref<MemberLevelRow[]>([])
|
|
const totalCount = ref(0)
|
|
const listLoading = ref(false)
|
|
const keyword = ref('')
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(30)
|
|
|
|
const drawerVisible = ref(false)
|
|
const drawerLoading = ref(false)
|
|
const editingId = ref<number | undefined>(undefined)
|
|
const formRef = ref()
|
|
|
|
const formState = reactive<MemberLevelFormState>(createEmptyForm())
|
|
|
|
const formRules = computed(() => ({
|
|
levelCode: [{ required: true, message: t('marketing.memberLevel.form.levelCodePlaceholder') }],
|
|
levelName: [{ required: true, message: t('marketing.memberLevel.form.levelNamePlaceholder') }],
|
|
minPoints: [{ required: true, message: t('marketing.memberLevel.form.minPointsExtra') }],
|
|
status: [{ required: true, message: t('marketing.memberLevel.form.status') }],
|
|
}))
|
|
|
|
const previewInitial = computed(() => {
|
|
const name = formState.levelName?.trim()
|
|
if (name) return name.slice(0, 1)
|
|
const code = formState.levelCode?.trim()
|
|
return code ? code.slice(0, 1).toUpperCase() : 'M'
|
|
})
|
|
|
|
const previewDiscountText = computed(() => {
|
|
const rate = Number(formState.extraDiscountRate || 0)
|
|
return `${(rate * 100).toFixed(2)}%`
|
|
})
|
|
|
|
function createEmptyForm(): MemberLevelFormState {
|
|
return {
|
|
levelCode: '',
|
|
levelName: '',
|
|
minPoints: 0,
|
|
extraDiscountRate: 0,
|
|
sort: 0,
|
|
iconUrl: '',
|
|
status: 2,
|
|
benefitDesc: '',
|
|
}
|
|
}
|
|
|
|
function resetForm(values?: Partial<MemberLevelFormState>) {
|
|
Object.assign(formState, createEmptyForm(), values || {})
|
|
}
|
|
|
|
function resolveStatusName(status: unknown, statusName?: string): string {
|
|
if (statusName === '启用' || statusName === '禁用') return statusName
|
|
return isEnableStatusEnabled(status) ? t('common.enabled') : t('common.disabled')
|
|
}
|
|
|
|
function mapRow(item: any): MemberLevelRow {
|
|
const status = item.status ?? item.Status ?? 2
|
|
return {
|
|
id: item.id ?? item.Id,
|
|
levelCode: item.levelCode ?? item.LevelCode ?? '',
|
|
levelName: item.levelName ?? item.LevelName ?? '',
|
|
minPoints: Number(item.minPoints ?? item.MinPoints ?? 0),
|
|
extraDiscountRate: Number(item.extraDiscountRate ?? item.ExtraDiscountRate ?? 0),
|
|
sort: Number(item.sort ?? item.Sort ?? 0),
|
|
iconUrl: String(item.iconUrl ?? item.IconUrl ?? ''),
|
|
status: isEnableStatusEnabled(status),
|
|
statusName: resolveStatusName(status, item.statusName ?? item.StatusName),
|
|
customerCount: Number(item.customerCount ?? item.CustomerCount ?? 0),
|
|
benefitDesc: item.benefitDesc ?? item.BenefitDesc ?? '',
|
|
}
|
|
}
|
|
|
|
const hotColumns = computed(() => [
|
|
{ key: 'levelCode', label: t('marketing.memberLevel.form.levelCode'), width: 100 },
|
|
{ key: 'levelName', label: t('marketing.memberLevel.form.levelName'), width: 120 },
|
|
{ key: 'minPoints', label: t('marketing.memberLevel.form.minPoints'), width: 100 },
|
|
{
|
|
key: 'extraDiscountRate',
|
|
label: t('marketing.memberLevel.form.extraDiscountRate'),
|
|
width: 100,
|
|
formatter: (v: number) => `${(Number(v) * 100).toFixed(2)}%`,
|
|
},
|
|
{ key: 'sort', label: t('marketing.memberLevel.form.sort'), width: 70 },
|
|
{ key: 'statusName', label: t('marketing.memberLevel.form.status'), width: 80 },
|
|
{ key: 'customerCount', label: t('marketing.memberLevel.form.customerCount'), width: 90 },
|
|
])
|
|
|
|
const iconEdit =
|
|
'<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" style="width:16px;height:16px"><path d="m30.48 19.038 5.733-5.734a1 1 0 0 0 0-1.414l-5.586-5.586a1 1 0 0 0-1.414 0l-5.734 5.734m7 7L15.763 33.754a1 1 0 0 1-.59.286l-6.048.708a1 1 0 0 1-1.113-1.069l.477-6.31a1 1 0 0 1 .29-.631l14.7-14.7m7 7-7-7M6 42h36"/></svg>'
|
|
const iconDelete =
|
|
'<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" style="width:16px;height:16px"><path d="M5 11h5.5m0 0v29a1 1 0 0 0 1 1h25a1 1 0 0 0 1-1V11m-27 0H16m21.5 0H43m-5.5 0H32m-16 0V7h16v4m-16 0h16M20 18v15m8-15v15"/></svg>'
|
|
|
|
function actionRenderer(_row: MemberLevelRow, rowIndex: number): string {
|
|
return `
|
|
<div class="action-btns" style="display:flex;gap:8px;align-items:center;justify-content:center;width:100%;">
|
|
<span data-action="edit" data-row-index="${rowIndex}" title="${t('common.edit')}"
|
|
style="cursor:pointer;color:var(--app-primary-color);display:inline-flex;">${iconEdit}</span>
|
|
<span data-action="delete" data-row-index="${rowIndex}" title="${t('common.delete')}"
|
|
style="cursor:pointer;color:#f53f3f;display:inline-flex;">${iconDelete}</span>
|
|
</div>
|
|
`
|
|
}
|
|
|
|
async function loadList() {
|
|
listLoading.value = true
|
|
try {
|
|
const { items, total } = await getMemberLevelList({
|
|
keyword: keyword.value.trim() || undefined,
|
|
page: currentPage.value,
|
|
pageSize: pageSize.value,
|
|
})
|
|
tableRows.value = (items || []).map(mapRow)
|
|
totalCount.value = Number(total || 0)
|
|
} catch (e: any) {
|
|
Message.error(e?.message || t('common.loadFailed'))
|
|
tableRows.value = []
|
|
totalCount.value = 0
|
|
} finally {
|
|
listLoading.value = false
|
|
}
|
|
}
|
|
|
|
function handleSearch() {
|
|
currentPage.value = 1
|
|
loadList()
|
|
}
|
|
|
|
function openDrawer(row?: MemberLevelRow) {
|
|
editingId.value = row?.id
|
|
if (row) {
|
|
resetForm({
|
|
levelCode: row.levelCode,
|
|
levelName: row.levelName,
|
|
minPoints: row.minPoints,
|
|
extraDiscountRate: row.extraDiscountRate,
|
|
sort: row.sort,
|
|
iconUrl: row.iconUrl || '',
|
|
status: toEnableStatus(row.status),
|
|
benefitDesc: row.benefitDesc || '',
|
|
})
|
|
} else {
|
|
resetForm()
|
|
}
|
|
drawerVisible.value = true
|
|
}
|
|
|
|
function handleDrawerCancel() {
|
|
if (drawerLoading.value) return
|
|
drawerVisible.value = false
|
|
editingId.value = undefined
|
|
}
|
|
|
|
function handleActionClick(row: MemberLevelRow, _rowIndex: number, action?: string) {
|
|
if (action === 'edit') openDrawer(row)
|
|
if (action === 'delete') handleDelete(row)
|
|
}
|
|
|
|
function handleDelete(row: MemberLevelRow) {
|
|
Modal.confirm({
|
|
title: t('common.deleteConfirm'),
|
|
content: t('marketing.memberLevel.deleteConfirm', {
|
|
name: row.levelName,
|
|
count: row.customerCount,
|
|
}),
|
|
onOk: async () => {
|
|
await deleteMemberLevel(row.id)
|
|
Message.success(t('common.deleteSuccess'))
|
|
await loadList()
|
|
},
|
|
})
|
|
}
|
|
|
|
async function handleDrawerOk() {
|
|
try {
|
|
await formRef.value?.validate()
|
|
} catch {
|
|
return
|
|
}
|
|
const levelCode = String(formState.levelCode || '').trim()
|
|
const levelName = String(formState.levelName || '').trim()
|
|
if (!levelCode || !levelName) {
|
|
Message.warning(t('marketing.memberLevel.form.requiredHint'))
|
|
return
|
|
}
|
|
drawerLoading.value = true
|
|
try {
|
|
const payload = {
|
|
id: editingId.value,
|
|
levelCode,
|
|
levelName,
|
|
minPoints: Number(formState.minPoints ?? 0),
|
|
extraDiscountRate: Number(formState.extraDiscountRate ?? 0),
|
|
sort: Number(formState.sort ?? 0),
|
|
iconUrl: String(formState.iconUrl || '').trim() || undefined,
|
|
status: Number(formState.status ?? 2),
|
|
benefitDesc: String(formState.benefitDesc || '').trim() || undefined,
|
|
}
|
|
if (editingId.value) {
|
|
await updateMemberLevel(payload)
|
|
} else {
|
|
await createMemberLevel(payload)
|
|
}
|
|
Message.success(t('common.saveSuccess'))
|
|
drawerVisible.value = false
|
|
editingId.value = undefined
|
|
await loadList()
|
|
} catch (e: any) {
|
|
Message.error(e?.message || t('common.saveFailed'))
|
|
} finally {
|
|
drawerLoading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => loadList())
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
@import '../styles/admin-shared.less';
|
|
|
|
.ml-drawer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
min-height: 0;
|
|
margin: -16px;
|
|
}
|
|
|
|
.ml-drawer__header {
|
|
flex-shrink: 0;
|
|
padding: 20px 24px 16px;
|
|
background:
|
|
linear-gradient(
|
|
135deg,
|
|
color-mix(in srgb, var(--app-primary-color) 12%, #fff) 0%,
|
|
#fff 58%
|
|
);
|
|
border-bottom: 1px solid var(--color-border-2, #e5e6eb);
|
|
}
|
|
|
|
.ml-drawer__title-row {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
|
|
.ml-drawer__eyebrow {
|
|
margin: 0 0 4px;
|
|
font-size: 12px;
|
|
letter-spacing: 0.04em;
|
|
color: var(--app-primary-color);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.ml-drawer__title {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
line-height: 1.35;
|
|
font-weight: 600;
|
|
color: var(--color-text-1, #1d2129);
|
|
}
|
|
|
|
.ml-drawer__desc {
|
|
margin: 10px 0 0;
|
|
font-size: 13px;
|
|
line-height: 1.55;
|
|
color: var(--color-text-2, #4e5969);
|
|
}
|
|
|
|
.ml-drawer__close {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
border: none;
|
|
border-radius: 8px;
|
|
background: rgba(0, 0, 0, 0.04);
|
|
color: var(--color-text-2, #4e5969);
|
|
cursor: pointer;
|
|
transition: background 0.2s ease, color 0.2s ease;
|
|
|
|
&:hover {
|
|
background: color-mix(in srgb, var(--app-primary-color) 12%, #fff);
|
|
color: var(--app-primary-color);
|
|
}
|
|
}
|
|
|
|
.ml-drawer__body {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: auto;
|
|
padding: 16px 24px 8px;
|
|
}
|
|
|
|
.ml-preview {
|
|
margin-bottom: 18px;
|
|
padding: 16px;
|
|
border-radius: 12px;
|
|
background:
|
|
radial-gradient(
|
|
120% 140% at 0% 0%,
|
|
color-mix(in srgb, #fff 28%, transparent),
|
|
transparent 55%
|
|
),
|
|
linear-gradient(
|
|
135deg,
|
|
color-mix(in srgb, var(--app-primary-color) 92%, #102020) 0%,
|
|
color-mix(in srgb, var(--app-primary-color) 62%, #1c3333) 100%
|
|
);
|
|
color: #fff;
|
|
box-shadow: 0 10px 28px color-mix(in srgb, var(--app-primary-color) 28%, transparent);
|
|
transition: opacity 0.2s ease;
|
|
|
|
&.is-disabled {
|
|
opacity: 0.72;
|
|
filter: grayscale(0.25);
|
|
}
|
|
}
|
|
|
|
.ml-preview__badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
margin-bottom: 12px;
|
|
padding: 2px 10px;
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
}
|
|
|
|
.ml-preview__dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background: #00b42a;
|
|
}
|
|
|
|
.ml-preview.is-disabled .ml-preview__dot {
|
|
background: #c9cdd4;
|
|
}
|
|
|
|
.ml-preview__main {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
.ml-preview__icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 12px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255, 255, 255, 0.14);
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
overflow: hidden;
|
|
|
|
img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
}
|
|
|
|
.ml-preview__name {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 650;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.ml-preview__code {
|
|
margin: 4px 0 0;
|
|
font-size: 12px;
|
|
opacity: 0.72;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.ml-preview__stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 8px;
|
|
}
|
|
|
|
.ml-preview__stat {
|
|
padding: 10px 10px;
|
|
border-radius: 8px;
|
|
background: rgba(255, 255, 255, 0.08);
|
|
min-width: 0;
|
|
}
|
|
|
|
.ml-preview__stat-label {
|
|
display: block;
|
|
font-size: 11px;
|
|
opacity: 0.7;
|
|
margin-bottom: 4px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.ml-preview__stat-value {
|
|
display: block;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.ml-section {
|
|
margin-bottom: 18px;
|
|
padding: 14px 14px 4px;
|
|
border-radius: 10px;
|
|
background: #f7f8fa;
|
|
border: 1px solid var(--color-border-2, #e5e6eb);
|
|
}
|
|
|
|
.ml-section__head {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: flex-start;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.ml-section__index {
|
|
flex-shrink: 0;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 8px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
color: var(--app-primary-color);
|
|
background: color-mix(in srgb, var(--app-primary-color) 14%, #fff);
|
|
}
|
|
|
|
.ml-section__title {
|
|
margin: 0;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--color-text-1, #1d2129);
|
|
}
|
|
|
|
.ml-section__hint {
|
|
margin: 2px 0 0;
|
|
font-size: 12px;
|
|
color: var(--color-text-3, #86909c);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.ml-form {
|
|
:deep(.arco-form-item) {
|
|
margin-bottom: 14px;
|
|
}
|
|
|
|
:deep(.arco-form-item-label) {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--color-text-1, #1d2129);
|
|
}
|
|
|
|
:deep(.arco-input-wrapper),
|
|
:deep(.arco-select-view-single),
|
|
:deep(.arco-textarea-wrapper),
|
|
:deep(.arco-input-number) {
|
|
border-radius: 6px;
|
|
}
|
|
}
|
|
|
|
.ml-status-group {
|
|
display: flex;
|
|
width: 100%;
|
|
|
|
:deep(.arco-radio-button) {
|
|
flex: 1;
|
|
text-align: center;
|
|
}
|
|
}
|
|
|
|
.ml-drawer__footer {
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
padding: 14px 24px 18px;
|
|
border-top: 1px solid var(--color-border-2, #e5e6eb);
|
|
background: #fff;
|
|
}
|
|
</style>
|
|
|
|
<style lang="less">
|
|
.member-level-drawer {
|
|
.arco-drawer-header {
|
|
display: none;
|
|
}
|
|
|
|
.arco-drawer-body {
|
|
padding: 16px;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
}
|
|
</style>
|