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:
@@ -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)
|
||||
})
|
||||
|
||||
47
src/components/manage/AddressDisplay.vue
Normal file
47
src/components/manage/AddressDisplay.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
115
src/components/manage/PointHistoryCard.vue
Normal file
115
src/components/manage/PointHistoryCard.vue
Normal 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>
|
||||
184
src/components/manage/PointOrderCard.vue
Normal file
184
src/components/manage/PointOrderCard.vue
Normal 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>
|
||||
Reference in New Issue
Block a user