mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-07 02:46:55 +08:00
songlist add import from file, partically complete point system
This commit is contained in:
264
src/views/pointViews/PointGoodsView.vue
Normal file
264
src/views/pointViews/PointGoodsView.vue
Normal file
@@ -0,0 +1,264 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AddressInfo,
|
||||
GoodsTypes,
|
||||
PointGoodsModel,
|
||||
ResponsePointGoodModel,
|
||||
ResponsePointOrder2UserModel,
|
||||
UserInfo,
|
||||
} from '@/api/api-models'
|
||||
import { useUser } from '@/api/user'
|
||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||
import PointGoodsItem from '@/components/manage/PointGoodsItem.vue'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import {
|
||||
NAlert,
|
||||
NButton,
|
||||
NCard,
|
||||
NDataTable,
|
||||
NDivider,
|
||||
NFlex,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NGrid,
|
||||
NGridItem,
|
||||
NIcon,
|
||||
NInputGroup,
|
||||
NInputGroupLabel,
|
||||
NInputNumber,
|
||||
NLayoutContent,
|
||||
NModal,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSpin,
|
||||
NTag,
|
||||
NText,
|
||||
NTimeline,
|
||||
NTimelineItem,
|
||||
NTooltip,
|
||||
SelectOption,
|
||||
useDialog,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { ref, computed, onMounted, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps<{
|
||||
userInfo: UserInfo
|
||||
biliInfo: any
|
||||
}>()
|
||||
|
||||
const useAuth = useAuthStore()
|
||||
const isLoading = ref(false)
|
||||
const message = useMessage()
|
||||
const dialog = useDialog()
|
||||
const biliAuth = computed(() => useAuth.biliAuth)
|
||||
|
||||
const goods = ref<ResponsePointGoodModel[]>([])
|
||||
const currentPoint = ref<number>(-1)
|
||||
|
||||
const showBuyModal = ref(false)
|
||||
const showAddressSelect = ref(false)
|
||||
|
||||
const currentGoods = ref<ResponsePointGoodModel>()
|
||||
const buyCount = ref(1)
|
||||
const selectedAddress = ref<AddressInfo>()
|
||||
const canDoBuy = computed(() => {
|
||||
return currentGoods.value && currentGoods.value.price * buyCount.value < currentPoint.value
|
||||
})
|
||||
|
||||
const addressOptions = computed(() => {
|
||||
if (!biliAuth.value.id) return []
|
||||
return (
|
||||
biliAuth.value.address?.map((item) => {
|
||||
return {
|
||||
label: item.address,
|
||||
value: item.id,
|
||||
}
|
||||
}) ?? []
|
||||
)
|
||||
})
|
||||
|
||||
const canBuy = computed(() => {
|
||||
if (!biliAuth.value.id) return false
|
||||
if (!currentPoint.value) return false
|
||||
return true
|
||||
})
|
||||
function getTooltip(goods: ResponsePointGoodModel) {
|
||||
if (!canBuy.value) return '请先进行账号认证'
|
||||
if ((currentPoint.value ?? 0) < goods.price) {
|
||||
return '当前积分不足'
|
||||
} else {
|
||||
return '开始兑换'
|
||||
}
|
||||
}
|
||||
async function buyGoods() {
|
||||
if (buyCount.value < 1) {
|
||||
message.error('兑换数量不能小于1')
|
||||
} else if (!selectedAddress.value && currentGoods.value?.type == GoodsTypes.Physical) {
|
||||
message.error('请选择收货地址')
|
||||
} else if (!Number.isInteger(buyCount.value)) {
|
||||
message.error('兑换数量必须为整数')
|
||||
} else {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await useAuth.QueryBiliAuthPostAPI<ResponsePointOrder2UserModel>(POINT_API_URL + 'buy', {
|
||||
vId: props.userInfo.id,
|
||||
goodsId: currentGoods.value?.id,
|
||||
count: buyCount.value,
|
||||
addressId: selectedAddress.value ? selectedAddress.value.id : null,
|
||||
})
|
||||
if (data.code == 200) {
|
||||
message.info('兑换成功')
|
||||
dialog.success({
|
||||
title: '成功',
|
||||
content: `兑换成功,订单号:${data.data.id}`,
|
||||
positiveText: '前往查看',
|
||||
negativeText: '我知道了',
|
||||
onPositiveClick: () => {
|
||||
useRouter().push({ name: 'PointOrderView', params: { id: data.data.id } })
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
showBuyModal.value = false
|
||||
showAddressSelect.value = false
|
||||
selectedAddress.value = undefined
|
||||
buyCount.value = 1
|
||||
currentGoods.value = undefined
|
||||
},
|
||||
})
|
||||
} else {
|
||||
message.error('兑换失败: ' + data.message)
|
||||
console.error(data)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
message.error('兑换失败: ' + err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
function onBuyClick(good: ResponsePointGoodModel) {
|
||||
showBuyModal.value = true
|
||||
currentGoods.value = good
|
||||
}
|
||||
const renderLabel = (option: SelectOption) => {
|
||||
return h(AddressDisplay, { address: biliAuth.value.address?.find((a) => a.id == option.value), size: 'small' })
|
||||
}
|
||||
const renderOption = ({ node, option }: { node: any; option: SelectOption }) => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
style: 'width: 100%;height: 100%;margin: 5px;padding: 12px;',
|
||||
secondary: true,
|
||||
type: selectedAddress.value?.id != option.value ? 'default' : 'info',
|
||||
onClick: () => {
|
||||
selectedAddress.value = biliAuth.value.address?.find((a) => a.id == option.value)
|
||||
showAddressSelect.value = false
|
||||
},
|
||||
},
|
||||
() => h(AddressDisplay, { address: biliAuth.value.address?.find((a) => a.id == option.value) }),
|
||||
)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.userInfo && useAuth.isAuthed) {
|
||||
if (!biliAuth.value.id) {
|
||||
isLoading.value = true
|
||||
await useAuth.getAuthInfo()
|
||||
}
|
||||
if (biliAuth.value.id) {
|
||||
currentPoint.value = (await useAuth.GetSpecificPoint(props.userInfo.id)) ?? -1
|
||||
}
|
||||
}
|
||||
|
||||
goods.value = await useAuth.GetGoods(props.userInfo.id, message)
|
||||
isLoading.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NAlert v-if="!useAuth.isAuthed" type="warning">
|
||||
你尚未进行 Bilibili 账号认证, 无法兑换积分
|
||||
<br />
|
||||
<NButton type="primary" @click="$router.push({ name: 'bili-auth' })" size="small" style="margin-top: 12px">
|
||||
立即认证
|
||||
</NButton>
|
||||
</NAlert>
|
||||
<NCard v-else>
|
||||
<template #header> 你好, {{ useAuth.biliAuth.name }} </template>
|
||||
<NText> 你在 {{ userInfo.extra?.streamerInfo?.name ?? userInfo.name }} 的直播间的积分为 {{ currentPoint }} </NText>
|
||||
</NCard>
|
||||
<NDivider />
|
||||
<NSpin :show="isLoading">
|
||||
<NGrid cols="1 500:2 700:3 1000:4 1200:5" x-gap="12" y-gap="8">
|
||||
<NGridItem v-for="item in goods" :key="item.id">
|
||||
<PointGoodsItem :goods="item">
|
||||
<template #footer>
|
||||
<NFlex justify="space-between" align="center">
|
||||
<NTooltip>
|
||||
<template #trigger>
|
||||
<NButton :disabled="!canBuy" size="small" type="primary" @click="onBuyClick(item)">兑换</NButton>
|
||||
</template>
|
||||
{{ getTooltip(item) }}
|
||||
</NTooltip>
|
||||
<NFlex style="flex: 1" justify="end">
|
||||
<NText style="size: 34px">
|
||||
🪙
|
||||
{{ item.price }}
|
||||
</NText>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
</template>
|
||||
</PointGoodsItem>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</NSpin>
|
||||
<NModal
|
||||
v-model:show="showBuyModal"
|
||||
v-if="currentGoods"
|
||||
preset="card"
|
||||
title="确认兑换"
|
||||
style="width: 400px; max-width: 90vw; height: auto"
|
||||
>
|
||||
<template #header>
|
||||
<NFlex align="baseline">
|
||||
<NTag :type="currentGoods.type == GoodsTypes.Physical ? 'info' : 'default'" :bordered="false">
|
||||
{{ currentGoods.type == GoodsTypes.Physical ? '实体礼物' : '虚拟物品' }}
|
||||
</NTag>
|
||||
<NText> {{ currentGoods.name }} </NText>
|
||||
</NFlex>
|
||||
</template>
|
||||
<PointGoodsItem v-if="currentGoods" :goods="currentGoods" />
|
||||
<template v-if="currentGoods.type == GoodsTypes.Physical">
|
||||
<NDivider> 选项 </NDivider>
|
||||
<NForm>
|
||||
<NFormItem label="兑换数量" required
|
||||
><NInputNumber v-model:value="buyCount" :min="1" style="max-width: 120px" step="1" :precision="0" />
|
||||
</NFormItem>
|
||||
<NFormItem label="收货地址" required>
|
||||
<NSelect
|
||||
v-model:show="showAddressSelect"
|
||||
:value="selectedAddress?.id"
|
||||
:options="addressOptions"
|
||||
:render-label="renderLabel"
|
||||
:render-option="renderOption"
|
||||
placeholder="请选择地址"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
</template>
|
||||
<NDivider>
|
||||
<NTag :type="currentGoods.price * buyCount > currentPoint ? 'error' : 'success'">
|
||||
{{ currentGoods.price * buyCount > currentPoint ? '积分不足' : '可兑换' }}
|
||||
</NTag>
|
||||
</NDivider>
|
||||
<NButton type="primary" :disabled="!canDoBuy" @click="buyGoods" :loading="isLoading"> 确认兑换 </NButton>
|
||||
<NText>
|
||||
所需积分: {{ currentGoods.price * buyCount }}
|
||||
<NDivider vertical />
|
||||
当前积分: {{ currentPoint }}
|
||||
</NText>
|
||||
</NModal>
|
||||
</template>
|
||||
41
src/views/pointViews/PointOrderView.vue
Normal file
41
src/views/pointViews/PointOrderView.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
import { ResponsePointOrder2UserModel } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import PointOrderCard from '@/components/manage/PointOrderCard.vue'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { NButton, NCard, NEmpty, NList, NListItem, useMessage } from 'naive-ui'
|
||||
import { h, onMounted, ref } from 'vue'
|
||||
|
||||
const message = useMessage()
|
||||
const useAuth = useAuthStore()
|
||||
|
||||
const orders = ref<ResponsePointOrder2UserModel[]>([])
|
||||
const isLoading = ref(false)
|
||||
|
||||
async function getOrders() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await useAuth.QueryBiliAuthGetAPI<ResponsePointOrder2UserModel[]>(POINT_API_URL + 'user/get-orders')
|
||||
if (data.code == 200) {
|
||||
return data.data
|
||||
} else {
|
||||
message.error('获取订单失败: ' + data.message)
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
message.error('获取订单失败: ' + err)
|
||||
}
|
||||
isLoading.value = false
|
||||
return []
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
orders.value = await getOrders()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NEmpty v-if="orders.length == 0" description="暂无订单"></NEmpty>
|
||||
<PointOrderCard v-else :order="orders" :loading="isLoading" type="user" />
|
||||
</template>
|
||||
40
src/views/pointViews/PointUserHistoryView.vue
Normal file
40
src/views/pointViews/PointUserHistoryView.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { NDataTable, NLayoutContent, NSpace, useMessage } from 'naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import { ResponsePointHisrotyModel } from '@/api/api-models'
|
||||
import PointHistoryCard from '@/components/manage/PointHistoryCard.vue'
|
||||
|
||||
const message = useMessage()
|
||||
const useAuth = useAuthStore()
|
||||
const isLoading = ref(false)
|
||||
|
||||
const history = ref<ResponsePointHisrotyModel[]>([])
|
||||
|
||||
async function getHistories() {
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await useAuth.QueryBiliAuthGetAPI<ResponsePointHisrotyModel[]>(POINT_API_URL + 'user/get-histories')
|
||||
if (data.code == 200) {
|
||||
console.log('[point] 已获取积分历史')
|
||||
return data.data
|
||||
} else {
|
||||
message.error('获取积分历史失败: ' + data.message)
|
||||
console.error(data)
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('获取积分历史失败: ' + err)
|
||||
console.error(err)
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
history.value = await getHistories()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PointHistoryCard :histories="history" />
|
||||
</template>
|
||||
185
src/views/pointViews/PointUserLayout.vue
Normal file
185
src/views/pointViews/PointUserLayout.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
NButton,
|
||||
NCard,
|
||||
NDataTable,
|
||||
NListItem,
|
||||
NTabPane,
|
||||
NTabs,
|
||||
NLayout,
|
||||
NLayoutContent,
|
||||
NText,
|
||||
useMessage,
|
||||
NLayoutHeader,
|
||||
NFlex,
|
||||
NDescriptions,
|
||||
NDescriptionsItem,
|
||||
NResult,
|
||||
NSpin,
|
||||
NDivider,
|
||||
NTag,
|
||||
NList,
|
||||
} from 'naive-ui'
|
||||
import { computed, h, onMounted, ref } from 'vue'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { UserInfo } from '@/api/api-models'
|
||||
import { POINT_API_URL } from '@/data/constants'
|
||||
import PointUserHistoryView from './PointUserHistoryView.vue'
|
||||
import PointUserSettings from './PointUserSettings.vue'
|
||||
import { useRouteHash } from '@vueuse/router'
|
||||
import PointOrderView from './PointOrderView.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const useAuth = useAuthStore()
|
||||
const message = useMessage()
|
||||
const realHash = useRouteHash('points', {
|
||||
mode: 'replace',
|
||||
})
|
||||
const hash = computed({
|
||||
get() {
|
||||
return realHash.value?.startsWith('#') ? realHash.value.slice(1) : realHash.value
|
||||
},
|
||||
set(val) {
|
||||
realHash.value = '#' + val
|
||||
},
|
||||
})
|
||||
|
||||
const biliAuth = computed(() => useAuth.biliAuth)
|
||||
const isLoading = ref(false)
|
||||
const points = ref<{ owner: UserInfo; points: number }[]>([])
|
||||
|
||||
const pointColumn = [
|
||||
{
|
||||
title: '所属用户',
|
||||
key: 'owner.name',
|
||||
},
|
||||
{
|
||||
title: '积分',
|
||||
key: 'points',
|
||||
},
|
||||
{
|
||||
title: '详情',
|
||||
key: 'action',
|
||||
render: (row: { owner: UserInfo; points: number }) => {
|
||||
return h(NButton, {
|
||||
onClick: () => {},
|
||||
})
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
async function getAllPoints() {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await useAuth.QueryBiliAuthGetAPI<{ owner: UserInfo; points: number }[]>(
|
||||
POINT_API_URL + 'user/get-all-point',
|
||||
)
|
||||
if (data.code == 200) {
|
||||
console.log('[point] 已获取积分')
|
||||
return data.data
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
message.error('获取积分失败: ' + err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
return []
|
||||
}
|
||||
function switchAuth(token: string) {
|
||||
if (token == useAuth.biliToken) {
|
||||
message.info('当前正在使用该账号')
|
||||
return
|
||||
}
|
||||
useAuth.setCurrentAuth(token)
|
||||
message.success('已选择账号')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const route = useRoute()
|
||||
if (route.query.auth) {
|
||||
useAuth.biliToken = route.query.auth as string
|
||||
}
|
||||
if (biliAuth.value?.id < 0) {
|
||||
isLoading.value = true
|
||||
await useAuth.getAuthInfo()
|
||||
isLoading.value = false
|
||||
}
|
||||
if (biliAuth.value.id >= 0) {
|
||||
points.value = await getAllPoints()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NLayout>
|
||||
<NSpin v-if="!biliAuth.id && useAuth.isLoading" :show="useAuth.isLoading" />
|
||||
<NLayoutContent v-else-if="!useAuth.biliToken && useAuth.biliTokens.length > 0" style="height: 100vh">
|
||||
<NCard title="选择B站账号" embedded>
|
||||
<NList clickable bordered>
|
||||
<NListItem v-for="item in useAuth.biliTokens" :key="item.token" @click="switchAuth(item.token)">
|
||||
<NFlex align="center"> {{ item.name }} - {{ item.uId }} </NFlex>
|
||||
</NListItem>
|
||||
</NList>
|
||||
</NCard>
|
||||
</NLayoutContent>
|
||||
<NLayoutContent v-else-if="!biliAuth.id" style="height: 100vh">
|
||||
<NResult status="error" title="你还未进行过B站账户验证" description="请先进行认证" style="padding-top: 64px">
|
||||
<template #footer>
|
||||
<NButton type="primary" @click="$router.push({ name: 'bili-auth' })">去认证</NButton>
|
||||
</template>
|
||||
</NResult>
|
||||
</NLayoutContent>
|
||||
<template v-else>
|
||||
<NLayoutHeader style="padding: 10px" bordered>
|
||||
<NFlex justify="center">
|
||||
<NText style="font-size: 24px"> 认证用户个人中心 </NText>
|
||||
</NFlex>
|
||||
</NLayoutHeader>
|
||||
<NLayoutContent content-style="padding: 24px;">
|
||||
<NFlex align="center" justify="center">
|
||||
<div style="max-width: 95vw; width: 900px">
|
||||
<NCard title="我的信息">
|
||||
<NDescriptions label-placement="left" bordered size="small">
|
||||
<NDescriptionsItem label="OpenId">
|
||||
{{ biliAuth.openId }}
|
||||
</NDescriptionsItem>
|
||||
<NDescriptionsItem label="UserId">
|
||||
{{ biliAuth.userId }}
|
||||
</NDescriptionsItem>
|
||||
</NDescriptions>
|
||||
</NCard>
|
||||
<NDivider />
|
||||
<NTabs v-if="hash" v-model:value="hash" default-value="points" animated>
|
||||
<NTabPane name="points" tab="我的积分" display-directive="show:lazy">
|
||||
<NDivider style="margin-top: 10px" />
|
||||
<NFlex justify="center">
|
||||
<NDataTable
|
||||
:loading="isLoading"
|
||||
:columns="pointColumn"
|
||||
:data="points"
|
||||
:pagination="{ defaultPageSize: 10, showSizePicker: true, pageSizes: [10, 25, 50, 100] }"
|
||||
size="small"
|
||||
style="max-width: 600px"
|
||||
/>
|
||||
</NFlex>
|
||||
</NTabPane>
|
||||
<NTabPane name="orders" tab="我的订单" display-directive="show:lazy">
|
||||
<NDivider style="margin-top: 10px" />
|
||||
<PointOrderView />
|
||||
</NTabPane>
|
||||
<NTabPane name="histories" tab="积分记录" display-directive="show:lazy">
|
||||
<NDivider style="margin-top: 10px" />
|
||||
<PointUserHistoryView />
|
||||
</NTabPane>
|
||||
<NTabPane name="settings" tab="设置" display-directive="show:lazy">
|
||||
<NDivider style="margin-top: 10px" />
|
||||
<PointUserSettings />
|
||||
</NTabPane>
|
||||
</NTabs>
|
||||
</div>
|
||||
</NFlex>
|
||||
</NLayoutContent>
|
||||
</template>
|
||||
</NLayout>
|
||||
</template>
|
||||
367
src/views/pointViews/PointUserSettings.vue
Normal file
367
src/views/pointViews/PointUserSettings.vue
Normal file
@@ -0,0 +1,367 @@
|
||||
<script setup lang="ts">
|
||||
import { AddressInfo } from '@/api/api-models'
|
||||
import { QueryGetAPI } from '@/api/query'
|
||||
import { POINT_API_URL, THINGS_URL } from '@/data/constants'
|
||||
import { useAuthStore } from '@/store/useAuthStore'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import {
|
||||
FormRules,
|
||||
NButton,
|
||||
NCard,
|
||||
NCheckbox,
|
||||
NCollapse,
|
||||
NCollapseItem,
|
||||
NDivider,
|
||||
NFlex,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NLayoutContent,
|
||||
NList,
|
||||
NListItem,
|
||||
NModal,
|
||||
NPopconfirm,
|
||||
NScrollbar,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NSpin,
|
||||
NTag,
|
||||
NText,
|
||||
NTimeline,
|
||||
NTimelineItem,
|
||||
SelectOption,
|
||||
useMessage,
|
||||
} from 'naive-ui'
|
||||
import { computed, h, ref } from 'vue'
|
||||
//@ts-ignore
|
||||
import UserAgreement from '@/document/UserAgreement.md'
|
||||
import AddressDisplay from '@/components/manage/AddressDisplay.vue'
|
||||
|
||||
type AreaData = {
|
||||
[province: string]: {
|
||||
[city: string]: {
|
||||
[district: string]: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const useAuth = useAuthStore()
|
||||
const message = useMessage()
|
||||
const isLoading = ref(false)
|
||||
|
||||
const userAgree = ref(false)
|
||||
|
||||
const areas = useStorage<{
|
||||
createAt: number
|
||||
data: AreaData
|
||||
}>('Data.Areas', {
|
||||
createAt: 0,
|
||||
data: {},
|
||||
})
|
||||
const provinceOptions = computed(() => {
|
||||
return Object.keys(areas.value?.data ?? {}).map((p) => ({ label: p, value: p }))
|
||||
})
|
||||
const cityOptions = (province: string) => {
|
||||
if (!areas.value?.data[province]) return []
|
||||
return Object.keys(areas.value?.data[province] ?? {}).map((c) => ({ label: c, value: c }))
|
||||
}
|
||||
const districtOptions = (province: string, city: string) => {
|
||||
if (!areas.value?.data[province]?.[city]) return []
|
||||
return Object.keys(areas.value?.data[province][city] ?? {}).map((d) => ({ label: d, value: d }))
|
||||
}
|
||||
const streetOptions = (province: string, city: string, district: string) => {
|
||||
if (!areas.value?.data[province]?.[city]?.[district]) return []
|
||||
return areas.value?.data[province][city][district]?.map((s) => ({ label: s, value: s })) ?? []
|
||||
}
|
||||
const rules: FormRules = {
|
||||
phone: {
|
||||
required: true,
|
||||
message: '请输入手机号',
|
||||
},
|
||||
address: {
|
||||
required: true,
|
||||
message: '请输入详细地址',
|
||||
},
|
||||
name: {
|
||||
required: true,
|
||||
message: '请输入收件人姓名',
|
||||
},
|
||||
area: {
|
||||
required: true,
|
||||
message: '请选择地区',
|
||||
validator: () => {
|
||||
if (currentAddress.value?.province && currentAddress.value?.city && currentAddress.value?.district) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
agreement: {
|
||||
required: true,
|
||||
message: '请阅读并同意用户协议',
|
||||
validator: () => {
|
||||
return userAgree.value
|
||||
},
|
||||
},
|
||||
}
|
||||
const formRef = ref()
|
||||
|
||||
const biliAuth = computed(() => useAuth.biliAuth)
|
||||
|
||||
const currentAddress = ref<AddressInfo>()
|
||||
|
||||
const showAddressModal = ref(false)
|
||||
const showAgreementModal = ref(false)
|
||||
|
||||
async function updateAddress() {
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(async () => {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await useAuth.QueryBiliAuthPostAPI<AddressInfo>(
|
||||
POINT_API_URL + 'user/update-address',
|
||||
currentAddress.value,
|
||||
)
|
||||
if (data.code == 200) {
|
||||
message.success('已保存')
|
||||
showAddressModal.value = false
|
||||
currentAddress.value = {} as AddressInfo
|
||||
if (biliAuth.value.address) {
|
||||
const index = biliAuth.value.address?.findIndex((a) => a.id == data.data.id) ?? -1
|
||||
if (index >= 0) {
|
||||
biliAuth.value.address[index] = data.data
|
||||
} else {
|
||||
biliAuth.value.address.push(data.data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error('更新地址失败: ' + data.message)
|
||||
console.error(data)
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('更新地址失败: ' + err)
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('信息未填写完成')
|
||||
})
|
||||
.finally(() => {
|
||||
isLoading.value = false
|
||||
})
|
||||
}
|
||||
async function deleteAddress(id: string) {
|
||||
isLoading.value = true
|
||||
try {
|
||||
const data = await useAuth.QueryBiliAuthGetAPI(POINT_API_URL + 'user/del-address', { id })
|
||||
if (data.code == 200) {
|
||||
message.success('已删除')
|
||||
if (biliAuth.value.address) {
|
||||
biliAuth.value.address = biliAuth.value.address?.filter((a) => a.id != id)
|
||||
}
|
||||
} else {
|
||||
message.error('删除地址失败: ' + data.message)
|
||||
console.error(data)
|
||||
}
|
||||
} catch (err) {
|
||||
message.error('删除地址失败: ' + err)
|
||||
console.error(err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
async function getArea() {
|
||||
if (areas.value && Date.now() - areas.value?.createAt < 1000 * 60 * 60 * 24 * 7) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
isLoading.value = true
|
||||
const data = await fetch(THINGS_URL + 'area_data.json')
|
||||
if (data.ok) {
|
||||
const area = {
|
||||
createAt: Date.now(),
|
||||
data: await data.json(),
|
||||
}
|
||||
console.log(area)
|
||||
areas.value = area
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
message.error('获取区域数据失败')
|
||||
}
|
||||
isLoading.value = false
|
||||
}
|
||||
async function onOpenAddressModal() {
|
||||
showAddressModal.value = true
|
||||
currentAddress.value = {} as AddressInfo
|
||||
await getArea()
|
||||
}
|
||||
function onAreaSelectChange(level: number) {
|
||||
if (!currentAddress.value) return
|
||||
const newValue = {} as AddressInfo
|
||||
switch (level) {
|
||||
case 0: {
|
||||
// @ts-ignore
|
||||
currentAddress.value.city = undefined
|
||||
// @ts-ignore
|
||||
currentAddress.value.district = undefined
|
||||
// @ts-ignore
|
||||
currentAddress.value.street = undefined
|
||||
}
|
||||
case 1: {
|
||||
// @ts-ignore
|
||||
currentAddress.value.district = undefined
|
||||
// @ts-ignore
|
||||
currentAddress.value.street = undefined
|
||||
}
|
||||
case 2: {
|
||||
// @ts-ignore
|
||||
currentAddress.value.street = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
function switchAuth(token: string) {
|
||||
if (token == useAuth.biliToken) {
|
||||
message.info('当前正在使用该账号')
|
||||
return
|
||||
}
|
||||
useAuth.setCurrentAuth(token)
|
||||
message.success('已切换账号')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpin :show="useAuth.isLoading">
|
||||
<NFlex justify="center" align="center">
|
||||
<NCard title="更多" embedded>
|
||||
<NCollapse>
|
||||
<NCollapseItem title="收货地址" name="1">
|
||||
<NFlex vertical>
|
||||
<NButton @click="onOpenAddressModal" type="primary"> 添加地址 </NButton>
|
||||
<NList size="small" bordered>
|
||||
<NListItem v-for="address in biliAuth.address" :key="address.id">
|
||||
<AddressDisplay :address="address">
|
||||
<template #actions>
|
||||
<NButton
|
||||
size="small"
|
||||
@click="
|
||||
() => {
|
||||
currentAddress = address
|
||||
showAddressModal = true
|
||||
}
|
||||
"
|
||||
type="info"
|
||||
>
|
||||
修改
|
||||
</NButton>
|
||||
<NPopconfirm @positive-click="() => deleteAddress(address?.id ?? '')">
|
||||
<template #trigger>
|
||||
<NButton size="small" type="error"> 删除 </NButton>
|
||||
</template>
|
||||
确定要删除这个收货信息吗?
|
||||
</NPopconfirm>
|
||||
</template>
|
||||
</AddressDisplay>
|
||||
</NListItem>
|
||||
</NList>
|
||||
</NFlex>
|
||||
</NCollapseItem>
|
||||
</NCollapse>
|
||||
</NCard>
|
||||
<NCard title="账号操作" embedded>
|
||||
<NDivider> 切换账号 </NDivider>
|
||||
<NList clickable bordered>
|
||||
<NListItem v-for="item in useAuth.biliTokens" :key="item.token" @click="switchAuth(item.token)">
|
||||
<NFlex align="center">
|
||||
<NTag v-if="useAuth.biliToken == item.token" type="info"> 当前账号 </NTag>
|
||||
{{ item.uId }}
|
||||
</NFlex>
|
||||
</NListItem>
|
||||
</NList>
|
||||
</NCard>
|
||||
</NFlex>
|
||||
</NSpin>
|
||||
<NModal
|
||||
v-model:show="showAddressModal"
|
||||
preset="card"
|
||||
style="width: 800px; max-width: 90vw; height: auto"
|
||||
title="添加/更新地址"
|
||||
>
|
||||
<NSpin v-if="currentAddress" :show="isLoading">
|
||||
<NForm ref="formRef" :model="currentAddress" :rules="rules">
|
||||
<NFormItem label="地址" path="area" required>
|
||||
<NFlex style="width: 100%">
|
||||
<NSelect
|
||||
v-model:value="currentAddress.province"
|
||||
:options="provinceOptions"
|
||||
@update:value="onAreaSelectChange(0)"
|
||||
placeholder="请选择省"
|
||||
style="width: 100px"
|
||||
filterable
|
||||
/>
|
||||
<NSelect
|
||||
v-model:value="currentAddress.city"
|
||||
:key="currentAddress.province"
|
||||
:options="cityOptions(currentAddress.province)"
|
||||
:disabled="!currentAddress?.province"
|
||||
@update:value="onAreaSelectChange(1)"
|
||||
placeholder="请选择市"
|
||||
style="width: 100px"
|
||||
filterable
|
||||
/>
|
||||
<NSelect
|
||||
v-model:value="currentAddress.district"
|
||||
:key="currentAddress.city"
|
||||
:options="districtOptions(currentAddress.province, currentAddress.city)"
|
||||
:disabled="!currentAddress?.city"
|
||||
@update:value="onAreaSelectChange(2)"
|
||||
placeholder="请选择区"
|
||||
style="width: 100px"
|
||||
filterable
|
||||
/>
|
||||
<NSelect
|
||||
v-model:value="currentAddress.street"
|
||||
:key="currentAddress.district"
|
||||
:options="streetOptions(currentAddress.province, currentAddress.city, currentAddress.district)"
|
||||
:disabled="!currentAddress?.district"
|
||||
placeholder="请选择街道"
|
||||
style="width: 150px"
|
||||
filterable
|
||||
/>
|
||||
</NFlex>
|
||||
</NFormItem>
|
||||
<NFormItem label="详细地址" path="address" required>
|
||||
<NInput v-model:value="currentAddress.address" placeholder="详细地址" type="textarea" />
|
||||
</NFormItem>
|
||||
<NFormItem label="联系电话" path="phone" required>
|
||||
<NInputNumber
|
||||
v-model:value="currentAddress.phone"
|
||||
placeholder="联系电话"
|
||||
:show-button="false"
|
||||
style="width: 200px"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="联系人" path="name" required>
|
||||
<NInput v-model:value="currentAddress.name" placeholder="联系人" style="max-width: 150px" />
|
||||
</NFormItem>
|
||||
<NFormItem label="用户协议" required>
|
||||
<NCheckbox v-model:checked="userAgree">
|
||||
阅读并同意本站
|
||||
<NButton text @click="showAgreementModal = true" type="info"> 用户协议 </NButton>
|
||||
</NCheckbox>
|
||||
</NFormItem>
|
||||
<NButton @click="updateAddress" type="info" :loading="isLoading"> 保存 </NButton>
|
||||
</NForm>
|
||||
</NSpin>
|
||||
</NModal>
|
||||
<NModal
|
||||
v-model:show="showAgreementModal"
|
||||
title="用户协议"
|
||||
preset="card"
|
||||
style="width: 800px; max-width: 90vw; height: 90vh"
|
||||
>
|
||||
<NScrollbar style="height: 80vh"> <UserAgreement /></NScrollbar>
|
||||
</NModal>
|
||||
</template>
|
||||
Reference in New Issue
Block a user