feat: 更新 '@capp/eden' 依赖至 0.0.10,优化地址管理表单字段和逻辑
This commit is contained in:
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -52,8 +52,8 @@ catalogs:
|
|||||||
specifier: 8.0.0
|
specifier: 8.0.0
|
||||||
version: 8.0.0
|
version: 8.0.0
|
||||||
'@capp/eden':
|
'@capp/eden':
|
||||||
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.9.tgz
|
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.10.tgz
|
||||||
version: 0.0.9
|
version: 0.0.10
|
||||||
'@cloudflare/workers-types':
|
'@cloudflare/workers-types':
|
||||||
specifier: ^4.20260113.0
|
specifier: ^4.20260113.0
|
||||||
version: 4.20260116.0
|
version: 4.20260116.0
|
||||||
@@ -298,7 +298,7 @@ importers:
|
|||||||
version: 8.0.0(@capacitor/core@8.0.0)
|
version: 8.0.0(@capacitor/core@8.0.0)
|
||||||
'@capp/eden':
|
'@capp/eden':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: http://192.168.1.2:9538/api/capp-eden-0.0.9.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
version: http://192.168.1.2:9538/api/capp-eden-0.0.10.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
||||||
'@elysiajs/eden':
|
'@elysiajs/eden':
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||||
@@ -1182,9 +1182,9 @@ packages:
|
|||||||
'@capacitor/synapse@1.0.4':
|
'@capacitor/synapse@1.0.4':
|
||||||
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
||||||
|
|
||||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.9.tgz':
|
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.10.tgz':
|
||||||
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.9.tgz}
|
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.10.tgz}
|
||||||
version: 0.0.9
|
version: 0.0.10
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@elysiajs/eden': ^1.4.6
|
'@elysiajs/eden': ^1.4.6
|
||||||
|
|
||||||
@@ -6903,7 +6903,7 @@ snapshots:
|
|||||||
|
|
||||||
'@capacitor/synapse@1.0.4': {}
|
'@capacitor/synapse@1.0.4': {}
|
||||||
|
|
||||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.9.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.10.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@elysiajs/eden': 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
'@elysiajs/eden': 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ catalog:
|
|||||||
'@capacitor/keyboard': 8.0.0
|
'@capacitor/keyboard': 8.0.0
|
||||||
'@capacitor/share': ^8.0.0
|
'@capacitor/share': ^8.0.0
|
||||||
'@capacitor/status-bar': 8.0.0
|
'@capacitor/status-bar': 8.0.0
|
||||||
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.9.tgz
|
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.10.tgz
|
||||||
'@cloudflare/workers-types': ^4.20260113.0
|
'@cloudflare/workers-types': ^4.20260113.0
|
||||||
'@elysiajs/eden': ^1.4.6
|
'@elysiajs/eden': ^1.4.6
|
||||||
'@faker-js/faker': ^10.2.0
|
'@faker-js/faker': ^10.2.0
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
|
import type { Treaty } from "@elysiajs/eden";
|
||||||
|
import type { TreatyBody } from "@/api/types";
|
||||||
import { toastController } from "@ionic/vue";
|
import { toastController } from "@ionic/vue";
|
||||||
import { callOutline, locationOutline, personOutline } from "ionicons/icons";
|
import { callOutline, locationOutline, personOutline } from "ionicons/icons";
|
||||||
import zod from "zod";
|
import zod from "zod";
|
||||||
|
import { client, safeClient } from "@/api";
|
||||||
|
|
||||||
|
type Address = TreatyBody<typeof client.api.shipping_address.post>;
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref<Address>({
|
||||||
name: "",
|
recipientName: "",
|
||||||
phone: "",
|
phoneNumber: "",
|
||||||
address: "",
|
detailAddress: "",
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -18,17 +23,16 @@ const isEditMode = computed(() => !!route.query.id);
|
|||||||
|
|
||||||
// 表单验证 Schema
|
// 表单验证 Schema
|
||||||
const AddressSchema = zod.object({
|
const AddressSchema = zod.object({
|
||||||
name: zod
|
recipientName: zod
|
||||||
.string()
|
.string()
|
||||||
.min(2, "请输入收货人姓名")
|
|
||||||
.max(20, "姓名长度不能超过20个字符"),
|
.max(20, "姓名长度不能超过20个字符"),
|
||||||
|
|
||||||
phone: zod
|
phoneNumber: zod
|
||||||
.string()
|
.string()
|
||||||
.min(1, "请输入手机号")
|
.min(1, "请输入手机号")
|
||||||
.regex(/^1[3-9]\d{9}$/, "请输入正确的手机号码"),
|
.regex(/^1[3-9]\d{9}$/, "请输入正确的手机号码"),
|
||||||
|
|
||||||
address: zod
|
detailAddress: zod
|
||||||
.string()
|
.string()
|
||||||
.min(5, "请输入详细地址")
|
.min(5, "请输入详细地址")
|
||||||
.max(200, "地址长度不能超过200个字符"),
|
.max(200, "地址长度不能超过200个字符"),
|
||||||
@@ -50,9 +54,9 @@ onMounted(() => {
|
|||||||
// TODO: 根据 route.query.id 加载地址数据
|
// TODO: 根据 route.query.id 加载地址数据
|
||||||
// 模拟数据
|
// 模拟数据
|
||||||
formData.value = {
|
formData.value = {
|
||||||
name: "张三",
|
recipientName: "张三",
|
||||||
phone: "13800138000",
|
phoneNumber: "13800138000",
|
||||||
address: "北京市朝阳区建国路88号SOHO现代城A座1001室",
|
detailAddress: "北京市朝阳区建国路88号SOHO现代城A座1001室",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -69,21 +73,18 @@ async function handleSubmit() {
|
|||||||
isSubmitting.value = true;
|
isSubmitting.value = true;
|
||||||
try {
|
try {
|
||||||
// TODO: 调用添加/编辑地址 API
|
// TODO: 调用添加/编辑地址 API
|
||||||
// if (isEditMode.value) {
|
if (isEditMode.value) {
|
||||||
// const { data } = await safeClient(client.api.address[route.query.id].put(formData.value));
|
// const { data } = await safeClient();
|
||||||
// } else {
|
}
|
||||||
// const { data } = await safeClient(client.api.address.post(formData.value));
|
else {
|
||||||
// }
|
await safeClient(client.api.shipping_address.post({
|
||||||
|
...formData.value,
|
||||||
// 模拟 API 调用
|
}));
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
}
|
||||||
|
|
||||||
await showToast(isEditMode.value ? "地址修改成功" : "地址添加成功", "success");
|
await showToast(isEditMode.value ? "地址修改成功" : "地址添加成功", "success");
|
||||||
router.back();
|
router.back();
|
||||||
}
|
}
|
||||||
catch (error) {
|
|
||||||
await showToast("操作失败,请重试", "danger");
|
|
||||||
}
|
|
||||||
finally {
|
finally {
|
||||||
isSubmitting.value = false;
|
isSubmitting.value = false;
|
||||||
}
|
}
|
||||||
@@ -122,7 +123,7 @@ async function handleSubmit() {
|
|||||||
</div>
|
</div>
|
||||||
<ion-item lines="none" class="input-item">
|
<ion-item lines="none" class="input-item">
|
||||||
<ion-input
|
<ion-input
|
||||||
v-model="formData.name"
|
v-model="formData.recipientName"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="请输入收货人姓名"
|
placeholder="请输入收货人姓名"
|
||||||
class="custom-input"
|
class="custom-input"
|
||||||
@@ -139,7 +140,7 @@ async function handleSubmit() {
|
|||||||
</div>
|
</div>
|
||||||
<ion-item lines="none" class="input-item">
|
<ion-item lines="none" class="input-item">
|
||||||
<ion-input
|
<ion-input
|
||||||
v-model="formData.phone"
|
v-model="formData.phoneNumber"
|
||||||
type="tel"
|
type="tel"
|
||||||
placeholder="请输入手机号码"
|
placeholder="请输入手机号码"
|
||||||
class="custom-input"
|
class="custom-input"
|
||||||
@@ -156,7 +157,7 @@ async function handleSubmit() {
|
|||||||
</div>
|
</div>
|
||||||
<ion-item lines="none" class="input-item textarea-item">
|
<ion-item lines="none" class="input-item textarea-item">
|
||||||
<ion-textarea
|
<ion-textarea
|
||||||
v-model="formData.address"
|
v-model="formData.detailAddress"
|
||||||
placeholder="请输入详细地址(省市区街道门牌号等)"
|
placeholder="请输入详细地址(省市区街道门牌号等)"
|
||||||
class="custom-textarea"
|
class="custom-textarea"
|
||||||
:rows="4"
|
:rows="4"
|
||||||
|
|||||||
@@ -1,35 +1,15 @@
|
|||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
|
import type { Treaty } from "@elysiajs/eden";
|
||||||
import { alertController, toastController } from "@ionic/vue";
|
import { alertController, toastController } from "@ionic/vue";
|
||||||
import { addOutline, callOutline, checkmarkCircleOutline, createOutline, locationOutline, trashOutline } from "ionicons/icons";
|
import { addOutline, callOutline, checkmarkCircleOutline, createOutline, locationOutline, trashOutline } from "ionicons/icons";
|
||||||
|
import { client, safeClient } from "@/api";
|
||||||
|
|
||||||
|
type Address = Treaty.Data<typeof client.api.shipping_address.get>["data"][number];
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { data: addresses, execute } = await safeClient(() => client.api.shipping_address.get());
|
||||||
|
|
||||||
interface Address {
|
async function showToast(message: string, color: "success" | "danger" | "warning" | "light" = "light") {
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
phone: string;
|
|
||||||
address: string;
|
|
||||||
isDefault: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const addresses = ref<Address[]>([
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: "张三",
|
|
||||||
phone: "13800138000",
|
|
||||||
address: "北京市朝阳区建国路88号SOHO现代城A座1001室",
|
|
||||||
isDefault: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: "李四",
|
|
||||||
phone: "13900139000",
|
|
||||||
address: "上海市浦东新区世纪大道1号东方明珠广播电视塔",
|
|
||||||
isDefault: false,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
async function showToast(message: string, color: "success" | "danger" | "warning" = "success") {
|
|
||||||
const toast = await toastController.create({
|
const toast = await toastController.create({
|
||||||
message,
|
message,
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
@@ -44,31 +24,21 @@ function handleAdd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleEdit(address: Address) {
|
function handleEdit(address: Address) {
|
||||||
// TODO: 跳转到编辑页面并传递地址ID
|
|
||||||
router.push(`/address/add?id=${address.id}`);
|
router.push(`/address/add?id=${address.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSetDefault(address: Address) {
|
async function handleSetDefault(address: Address) {
|
||||||
if (address.isDefault) {
|
if (address.isDefault) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await safeClient(client.api.shipping_address({ id: address.id }).default.post());
|
||||||
// 取消所有默认地址
|
|
||||||
addresses.value.forEach((item) => {
|
|
||||||
item.isDefault = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置当前地址为默认
|
|
||||||
address.isDefault = true;
|
|
||||||
|
|
||||||
// TODO: 调用 API 更新默认地址
|
|
||||||
await showToast("已设为默认地址");
|
await showToast("已设为默认地址");
|
||||||
|
execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete(address: Address) {
|
async function handleDelete(address: Address) {
|
||||||
const alert = await alertController.create({
|
const alert = await alertController.create({
|
||||||
header: "确认删除",
|
header: "确认删除",
|
||||||
message: `确定要删除"${address.name}"的收货地址吗?`,
|
message: `确定要删除"${address.recipientName}"的收货地址吗?`,
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
text: "取消",
|
text: "取消",
|
||||||
@@ -78,22 +48,9 @@ async function handleDelete(address: Address) {
|
|||||||
text: "删除",
|
text: "删除",
|
||||||
role: "destructive",
|
role: "destructive",
|
||||||
handler: async () => {
|
handler: async () => {
|
||||||
// 如果删除的是默认地址,需要先设置其他地址为默认
|
await safeClient(client.api.shipping_address({ id: address.id }).delete());
|
||||||
if (address.isDefault && addresses.value.length > 1) {
|
|
||||||
const otherAddress = addresses.value.find(item => item.id !== address.id);
|
|
||||||
if (otherAddress) {
|
|
||||||
otherAddress.isDefault = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除地址
|
|
||||||
const index = addresses.value.findIndex(item => item.id === address.id);
|
|
||||||
if (index > -1) {
|
|
||||||
addresses.value.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 调用 API 删除地址
|
|
||||||
await showToast("删除成功");
|
await showToast("删除成功");
|
||||||
|
execute();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -120,10 +77,24 @@ async function handleDelete(address: Address) {
|
|||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<div v-if="addresses.length > 0" class="ion-padding">
|
<div v-if="addresses?.data.length === 0" class="empty-state">
|
||||||
|
<empty title="暂无收货地址">
|
||||||
|
<template #icon>
|
||||||
|
<ion-icon :icon="locationOutline" class="empty-icon" />
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<ion-button class="add-button" @click="handleAdd">
|
||||||
|
<ion-icon slot="start" :icon="addOutline" />
|
||||||
|
添加收货地址
|
||||||
|
</ion-button>
|
||||||
|
</template>
|
||||||
|
</empty>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="ion-padding">
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div
|
<div
|
||||||
v-for="address in addresses"
|
v-for="address in addresses?.data"
|
||||||
:key="address.id"
|
:key="address.id"
|
||||||
class="address-card"
|
class="address-card"
|
||||||
>
|
>
|
||||||
@@ -132,8 +103,8 @@ async function handleDelete(address: Address) {
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<ion-icon :icon="locationOutline" class="text-xl text-primary" />
|
<ion-icon :icon="locationOutline" class="text-xl text-primary" />
|
||||||
<span class="name">{{ address.name }}</span>
|
<span class="name">{{ address.recipientName }}</span>
|
||||||
<span class="phone">{{ address.phone }}</span>
|
<span class="phone">{{ address.phoneNumber }}</span>
|
||||||
</div>
|
</div>
|
||||||
<ion-badge v-if="address.isDefault" color="danger" class="default-badge">
|
<ion-badge v-if="address.isDefault" color="danger" class="default-badge">
|
||||||
默认
|
默认
|
||||||
@@ -141,7 +112,7 @@ async function handleDelete(address: Address) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="address-text">
|
<div class="address-text">
|
||||||
{{ address.address }}
|
{{ address.detailAddress }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -150,6 +121,7 @@ async function handleDelete(address: Address) {
|
|||||||
<ion-button
|
<ion-button
|
||||||
fill="clear"
|
fill="clear"
|
||||||
size="small"
|
size="small"
|
||||||
|
:color="address.isDefault ? 'primary' : 'medium'"
|
||||||
@click="handleSetDefault(address)"
|
@click="handleSetDefault(address)"
|
||||||
>
|
>
|
||||||
<ion-icon slot="start" :icon="checkmarkCircleOutline" />
|
<ion-icon slot="start" :icon="checkmarkCircleOutline" />
|
||||||
@@ -182,23 +154,7 @@ async function handleDelete(address: Address) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 空状态 -->
|
<div v-if="addresses?.data && addresses.data.length > 0" class="fixed-bottom">
|
||||||
<div v-else class="empty-state">
|
|
||||||
<empty title="暂无收货地址">
|
|
||||||
<template #icon>
|
|
||||||
<ion-icon :icon="locationOutline" class="empty-icon" />
|
|
||||||
</template>
|
|
||||||
<template #extra>
|
|
||||||
<ion-button class="add-button" @click="handleAdd">
|
|
||||||
<ion-icon slot="start" :icon="addOutline" />
|
|
||||||
添加收货地址
|
|
||||||
</ion-button>
|
|
||||||
</template>
|
|
||||||
</empty>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 底部添加按钮 -->
|
|
||||||
<div v-if="addresses.length > 0" class="fixed-bottom">
|
|
||||||
<ion-button expand="block" class="add-button" @click="handleAdd">
|
<ion-button expand="block" class="add-button" @click="handleAdd">
|
||||||
<ion-icon slot="start" :icon="addOutline" />
|
<ion-icon slot="start" :icon="addOutline" />
|
||||||
添加新地址
|
添加新地址
|
||||||
|
|||||||
Reference in New Issue
Block a user