songlist add import from file, partically complete point system

This commit is contained in:
2024-02-10 13:05:18 +08:00
parent a69fd44706
commit ae576ed20c
39 changed files with 3629 additions and 420 deletions

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import { useProviderStore } from '@/store/useProviderStore'
import { useLoadingBarStore } from '@/store/useLoadingBarStore'
import { useLoadingBar } from 'naive-ui'
import { onMounted } from 'vue'
// Setup code
onMounted(() => {
const providerStore = useProviderStore()
const providerStore = useLoadingBarStore()
const loadingBar = useLoadingBar()
providerStore.setLoadingBar(loadingBar)
})

View File

@@ -0,0 +1,47 @@
<script setup lang="ts">
import { AddressInfo } from '@/api/api-models'
import { useElementSize } from '@vueuse/core'
import { NButton, NFlex, NPopconfirm, NTag, NText } from 'naive-ui'
import { ref } from 'vue'
const { size = 'default' } = defineProps<{
address: AddressInfo | undefined
size?: 'small' | 'default'
}>()
const elementRef = ref()
const { height } = useElementSize(elementRef.value)
</script>
<template>
<NText v-if="!address" depth="3" italic> 未知 </NText>
<NFlex v-else ref="elementRef">
<NFlex vertical :size="5">
<NText v-if="size != 'small'">
{{ address.province }}
<NText depth="3"> </NText>
{{ address.city }}
<NText depth="3"> </NText>
{{ address.district }}
<NText depth="3"> </NText>
{{ address.street }}
</NText>
<NText depth="3">
<NFlex align="center">
<NTag size="tiny" type="info" :bordered="false"> 详细地址 </NTag>
{{ address.address }}
</NFlex>
</NText>
<NText v-if="size != 'small'" depth="3">
<NFlex align="center">
<NTag size="tiny" type="info" :bordered="false"> 收货人 </NTag>
<span> {{ address.phone }} {{ address.name }} </span>
</NFlex>
</NText>
</NFlex>
<NFlex style="flex: 1" justify="end" align="center">
<slot name="actions"></slot>
</NFlex>
</NFlex>
</template>

View File

@@ -1,45 +1,57 @@
<script setup lang="ts">
import { ResponsePointGoodModel } from '@/api/api-models'
import { NButton, NCard, NDropdown, NEllipsis, NFlex, NIcon, NImage, NPopselect, NTag, NText } from 'naive-ui'
import { GoodsTypes, ResponsePointGoodModel } from '@/api/api-models'
import { NButton, NCard, NDropdown, NEllipsis, NEmpty, NFlex, NIcon, NImage, NPopselect, NTag, NText } from 'naive-ui'
import { FILE_BASE_URL, IMGUR_URL } from '@/data/constants'
import { computed, ref } from 'vue'
import { MoreHorizontal16Filled, MoreVertical16Filled } from '@vicons/fluent'
const props = defineProps<{
goods: ResponsePointGoodModel
goods: ResponsePointGoodModel | undefined
}>()
const emptyCover = IMGUR_URL + 'None.png'
</script>
<template>
<NCard>
<NEmpty v-if="!goods" description="已失效" />
<NCard v-else embedded>
<template #cover>
<NImage :src="goods.cover ? FILE_BASE_URL + goods.cover : emptyCover" :fallback-src="emptyCover" height="150" object-fit="cover" :preview-disabled="!goods.cover" style="width: 100%" />
<NImage
:src="goods.cover ? FILE_BASE_URL + goods.cover : emptyCover"
:fallback-src="emptyCover"
height="150"
object-fit="cover"
:preview-disabled="!goods.cover"
style="width: 100%"
/>
</template>
<template #header-extra>
<slot name="header-extra"></slot>
</template>
<template #header>
<NEllipsis>
{{ goods.name }}
</NEllipsis>
</template>
<NFlex vertical>
<NText depth="3" :italic="!goods.description">
{{ goods.description }}
</NText>
<NFlex>
<NTag v-for="tag in goods.tags" :key="tag" :bordered="false">{{ tag }}</NTag>
</NFlex>
<NFlex justify="space-between">
<NFlex>
<NText> 库存: </NText>
<NText depth="3"> 库存: </NText>
<NText v-if="goods.count && goods.count > -1">
{{ goods.count }}
</NText>
<NText v-else> 不限 </NText>
<NText v-else> </NText>
</NFlex>
</NFlex>
</template>
<template #header>
<NFlex align="center">
<NTag size="small" :bordered="goods.type != GoodsTypes.Physical">
{{ goods.type == GoodsTypes.Physical ? '实物' : '虚拟' }}
</NTag>
<NEllipsis>
{{ goods.name }}
</NEllipsis>
</NFlex>
</template>
<NFlex vertical>
<NText :depth="goods.description ? 1 : 3" :italic="!goods.description">
{{ goods.description ? goods.description : '暂无描述' }}
</NText>
<NFlex>
<NTag v-for="tag in goods.tags" :key="tag" :bordered="false" size="tiny">{{ tag }}</NTag>
</NFlex>
</NFlex>
<template #footer>
<slot name="footer"></slot>

View File

@@ -0,0 +1,115 @@
<script setup lang="ts">
import { DataTableColumns, NDataTable, NDivider, NFlex, NTag, NText, NTime, NTooltip } from 'naive-ui'
import { h } from 'vue'
import { EventDataTypes, PointFrom, ResponsePointHisrotyModel } from '@/api/api-models'
const props = defineProps<{
histories: ResponsePointHisrotyModel[]
}>()
const historyColumn: DataTableColumns<ResponsePointHisrotyModel> = [
{
title: '时间',
key: 'createAt',
sorter: 'default',
render: (row: ResponsePointHisrotyModel) => {
return h(NTooltip, null, {
trigger: () => h(NTime, { time: row.createAt, type: 'relative' }),
default: () => h(NTime, { time: row.createAt }),
})
},
},
{
title: '积分变动',
key: 'point',
render: (row: ResponsePointHisrotyModel) => {
return h(NText, { style: { color: row.from === PointFrom.Use ? 'red' : 'green' } }, () => (row.from === PointFrom.Use ? '' : '+') + row.point)
},
},
{
title: '来自',
key: 'from',
filter(value, row) {
return ~row.from == value
},
filterOptions: [
{
label: '直播间',
value: PointFrom.Danmaku,
},
{
label: '手动',
value: PointFrom.Manual,
},
{
label: '使用',
value: PointFrom.Use,
},
],
render: (row: ResponsePointHisrotyModel) => {
const get = () => {
switch (row.from) {
case PointFrom.Danmaku:
return h(NTag, { type: 'info', bordered: false, size: 'small' }, () => '直播间')
case PointFrom.Manual:
return h(NTag, { type: 'success', bordered: false, size: 'small' }, () => '手动')
case PointFrom.Use:
return h(NTag, { type: 'warning', bordered: false, size: 'small' }, () => '使用')
}
}
return h(NFlex, {}, () => get())
},
},
{
title: '详情',
key: 'action',
render: (row: ResponsePointHisrotyModel) => {
switch (row.from) {
case PointFrom.Danmaku:
switch (row.type) {
case EventDataTypes.Guard:
return h(NFlex, { justify: 'center', align: 'center' }, () => [
h(NTag, { type: 'info', size: 'small' }, () => '上舰'),
h(NDivider, { vertical: true, style: { margin: '0' } }),
row.extra?.msg,
])
case EventDataTypes.Gift:
return h(NFlex, { justify: 'center' }, () => [
h(NTag, { type: 'info', size: 'small', style: { margin: '0' } }, () => '礼物'),
h(NDivider, { vertical: true }),
row.extra?.msg,
])
case EventDataTypes.SC:
return h(NFlex, { justify: 'center' }, () => [
h(NTag, { type: 'info', size: 'small', style: { margin: '0' } }, () => 'SC'),
h(NDivider, { vertical: true }),
row.extra?.price,
])
}
case PointFrom.Manual:
return h(NFlex, { align: 'center' }, () => [
h(NTag, { type: 'info', size: 'small', style: { margin: '0' } }, () => '备注'),
h(NDivider, { vertical: true }),
h(NText, { depth: 3 }, () => row.extra ?? h(NText, { italic: true, depth: '3' }, () => '未提供')),
])
case PointFrom.Use:
return h(NFlex, { align: 'center' }, () => [
h(NTag, { type: 'success', size: 'small', style: { margin: '0' }, strong: true }, () => '购买'),
h(NDivider, { vertical: true }),
row.extra,
])
}
},
},
]
</script>
<template>
<NDataTable
:columns="historyColumn"
:data="histories"
:pagination="{ showSizePicker: true, pageSizes: [10, 25, 50, 100], defaultPageSize: 10, size: 'small' }"
>
</NDataTable>
</template>

View File

@@ -0,0 +1,184 @@
<script setup lang="ts">
import {
GoodsTypes,
PointOrderStatus,
ResponsePointGoodModel,
ResponsePointOrder2OwnerModel,
ResponsePointOrder2UserModel,
} from '@/api/api-models'
import {
DataTableColumns,
NButton,
NCard,
NDataTable,
NDivider,
NFlex,
NIcon,
NInput,
NModal,
NScrollbar,
NTag,
NText,
NTime,
NTooltip,
} from 'naive-ui'
import { computed, h, ref, watch } from 'vue'
import AddressDisplay from './AddressDisplay.vue'
import PointGoodsItem from './PointGoodsItem.vue'
import { Info24Filled } from '@vicons/fluent'
const props = defineProps<{
order: ResponsePointOrder2UserModel[] | ResponsePointOrder2OwnerModel[]
type: 'user' | 'owner'
goods?: ResponsePointGoodModel[]
loading?: boolean
}>()
const isLoading = ref(false)
watch(
() => props.loading,
() => {
isLoading.value = props.loading
},
)
const orderAsUser = computed(() => {
return props.order as ResponsePointOrder2UserModel[]
})
const orderAsOwner = computed(() => {
return props.order as ResponsePointOrder2OwnerModel[]
})
const showDetailModal = ref(false)
const orderDetail = ref<ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel>()
const currentGoods = computed(() => {
//@ts-ignore
if (props.type == 'user') return orderDetail.value.goods
//@ts-ignore
else return props.goods.find((g) => g.id == orderDetail.value.goodsId)
})
const orderColumn: DataTableColumns<ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel> = [
{
title: '订单号',
key: 'id',
},
{
title: '时间',
key: 'time',
sorter: 'default',
render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => {
return h(NTime, { time: row.createAt })
},
},
{
title: '使用积分',
key: 'point',
},
{
title: '订单状态',
key: 'status',
sorter: 'default',
render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => {
switch (row.status) {
case PointOrderStatus.Pending:
return h(NTag, { size: 'small' }, () => '等待发货')
case PointOrderStatus.Shipped:
return h(NTag, { size: 'small', type: 'info' }, () => '已发货')
case PointOrderStatus.Completed:
return h(NTag, { size: 'small', type: 'success' }, () => '已完成')
}
},
},
{
title: '订单类型',
key: 'type',
filter: (filterOptionValue: unknown, row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => {
return row.type == filterOptionValue
},
filterOptions: [
{
label: '实体礼物',
value: GoodsTypes.Physical,
},
{
label: '虚拟礼物',
value: GoodsTypes.Virtual,
},
],
render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => {
return h(NTag, { type: 'success', bordered: false, size: 'small' }, () =>
row.type == GoodsTypes.Physical ? '实体礼物' : '虚拟礼物',
)
},
},
{
title: '地址',
key: 'address',
render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => {
if (row.type == GoodsTypes.Physical) {
return h(AddressDisplay, { address: row.address })
} else {
return h(NText, { depth: 3 }, () => '无需发货')
}
},
},
{
title: '操作',
key: 'action',
render: (row: ResponsePointOrder2UserModel | ResponsePointOrder2OwnerModel) => {
return h(
NButton,
{
type: 'info',
size: 'small',
onClick: () => {
orderDetail.value = row
showDetailModal.value = true
},
},
{ default: () => '详情' },
)
},
},
]
</script>
<template>
<NDataTable
:loading="isLoading"
:columns="orderColumn"
:data="order"
:pagination="{ showSizePicker: true, pageSizes: [10, 25, 50, 100], defaultPageSize: 10, size: 'small' }"
>
</NDataTable>
<NModal
v-if="orderDetail"
v-model:show="showDetailModal"
preset="card"
title="订单详情"
style="max-width: 800px; max-height: 90vh"
>
<NScrollbar style="max-height: 80vh">
<div style="width: 97%">
<template v-if="type == 'user'">
<NDivider style="margin-top: 0">
商品快照
<NTooltip>
<template #trigger>
<NIcon :component="Info24Filled" />
</template>
兑换成功时生成的礼物快照, 即使主播对礼物内容进行了修改这个地方也不会变化
</NTooltip>
</NDivider>
<NFlex justify="center">
<PointGoodsItem style="max-width: 300px" :goods="currentGoods" />
</NFlex>
<template v-if="orderDetail.type == GoodsTypes.Virtual">
<NDivider> 虚拟礼物内容 </NDivider>
<NInput :value="currentGoods?.content" type="textarea" readonly placeholder="无内容" />
</template>
</template>
<template v-else-if="type == 'owner'"> </template>
</div>
</NScrollbar>
</NModal>
</template>