Skip to content

Commit 46275bf

Browse files
author
Kerwin
committed
chore: allow filter user statistic(Close #378)
1 parent d372aa0 commit 46275bf

File tree

4 files changed

+54
-12
lines changed

4 files changed

+54
-12
lines changed

service/src/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import { authLimiter, limiter } from './middleware/limiter'
5151
import { hasAnyRole, isEmail, isNotEmptyString } from './utils/is'
5252
import { sendNoticeMail, sendResetPasswordMail, sendTestMail, sendVerifyMail, sendVerifyMailAdmin } from './utils/mail'
5353
import { checkUserResetPassword, checkUserVerify, checkUserVerifyAdmin, getUserResetPasswordUrl, getUserVerifyUrl, getUserVerifyUrlAdmin, md5 } from './utils/security'
54-
import { rootAuth } from './middleware/rootAuth'
54+
import { isAdmin, rootAuth } from './middleware/rootAuth'
5555

5656
dotenv.config()
5757

@@ -553,9 +553,7 @@ router.post('/user-register', authLimiter, async (req, res) => {
553553
router.post('/config', rootAuth, async (req, res) => {
554554
try {
555555
const userId = req.headers.userId.toString()
556-
557-
const user = await getUserById(userId)
558-
if (user == null || user.status !== Status.Normal || !user.roles.includes(UserRole.Admin))
556+
if (!isAdmin(userId))
559557
throw new Error('无权限 | No permission.')
560558

561559
const response = await chatConfig()
@@ -1107,8 +1105,11 @@ router.post('/setting-key-upsert', rootAuth, async (req, res) => {
11071105

11081106
router.post('/statistics/by-day', auth, async (req, res) => {
11091107
try {
1110-
const userId = req.headers.userId
1111-
const { start, end } = req.body as { start: number; end: number }
1108+
let { userId, start, end } = req.body as { userId?: string; start: number; end: number }
1109+
if (!userId)
1110+
userId = req.headers.userId as string
1111+
else if (!isAdmin(req.headers.userId as string))
1112+
throw new Error('无权限 | No permission')
11121113

11131114
const data = await getUserStatisticsByDay(new ObjectId(userId as string), start, end)
11141115
res.send({ status: 'Success', message: '', data })

service/src/middleware/rootAuth.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,9 @@ const rootAuth = async (req, res, next) => {
2828
}
2929
}
3030

31-
export { rootAuth }
31+
const isAdmin = async (userId: string) => {
32+
const user = await getUserById(userId)
33+
return user != null && user.status === Status.Normal && user.roles.includes(UserRole.Admin)
34+
}
35+
36+
export { rootAuth, isAdmin }

src/api/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,10 @@ export function fetchUpdateBaseSetting<T = any>(config: ConfigState) {
288288
})
289289
}
290290

291-
export function fetchUserStatistics<T = any>(start: number, end: number) {
291+
export function fetchUserStatistics<T = any>(userId: string, start: number, end: number) {
292292
return post<T>({
293293
url: '/statistics/by-day',
294-
data: { start, end },
294+
data: { userId, start, end },
295295
})
296296
}
297297

src/components/common/Setting/Statistics.vue

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
<script setup lang='ts'>
2+
import type { Ref } from 'vue'
23
import { nextTick, onMounted, reactive, ref } from 'vue'
3-
import { NCol, NDatePicker, NIcon, NNumberAnimation, NRow, NSpin, NStatistic } from 'naive-ui'
4+
import { NCol, NDatePicker, NIcon, NNumberAnimation, NRow, NSelect, NSpin, NStatistic } from 'naive-ui'
45
import type { ChartData, ChartOptions } from 'chart.js'
56
import { BarElement, CategoryScale, Chart as ChartJS, Legend, LinearScale, Title, Tooltip } from 'chart.js'
67
import { Bar } from 'vue-chartjs'
78
import dayjs from 'dayjs'
9+
import type { UserInfo } from './model'
810
import { t } from '@/locales'
9-
import { fetchUserStatistics } from '@/api'
11+
import { fetchGetUsers, fetchUserStatistics } from '@/api'
1012
import { SvgIcon } from '@/components/common'
13+
import { useUserStore } from '@/store'
1114
1215
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
1316
17+
const userStore = useUserStore()
18+
1419
const chartData: ChartData<'bar'> = reactive({
1520
labels: [],
1621
datasets: [
@@ -38,6 +43,10 @@ const summary = ref({
3843
completionTokens: 0,
3944
totalTokens: 0,
4045
})
46+
47+
const usersOptions: Ref<{ label: string; filter: string; value: string }[]> = ref([])
48+
const user: Ref<string | null> = ref(null)
49+
4150
const loading = ref(false)
4251
const range: any = ref([
4352
dayjs().subtract(30, 'day').startOf('day').valueOf(),
@@ -66,6 +75,7 @@ async function fetchStatistics() {
6675
try {
6776
loading.value = true
6877
const { data } = await fetchUserStatistics(
78+
user.value as string,
6979
dayjs(range.value[0]).startOf('day').valueOf(),
7080
dayjs(range.value[1]).endOf('day').valueOf(),
7181
)
@@ -91,8 +101,25 @@ async function fetchStatistics() {
91101
}
92102
}
93103
104+
async function fetchUsers() {
105+
const result = await fetchGetUsers(1, 10000)
106+
result.data.users.forEach((user: UserInfo) => {
107+
usersOptions.value.push({
108+
label: `${user.email}`,
109+
value: `${user._id}`,
110+
filter: `${user.email} ${user.remark}`,
111+
})
112+
})
113+
}
114+
115+
function filter(pattern: string, option: object): boolean {
116+
const a = option as { label: string; filter: string; value: string }
117+
return !a.filter ? false : a.filter.includes(pattern)
118+
}
119+
94120
onMounted(() => {
95121
fetchStatistics()
122+
fetchUsers()
96123
})
97124
</script>
98125

@@ -101,7 +128,16 @@ onMounted(() => {
101128
<div class="p-4 space-y-5 min-h-[200px]">
102129
<div class="space-y-6">
103130
<div class="flex items-center space-x-4">
104-
<span class="flex-shrink-0 w-[100px]">{{ $t('setting.statisticsPeriod') }}</span>
131+
<NSelect
132+
v-if="userStore.userInfo.root"
133+
v-model:value="user"
134+
style="width: 250px"
135+
filterable
136+
:filter="filter"
137+
placeholder="Email or remark"
138+
:options="usersOptions"
139+
@update-value="(value) => { user = value; fetchStatistics() }"
140+
/>
105141
<div class="flex-1">
106142
<NDatePicker
107143
v-model:value="range"

0 commit comments

Comments
 (0)