Files
geo/apps/admin-web/src/components/kol/KolPackageFormModal.vue
T

244 lines
6.4 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { DeleteOutlined, PictureOutlined } from '@ant-design/icons-vue'
import type { CreateKolPackageRequest } from '@geo/shared-types'
import { message } from 'ant-design-vue'
import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import CoverPickerModal from '@/components/CoverPickerModal.vue'
import { imagesApi, resolveApiURL } from '@/lib/api'
import { deriveCoverFileName } from '@/lib/cover-requirements'
const props = defineProps<{
open: boolean
initialValue?: Partial<CreateKolPackageRequest>
}>()
const emit = defineEmits<{
(e: 'update:open', value: boolean): void
(e: 'submit', value: CreateKolPackageRequest): void
}>()
const { t } = useI18n()
const coverPickerOpen = ref(false)
const formState = reactive<CreateKolPackageRequest>({
name: '',
description: '',
industry: '',
tags: [],
price_note: '',
cover_url: '',
})
watch(
() => props.open,
(isOpen) => {
if (isOpen) {
Object.assign(formState, {
name: props.initialValue?.name || '',
description: props.initialValue?.description || '',
industry: props.initialValue?.industry || '',
tags: [...(props.initialValue?.tags || [])],
price_note: props.initialValue?.price_note || '',
cover_url: props.initialValue?.cover_url || '',
})
return
}
coverPickerOpen.value = false
},
)
const coverPreviewUrl = computed(() => resolveApiURL(formState.cover_url))
const coverFileName = computed(() => deriveCoverFileName(formState.cover_url))
function handleOk() {
if (!formState.name.trim()) {
message.warning(t('kol.manage.packageForm.nameRequired'))
return
}
emit('submit', { ...formState, tags: [...formState.tags] })
emit('update:open', false)
}
function handleCancel() {
emit('update:open', false)
}
function handleRemoveCover() {
formState.cover_url = ''
}
function handleCoverConfirmed(payload: { url: string }) {
formState.cover_url = resolveApiURL(payload.url)
}
async function uploadCover(file: File) {
const uploaded = await imagesApi.upload(file)
return {
url: uploaded.url,
fileName: uploaded.name,
assetId: uploaded.id,
}
}
</script>
<template>
<a-modal
:open="open"
:title="initialValue ? t('kol.manage.editPackage') : t('kol.manage.createPackage')"
:ok-text="t('common.confirm')"
:cancel-text="t('common.cancel')"
@ok="handleOk"
@cancel="handleCancel"
>
<a-form layout="vertical">
<a-form-item :label="t('common.title')" required>
<a-input v-model:value="formState.name" :placeholder="t('common.inputPlease')" />
</a-form-item>
<a-form-item :label="t('common.description')">
<a-textarea v-model:value="formState.description" :rows="3" />
</a-form-item>
<a-form-item :label="t('kol.marketplace.filter.industry')">
<a-input v-model:value="formState.industry" />
</a-form-item>
<a-form-item :label="t('kol.manage.packageForm.tags')">
<a-select
v-model:value="formState.tags"
mode="tags"
style="width: 100%"
:placeholder="t('kol.manage.packageForm.tagsPlaceholder')"
/>
</a-form-item>
<a-form-item :label="t('kol.manage.packageForm.priceNote')">
<a-input
v-model:value="formState.price_note"
:placeholder="t('kol.manage.packageForm.priceNotePlaceholder')"
/>
</a-form-item>
<a-form-item :label="t('kol.manage.packageForm.cover')">
<div class="package-cover-field">
<button type="button" class="package-cover-picker" @click="coverPickerOpen = true">
<template v-if="coverPreviewUrl">
<img
:src="coverPreviewUrl"
:alt="t('kol.manage.packageForm.cover')"
class="package-cover-picker__image"
/>
</template>
<template v-else>
<div class="package-cover-picker__empty">
<PictureOutlined class="package-cover-picker__icon" />
<strong>{{ t('kol.manage.packageForm.selectCover') }}</strong>
<span>{{ t('kol.manage.packageForm.coverHint') }}</span>
</div>
</template>
</button>
<div class="package-cover-actions">
<a-button @click="coverPickerOpen = true">
{{
coverPreviewUrl
? t('kol.manage.packageForm.changeCover')
: t('kol.manage.packageForm.selectCover')
}}
</a-button>
<a-button v-if="coverPreviewUrl" danger @click="handleRemoveCover">
<template #icon><DeleteOutlined /></template>
{{ t('kol.manage.packageForm.removeCover') }}
</a-button>
</div>
</div>
</a-form-item>
</a-form>
</a-modal>
<CoverPickerModal
v-model:open="coverPickerOpen"
:article-id="null"
:platform-ids="[]"
:current-url="coverPreviewUrl"
:current-file-name="coverFileName"
:title="t('kol.manage.packageForm.coverPicker.title')"
:local-tab-label="t('kol.manage.packageForm.coverPicker.localTab')"
:empty-upload-title="t('kol.manage.packageForm.coverPicker.emptyTitle')"
:empty-upload-hint="t('kol.manage.packageForm.coverPicker.emptyHint')"
:upload-handler="uploadCover"
@confirmed="handleCoverConfirmed"
/>
</template>
<style scoped>
.package-cover-field {
display: flex;
flex-direction: column;
gap: 12px;
}
.package-cover-picker {
width: 100%;
min-height: 180px;
border: 1px dashed #d0d5dd;
border-radius: 12px;
padding: 0;
overflow: hidden;
background: #f8fafc;
cursor: pointer;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
}
.package-cover-picker:hover {
border-color: #1677ff;
box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.08);
}
.package-cover-picker__image {
display: block;
width: 100%;
height: 180px;
object-fit: cover;
}
.package-cover-picker__empty {
display: flex;
min-height: 180px;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 20px;
color: #475467;
text-align: center;
}
.package-cover-picker__empty strong {
color: #111827;
font-size: 15px;
font-weight: 700;
}
.package-cover-picker__empty span {
max-width: 320px;
font-size: 12px;
line-height: 1.6;
}
.package-cover-picker__icon {
font-size: 28px;
color: #1677ff;
}
.package-cover-actions {
display: flex;
gap: 8px;
}
</style>