Skip to content

Commit 50665f6

Browse files
authored
feat: add upload gift card page (#510)
* feat: add upload gift card page 新增了一个上传giftcard的页面 * chore: change deleteMany to drop change deletemany to drop when overRide giftcards DB
1 parent 5899b04 commit 50665f6

File tree

10 files changed

+218
-2
lines changed

10 files changed

+218
-2
lines changed

service/src/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import type { ChatMessage } from './chatgpt'
1111
import { abortChatProcess, chatConfig, chatReplyProcess, containsSensitiveWords, initAuditService } from './chatgpt'
1212
import { auth, getUserId } from './middleware/auth'
1313
import { clearApiKeyCache, clearConfigCache, getApiKeys, getCacheApiKeys, getCacheConfig, getOriginConfig } from './storage/config'
14-
import type { AnnounceConfig, AuditConfig, ChatInfo, ChatOptions, Config, KeyConfig, MailConfig, SiteConfig, UserConfig, UserInfo } from './storage/model'
14+
import type { AnnounceConfig, AuditConfig, ChatInfo, ChatOptions, Config, GiftCard, KeyConfig, MailConfig, SiteConfig, UserConfig, UserInfo } from './storage/model'
1515
import { AdvancedConfig, Status, UsageResponse, UserRole } from './storage/model'
1616
import {
1717
clearChat,
@@ -39,6 +39,7 @@ import {
3939
updateChat,
4040
updateConfig,
4141
updateGiftCard,
42+
updateGiftCards,
4243
updateRoomChatModel,
4344
updateRoomPrompt,
4445
updateRoomUsingContext,
@@ -888,6 +889,18 @@ router.post('/redeem-card', auth, async (req, res) => {
888889
}
889890
})
890891

892+
// update giftcard database
893+
router.post('/giftcard-update', rootAuth, async (req, res) => {
894+
try {
895+
const { data, overRideSwitch } = req.body as { data: GiftCard[];overRideSwitch: boolean }
896+
await updateGiftCards(data, overRideSwitch)
897+
res.send({ status: 'Success', message: '更新成功 | Update successfully' })
898+
}
899+
catch (error) {
900+
res.send({ status: 'Fail', message: error.message, data: null })
901+
}
902+
})
903+
891904
router.post('/user-chat-model', auth, async (req, res) => {
892905
try {
893906
const { chatModel } = req.body as { chatModel: string }

service/src/storage/mongo.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ export async function updateAmountMinusOne(userId: string) {
6767
return result.modifiedCount > 0
6868
}
6969

70+
// update giftcards database
71+
export async function updateGiftCards(data: GiftCard[], overRide = true) {
72+
if (overRide) {
73+
// i am not sure is there a drop option for the node driver reference https://mongodb.github.io/node-mongodb-native/6.4/
74+
// await redeemCol.deleteMany({})
75+
await redeemCol.drop()
76+
}
77+
const insertResult = await redeemCol.insertMany(data)
78+
return insertResult
79+
}
80+
7081
export async function insertChat(uuid: number, text: string, images: string[], roomId: number, options?: ChatOptions) {
7182
const chatInfo = new ChatInfo(roomId, uuid, text, images, options)
7283
await chatCol.insertOne(chatInfo)

src/api/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AxiosProgressEvent, GenericAbortSignal } from 'axios'
22
import { get, post } from '@/utils/request'
3-
import type { AnnounceConfig, AuditConfig, ConfigState, KeyConfig, MailConfig, SiteConfig, Status, UserInfo, UserPassword } from '@/components/common/Setting/model'
3+
import type { AnnounceConfig, AuditConfig, ConfigState, GiftCard, KeyConfig, MailConfig, SiteConfig, Status, UserInfo, UserPassword } from '@/components/common/Setting/model'
44
import { useAuthStore, useUserStore } from '@/store'
55
import type { SettingsState } from '@/store/modules/user/helper'
66

@@ -153,6 +153,13 @@ export function decode_redeemcard<T = any>(redeemCardNo: string) {
153153
})
154154
}
155155

156+
export function fetchUpdateGiftCards<T = any>(data: GiftCard[], overRideSwitch: boolean) {
157+
return post<T>({
158+
url: '/giftcard-update',
159+
data: { data, overRideSwitch },
160+
})
161+
}
162+
156163
export function fetchUpdateUserChatModel<T = any>(chatModel: string) {
157164
return post<T>({
158165
url: '/user-chat-model',
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<script setup lang='ts'>
2+
import { ref } from 'vue'
3+
import type { UploadFileInfo } from 'naive-ui'
4+
import { NButton, NDataTable, NDivider, NIcon, NP, NSpace, NSwitch, NText, NUpload, NUploadDragger, useMessage } from 'naive-ui'
5+
import type { GiftCard } from './model'
6+
import { SvgIcon } from '@/components/common'
7+
import { fetchUpdateGiftCards } from '@/api'
8+
import { t } from '@/locales'
9+
10+
const ms = useMessage()
11+
const loading = ref(false)
12+
const overRideSwitch = ref(true)
13+
const fileListRef = ref<UploadFileInfo[]>([])
14+
15+
const handleSaving = ref(false)
16+
const columns = [
17+
{
18+
title: 'cardno',
19+
key: 'cardno',
20+
resizable: true,
21+
width: 100,
22+
minWidth: 100,
23+
maxWidth: 200,
24+
},
25+
{
26+
title: 'amount',
27+
key: 'amount',
28+
width: 80,
29+
},
30+
{
31+
title: 'redeemed',
32+
key: 'redeemed',
33+
width: 100,
34+
},
35+
]
36+
37+
const csvData = ref<Array<GiftCard>>([])
38+
39+
// const csvData: giftcard[] = [
40+
// {
41+
// cardno: 'dfsdfasf',
42+
// amount: 10,
43+
// redeemed: 0,
44+
// },
45+
// {
46+
// cardno: 'ooioioo',
47+
// amount: 20,
48+
// redeemed: 0,
49+
// },
50+
// {
51+
// cardno: '765653',
52+
// amount: 30,
53+
// redeemed: 1,
54+
// },
55+
// ]
56+
57+
function readfile(file: Blob) {
58+
try {
59+
// const file = event.target.files[0]
60+
if (file) {
61+
ms.info('生成预览中 | Generating Preview')
62+
const reader = new FileReader()
63+
reader.onload = (e) => {
64+
const contents = e.target?.result as string
65+
csvData.value = parseCSV(contents)
66+
}
67+
reader.readAsText(file)
68+
}
69+
else {
70+
ms.info('没有读取到文件 | No file find')
71+
}
72+
}
73+
catch (error: any) {
74+
ms.info(`读取文件出错 | Error reading file | ${error.message}`)
75+
}
76+
}
77+
78+
function parseCSV(content: string) {
79+
const rows = content.trim().split(/\r?\n/)
80+
// const headers = rows[0].split(',')
81+
const giftCards: GiftCard[] = rows.slice(1).map(row => row.split(',')).map(row => ({
82+
cardno: row[0],
83+
amount: Number(row[1].trim()),
84+
redeemed: Number(row[2].trim()),
85+
}))
86+
return giftCards
87+
}
88+
89+
function handleUploadChange(data: { file: UploadFileInfo, fileList: Array<UploadFileInfo>, event?: Event }) {
90+
fileListRef.value = data.fileList
91+
csvData.value = []
92+
if (data.event) {
93+
const file_bolb = data.fileList[0].file
94+
if (file_bolb)
95+
readfile(file_bolb)
96+
}
97+
}
98+
99+
async function uploadGiftCards() {
100+
handleSaving.value = true
101+
try {
102+
if (csvData.value.length > 0)
103+
await fetchUpdateGiftCards(csvData.value, overRideSwitch.value)
104+
ms.success(`${t('common.success')}`)
105+
}
106+
catch (error: any) {
107+
ms.error(`Failed update DB ${error.message}`)
108+
}
109+
110+
handleSaving.value = false
111+
}
112+
</script>
113+
114+
<template>
115+
<div class="p-4 space-y-5 min-h-[300px]">
116+
<div class="space-y-6">
117+
<NUpload
118+
:max="1"
119+
accept=".csv"
120+
:on-change="handleUploadChange"
121+
>
122+
<NUploadDragger>
123+
<div style="margin-bottom: 12px">
124+
<NIcon size="48" :depth="3">
125+
<SvgIcon icon="mage:box-upload" />
126+
</NIcon>
127+
</div>
128+
<NText style="font-size: 16px">
129+
点击或者拖动文件到该区域来上传|Upload CSV
130+
</NText>
131+
<NP depth="3" style="margin: 8px 0 0 0">
132+
请不要上传敏感数据,文件仅限csv(2k行内),表头为cardno,amount,redeemed<br>
133+
warning: duplicated cardno will not be detected in this process
134+
</NP>
135+
</NUploadDragger>
136+
</NUpload>
137+
138+
<NSpace vertical :size="12">
139+
<span class="flex-shrink-0 w-[100px]">Data Preview(Top 30) & Due to body-parser limits csv files >2k rows not supported </span>
140+
<NDataTable
141+
remote
142+
:loading="loading"
143+
:row-key="(rowData:GiftCard) => rowData.cardno"
144+
:columns="columns"
145+
:data="csvData.slice(0, 30)"
146+
:max-height="200"
147+
/>
148+
</NSpace>
149+
</div>
150+
<NDivider />
151+
<div class="flex items-center space-x-4">
152+
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.overRide') }}</span>
153+
<div class="flex-1">
154+
<NSwitch v-model:value="overRideSwitch" />
155+
</div>
156+
<div class="flex-1">
157+
<NButton type="primary" :loading="handleSaving" size="large" @click="uploadGiftCards()">
158+
{{ $t('setting.uploadgifts') }}
159+
</NButton>
160+
</div>
161+
</div>
162+
</div>
163+
</template>

src/components/common/Setting/index.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import About from './About.vue'
88
import Site from './Site.vue'
99
import Mail from './Mail.vue'
1010
import Audit from './Audit.vue'
11+
import Gift from './Gift.vue'
1112
import User from './User.vue'
1213
import Key from './Keys.vue'
1314
import Password from './Password.vue'
@@ -141,6 +142,13 @@ const show = computed({
141142
</template>
142143
<Key />
143144
</NTabPane>
145+
<NTabPane v-if="userStore.userInfo.root" name="GiftCardConfig" tab="GiftCardConfig">
146+
<template #tab>
147+
<SvgIcon class="text-lg" icon="mdi-gift" />
148+
<span class="ml-2">{{ $t('setting.uploadgifts') }}</span>
149+
</template>
150+
<Gift />
151+
</NTabPane>
144152
</NTabs>
145153
</div>
146154
</NModal>

src/components/common/Setting/model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,9 @@ export class TwoFAConfig {
162162
this.testCode = ''
163163
}
164164
}
165+
166+
export interface GiftCard {
167+
cardno: string
168+
amount: number
169+
redeemed: number
170+
}

src/locales/en-US.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export default {
8080
disable2FAConfirm: 'Are you sure to disable 2FA for this user?',
8181
},
8282
setting: {
83+
overRide: 'Enable Override',
84+
uploadgifts: 'Upload Redemption Code',
8385
announceConfig: 'Announcement',
8486
announceEnabled: 'Open Announcement',
8587
announceWords: 'Announcement Content',

src/locales/ko-KR.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export default {
8080
disable2FAConfirm: 'Are you sure to disable 2FA for this user?',
8181
},
8282
setting: {
83+
overRide: '덮어쓰기 활성화',
84+
uploadgifts: '교환 코드 업로드',
8385
announceConfig: '网站公告',
8486
announceEnabled: 'Open Announcement',
8587
announceWords: 'Announcement Content',

src/locales/zh-CN.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export default {
8080
disable2FAConfirm: '您确定要为此用户禁用两步验证吗??',
8181
},
8282
setting: {
83+
overRide: '开启覆写',
84+
uploadgifts: '上传兑换码',
8385
announceConfig: '网站公告',
8486
announceEnabled: '公告开关',
8587
announceWords: '公告内容',

src/locales/zh-TW.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export default {
8080
disable2FAConfirm: '您确定要为此用户禁用两步验证吗??',
8181
},
8282
setting: {
83+
overRide: '開啟覆寫',
84+
uploadgifts: '上傳兌換碼',
8385
announceConfig: '网站公告',
8486
announceEnabled: '打开公告',
8587
announceWords: '公告内容',

0 commit comments

Comments
 (0)