|
@@ -0,0 +1,219 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="product-edit">
|
|
|
|
|
+ <van-nav-bar
|
|
|
|
|
+ :title="isEdit ? $t('merchant.product.editTitle') : $t('merchant.product.createTitle')"
|
|
|
|
|
+ left-arrow
|
|
|
|
|
+ @click-left="$router.back()"
|
|
|
|
|
+ fixed
|
|
|
|
|
+ placeholder
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <van-form @submit="onSubmit">
|
|
|
|
|
+ <van-cell-group inset class="mt-2">
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ v-model="form.name"
|
|
|
|
|
+ name="name"
|
|
|
|
|
+ :label="$t('merchant.product.form.name')"
|
|
|
|
|
+ :placeholder="$t('merchant.product.form.namePlaceholder')"
|
|
|
|
|
+ :rules="[{ required: true, message: $t('merchant.product.form.nameRequired') }]"
|
|
|
|
|
+ />
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ v-model="form.price"
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ name="price"
|
|
|
|
|
+ :label="$t('merchant.product.form.price')"
|
|
|
|
|
+ :placeholder="$t('merchant.product.form.pricePlaceholder')"
|
|
|
|
|
+ :rules="[{ required: true, message: $t('merchant.product.form.priceRequired') }]"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #extra>{{ $t('merchant.product.form.currency') }}</template>
|
|
|
|
|
+ </van-field>
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ v-model="form.category"
|
|
|
|
|
+ is-link
|
|
|
|
|
+ readonly
|
|
|
|
|
+ name="category"
|
|
|
|
|
+ :label="$t('merchant.product.form.category')"
|
|
|
|
|
+ :placeholder="$t('merchant.product.form.categoryPlaceholder')"
|
|
|
|
|
+ @click="showCategoryPicker = true"
|
|
|
|
|
+ :rules="[{ required: true, message: $t('merchant.product.form.categoryRequired') }]"
|
|
|
|
|
+ />
|
|
|
|
|
+ <van-popup v-model:show="showCategoryPicker" position="bottom">
|
|
|
|
|
+ <van-picker
|
|
|
|
|
+ :columns="categories"
|
|
|
|
|
+ @confirm="onConfirmCategory"
|
|
|
|
|
+ @cancel="showCategoryPicker = false"
|
|
|
|
|
+ />
|
|
|
|
|
+ </van-popup>
|
|
|
|
|
+
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ v-model="form.spec"
|
|
|
|
|
+ name="spec"
|
|
|
|
|
+ :label="$t('merchant.product.form.spec')"
|
|
|
|
|
+ :placeholder="$t('merchant.product.form.specPlaceholder')"
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <van-field name="status" :label="$t('merchant.product.form.status')">
|
|
|
|
|
+ <template #input>
|
|
|
|
|
+ <van-switch v-model="form.status" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </van-field>
|
|
|
|
|
+
|
|
|
|
|
+ <van-field
|
|
|
|
|
+ v-model="form.description"
|
|
|
|
|
+ rows="3"
|
|
|
|
|
+ autosize
|
|
|
|
|
+ :label="$t('merchant.product.form.description')"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :placeholder="$t('merchant.product.form.descriptionPlaceholder')"
|
|
|
|
|
+ />
|
|
|
|
|
+ </van-cell-group>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图片上传 (Mock) -->
|
|
|
|
|
+ <van-cell-group inset class="mt-2">
|
|
|
|
|
+ <van-cell :title="$t('merchant.product.form.image')">
|
|
|
|
|
+ <template #label>
|
|
|
|
|
+ <div class="upload-mock">
|
|
|
|
|
+ <van-icon name="photograph" size="30" color="#dcdee0" />
|
|
|
|
|
+ <span>{{ $t('merchant.product.form.uploadMock') }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </van-cell>
|
|
|
|
|
+ </van-cell-group>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="submit-btn">
|
|
|
|
|
+ <van-button round block type="primary" native-type="submit" :loading="loading">
|
|
|
|
|
+ {{ $t('merchant.product.form.save') }}
|
|
|
|
|
+ </van-button>
|
|
|
|
|
+ <van-button
|
|
|
|
|
+ v-if="isEdit"
|
|
|
|
|
+ round
|
|
|
|
|
+ block
|
|
|
|
|
+ type="danger"
|
|
|
|
|
+ class="mt-2"
|
|
|
|
|
+ @click="handleDelete"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ $t('merchant.product.form.delete') }}
|
|
|
|
|
+ </van-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </van-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref, onMounted, computed } from 'vue'
|
|
|
|
|
+import { useRoute, useRouter } from 'vue-router'
|
|
|
|
|
+import { showToast, showDialog } from 'vant'
|
|
|
|
|
+import { useMerchantStore } from '@/store/modules/merchant'
|
|
|
|
|
+import { useI18n } from 'vue-i18n'
|
|
|
|
|
+
|
|
|
|
|
+const { t } = useI18n()
|
|
|
|
|
+const route = useRoute()
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+const merchantStore = useMerchantStore()
|
|
|
|
|
+
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const showCategoryPicker = ref(false)
|
|
|
|
|
+const categories = [
|
|
|
|
|
+ { text: '面食', value: '面食' },
|
|
|
|
|
+ { text: '饭类', value: '饭类' },
|
|
|
|
|
+ { text: '小吃', value: '小吃' },
|
|
|
|
|
+ { text: '饮料', value: '饮料' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const form = ref({
|
|
|
|
|
+ id: '',
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ price: '',
|
|
|
|
|
+ category: '',
|
|
|
|
|
+ description: '',
|
|
|
|
|
+ status: true,
|
|
|
|
|
+ spec: '',
|
|
|
|
|
+ image: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const isEdit = computed(() => !!route.params.id)
|
|
|
|
|
+
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ if (isEdit.value) {
|
|
|
|
|
+ const products = await merchantStore.getProducts()
|
|
|
|
|
+ const product = products.find((p: any) => p.id === route.params.id)
|
|
|
|
|
+ if (product) {
|
|
|
|
|
+ form.value = { ...product }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showToast(t('common.error')) // Using common error as placeholder for "product not found" if not strictly defined
|
|
|
|
|
+ router.back()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const onConfirmCategory = ({ selectedOptions }: any) => {
|
|
|
|
|
+ form.value.category = selectedOptions[0].text
|
|
|
|
|
+ showCategoryPicker.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const onSubmit = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const data = {
|
|
|
|
|
+ ...form.value,
|
|
|
|
|
+ price: Number(form.value.price)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isEdit.value) {
|
|
|
|
|
+ await merchantStore.updateProduct(form.value.id, data)
|
|
|
|
|
+ showToast(t('merchant.product.updateSuccess'))
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await merchantStore.addProduct(data)
|
|
|
|
|
+ showToast(t('merchant.product.createSuccess'))
|
|
|
|
|
+ }
|
|
|
|
|
+ router.back()
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ showToast(t('merchant.product.saveFailed'))
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDelete = () => {
|
|
|
|
|
+ showDialog({
|
|
|
|
|
+ title: t('merchant.product.confirmDelete'),
|
|
|
|
|
+ message: t('merchant.product.confirmDeleteMessage', { name: form.value.name }),
|
|
|
|
|
+ showCancelButton: true
|
|
|
|
|
+ }).then(async (action) => {
|
|
|
|
|
+ if (action === 'confirm') {
|
|
|
|
|
+ await merchantStore.deleteProduct(form.value.id)
|
|
|
|
|
+ showToast(t('merchant.product.deleteSuccess'))
|
|
|
|
|
+ router.back()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.product-edit {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ padding-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.mt-2 {
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.submit-btn {
|
|
|
|
|
+ margin: 24px 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-mock {
|
|
|
|
|
+ width: 80px;
|
|
|
|
|
+ height: 80px;
|
|
|
|
|
+ background: #f7f8fa;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 10px;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|