Compare commits

..

10 Commits

Author SHA1 Message Date
Rogee
9b38699764 feat: fix duration
Some checks failed
build quyun abc / Build (push) Failing after 1m28s
2025-05-22 09:57:17 +08:00
Rogee
5798b01376 feat: send 2025-05-21 21:31:48 +08:00
Rogee
998941511f fix: send issues 2025-05-21 21:30:15 +08:00
Rogee
69ac9ddd9c fix:duration issues 2025-05-21 21:24:40 +08:00
Rogee
b68fb0aac2 feat: fix expiration 2025-05-21 21:21:43 +08:00
Rogee
6b1d9d5feb feat: update article list 2025-05-19 15:25:59 +08:00
Rogee
ab1dec1f79 feat: add user infos 2025-05-16 12:02:55 +08:00
Rogee
9f488cbb7e fix: admin page 2025-05-16 11:55:58 +08:00
Rogee
f950fc0a8e fix: balance issue 2025-05-15 16:25:57 +08:00
Rogee
7ebab50d1e feat: update routes 2025-05-15 10:54:44 +08:00
9 changed files with 54 additions and 37 deletions

View File

@@ -138,4 +138,10 @@ func (r *Routes) Register(router fiber.Router) {
Query[requests.Pagination]("pagination"), Query[requests.Pagination]("pagination"),
)) ))
router.Post("/admin/users/:id/balance", Func2(
r.users.Balance,
PathParam[int64]("id"),
Body[UserBalance]("balance"),
))
} }

View File

@@ -194,7 +194,11 @@ func (ctl *posts) Play(ctx fiber.Ctx, id int64, user *model.Users) (*PlayUrl, er
log.WithError(err).Errorf("medias GetByID err: %v", err) log.WithError(err).Errorf("medias GetByID err: %v", err)
return nil, err return nil, err
} }
url, err := ctl.oss.GetSignedUrl(ctx.Context(), media.Path) duration := 2*asset.Metas.Duration + 30
if asset.Metas.Duration == 0 {
duration = 60 * 5
}
url, err := ctl.oss.GetSignedUrl(ctx.Context(), media.Path, ali.WithExpire(time.Second*time.Duration(duration)))
if err != nil { if err != nil {
log.WithError(err).Errorf("media GetSignedUrl err: %v", err) log.WithError(err).Errorf("media GetSignedUrl err: %v", err)
return nil, err return nil, err

View File

@@ -224,12 +224,15 @@ func (m *postsModel) DeleteByID(ctx context.Context, id int64) error {
} }
// SendTo // SendTo
func (m *postsModel) SendTo(ctx context.Context, userId, postId int64) error { func (m *postsModel) SendTo(ctx context.Context, postId, userId int64) error {
// add record to user_posts // add record to user_posts
tbl := table.UserPosts tbl := table.UserPosts
stmt := tbl.INSERT(tbl.MutableColumns).MODEL(model.UserPosts{ stmt := tbl.INSERT(tbl.MutableColumns).MODEL(model.UserPosts{
UserID: userId, CreatedAt: time.Now(),
PostID: postId, UpdatedAt: time.Now(),
UserID: userId,
PostID: postId,
Price: -1,
}) })
m.log.Infof("sql: %s", stmt.DebugSql()) m.log.Infof("sql: %s", stmt.DebugSql())
if _, err := stmt.ExecContext(ctx, db); err != nil { if _, err := stmt.ExecContext(ctx, db); err != nil {

View File

@@ -167,6 +167,7 @@ func (m *usersModel) Update(ctx context.Context, id int64, userModel *model.User
stmt := tbl. stmt := tbl.
UPDATE( UPDATE(
tbl.MutableColumns.Except( tbl.MutableColumns.Except(
tbl.Balance,
tbl.CreatedAt, tbl.CreatedAt,
tbl.DeletedAt, tbl.DeletedAt,
), ),

View File

@@ -27,7 +27,12 @@ export const userService = {
getUserById(id) { getUserById(id) {
return httpClient.get(`/admin/users/${id}`); return httpClient.get(`/admin/users/${id}`);
}, },
getUserArticles(userId) { getUserArticles(userId, page, limit) {
return httpClient.get(`/admin/users/${userId}/articles`); return httpClient.get(`/admin/users/${userId}/articles`, {
params: {
page,
limit,
}
});
} }
} }

View File

@@ -91,6 +91,14 @@ const formatPrice = (price) => {
<div class="flex-1"> <div class="flex-1">
<h2 class="text-xl font-bold mb-2">{{ user.username }}</h2> <h2 class="text-xl font-bold mb-2">{{ user.username }}</h2>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div>
<p class="text-gray-500">ID</p>
<p>{{ user.id }}</p>
</div>
<div>
<p class="text-gray-500">余额</p>
<p>{{ (user.balance / 100).toFixed(2) }}</p>
</div>
<div> <div>
<p class="text-gray-500">OpenID</p> <p class="text-gray-500">OpenID</p>
<p>{{ user.open_id }}</p> <p>{{ user.open_id }}</p>

View File

@@ -27,45 +27,35 @@ const showArticle = (article) => {
router.push(`/posts/${article.id}`) router.push(`/posts/${article.id}`)
} }
const getBgImage = (id) => {
const idx = id % 79
return `background-image: url(/avatar/${idx}.jpeg); background-size: 100% 100%`
}
</script> </script>
<template> <template>
<div class="bg-white shadow overflow-hidden hover:shadow-md transition-shadow duration-200 cursor-pointer flex h-32" <div :style="getBgImage(article.id)"
class="flex flex-col rounded-md overflow-hidden bg-gray-500 bg-no-repeat shadow-lg shadow-gray-500"
@click="showArticle(article)"> @click="showArticle(article)">
<!-- Left side - Image --> <div class="flex-1 p-4 backdrop-blur-xl w-full h-full backdrop-brightness-50">
<div v-if="article.head_images && article.head_images.length > 0" <h3 class="text-xl font-semibold text-gray-200 drop-shadow-md drop-shadow-white-100">
class="relative w-32 flex-shrink-0 bg-gray-100 overflow-hidden"> {{ article.title }}
<img :src="article.head_images[0]" </h3>
class="absolute inset-0 h-full w-full object-cover transition-transform duration-300 hover:scale-105"
:alt="article.title" />
<div class="absolute inset-0 bg-gradient-to-r from-transparent to-black/20"></div>
</div>
<!-- Right side - Content -->
<div class="flex-1 p-4 flex flex-col justify-between min-w-0">
<div class="space-y-2">
<h3 class="text-lg font-semibold text-gray-800 line-clamp-2">
{{ article.title }}
</h3>
<p v-if="article.description" class="text-gray-600 text-sm line-clamp-2"> <div class="flex flex-wrap gap-1">
{{ article.description }} <span v-for="tag in article.tags" :key="tag"
</p> class="px-2 py-0.5 text-xs bg-blue-50 text-blue-600 rounded-full">
{{ tag }}
<div class="flex flex-wrap gap-1"> </span>
<span v-for="tag in article.tags" :key="tag"
class="px-2 py-0.5 text-xs bg-blue-50 text-blue-600 rounded-full">
{{ tag }}
</span>
</div>
</div> </div>
<div class="flex items-center justify-between pt-1 text-sm"> <div class="flex items-center justify-between pt-4 text-sm">
<div class="flex items-center gap-2 text-gray-500"> <div class="flex items-center gap-2 text-gray-100">
<AiOutlineEye class="w-4 h-4" /> <AiOutlineEye class="w-4 h-4" />
<span>{{ article.view_count || 0 }}</span> <span>{{ article.view_count || 0 }}</span>
</div> </div>
<div class="text-orange-600 font-mono text-lg" v-if="!article.bought"> <div class="text-gray-100 font-mono text-lg" v-if="!article.bought">
¥{{ discountPrice }} ¥{{ discountPrice }}
</div> </div>
</div> </div>

View File

@@ -87,8 +87,8 @@ onMounted(() => {
</div> </div>
<div class="flex-1 overflow-y-auto"> <div class="flex-1 overflow-y-auto">
<div class="container max-w-2xl mx-auto py-4"> <div class="container max-w-2xl mx-auto p-4">
<div class="grid gap-4"> <div class="grid gap-6">
<ArticleListItem v-for="article in articles" :key="article.id" :article="article" /> <ArticleListItem v-for="article in articles" :key="article.id" :article="article" />
</div> </div>

File diff suppressed because one or more lines are too long