This commit introduces the foundational structure for the admin dashboard, focusing on the member management feature. Key additions include: - A new page at `/admin/manage/members` to display and manage members. - An `AddModal` component with a comprehensive form for creating new members, featuring validation with Zod and input masking. - A reusable `PhoneInput` component with country code selection, backed by a new `useCountries` composable and a full country dataset. - A custom, user-friendly global error page (`error.vue`) to handle application errors gracefully. - Updated dashboard sidebar navigation to include the new member management section. - Added recommended VS Code extensions and settings to improve developer experience.
65 lines
1.5 KiB
Vue
65 lines
1.5 KiB
Vue
<!-- ~/components/PhoneInput.vue -->
|
|
<script setup lang="ts">
|
|
import { USelectMenu, UInput } from "#components";
|
|
import { useCountries } from "~/composables/useCountries";
|
|
|
|
interface Props {
|
|
modelValue?: string;
|
|
defaultDial?: string;
|
|
}
|
|
|
|
const props = defineProps<Props>();
|
|
const emit = defineEmits(["update:modelValue", "update:country"]);
|
|
|
|
const { getAll } = useCountries();
|
|
|
|
// 当前选中国家
|
|
const selectedCountry = ref(
|
|
getAll().find((c) => c.dial === props.defaultDial)?.dial || getAll()[0]?.dial
|
|
);
|
|
|
|
console.table(getAll().map(c => ({ name: c.name.cn, dial: c.dial })));
|
|
|
|
// 用户输入的电话号码
|
|
const phone = ref(props.modelValue || "");
|
|
|
|
// 计算选项
|
|
const countryOptions = computed(() =>
|
|
getAll()
|
|
.filter((c) => c.dial && c.dial.trim() !== "")
|
|
.map((c) => ({
|
|
label: `${c.name.cn} (${c.dial || "未知"})`,
|
|
id: c.dial || "unknown",
|
|
}))
|
|
);
|
|
|
|
// 完整号码输出
|
|
const fullNumber = computed(() => {
|
|
const dial = selectedCountry.value || "";
|
|
return `${dial}${phone.value}`;
|
|
});
|
|
|
|
// 双向绑定
|
|
watch(phone, () => emit("update:modelValue", fullNumber.value));
|
|
watch(selectedCountry, () => emit("update:country", selectedCountry.value));
|
|
</script>
|
|
|
|
<template>
|
|
<div class="flex items-center gap-2">
|
|
<!-- 国家区号选择 -->
|
|
<USelectMenu
|
|
v-model="selectedCountry"
|
|
value-key="id"
|
|
:items="countryOptions"
|
|
/>
|
|
|
|
<!-- 电话号码输入 -->
|
|
<UInput
|
|
v-model="phone"
|
|
placeholder="输入电话号码"
|
|
type="tel"
|
|
class="flex-1"
|
|
/>
|
|
</div>
|
|
</template>
|