feat: update

This commit is contained in:
yanghao05
2025-04-29 14:22:55 +08:00
parent c9740e6403
commit dfb3d8674c
9 changed files with 185 additions and 19 deletions

View File

@@ -57,11 +57,13 @@ func Provide(opts ...opt.Option) error {
auth *auth,
pays *pays,
posts *posts,
users *users,
) (contracts.HttpRoute, error) {
obj := &Routes{
auth: auth,
pays: pays,
posts: posts,
users: users,
}
if err := obj.Prepare(); err != nil {
return nil, err
@@ -71,5 +73,12 @@ func Provide(opts ...opt.Option) error {
}, atom.GroupRoutes); err != nil {
return err
}
if err := container.Container.Provide(func() (*users, error) {
obj := &users{}
return obj, nil
}); err != nil {
return err
}
return nil
}

View File

@@ -18,6 +18,7 @@ type Routes struct {
auth *auth
pays *pays
posts *posts
users *users
}
func (r *Routes) Prepare() error {
@@ -82,4 +83,16 @@ func (r *Routes) Register(router fiber.Router) {
Local[*model.Users]("user"),
))
// 注册路由组: users
router.Get("/users/profile", DataFunc1(
r.users.Profile,
Local[*model.Users]("user"),
))
router.Put("/users/username", Func2(
r.users.Update,
Local[*model.Users]("user"),
Body[ProfileForm]("form"),
))
}

54
backend/app/http/users.go Normal file
View File

@@ -0,0 +1,54 @@
package http
import (
"strings"
"time"
"quyun/app/models"
"quyun/database/schemas/public/model"
"github.com/gofiber/fiber/v3"
)
// @provider
type users struct{}
type UserInfo struct {
ID int64 `json:"id,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
Username string `json:"username,omitempty"`
}
// @Router /users/profile [get]
// @Bind user local
func (ctl *users) Profile(ctx fiber.Ctx, user *model.Users) (*UserInfo, error) {
return &UserInfo{
ID: user.ID,
CreatedAt: user.CreatedAt,
Username: user.Username,
}, nil
}
type ProfileForm struct {
Username string `json:"username" validate:"required"`
}
// Update
// @Router /users/username [put]
// @Bind user local
// @Bind form body
func (ctl *users) Update(ctx fiber.Ctx, user *model.Users, form *ProfileForm) error {
username := strings.TrimSpace(form.Username)
if len([]rune(username)) > 12 {
return fiber.NewError(fiber.StatusBadRequest, "Username exceeds maximum length of 12 characters")
}
if username == "" {
return fiber.NewError(fiber.StatusBadRequest, "Username cannot be empty")
}
if err := models.Users.UpdateUsername(ctx.Context(), user.ID, username); err != nil {
return err
}
return nil
}

View File

@@ -343,3 +343,21 @@ func (m *usersModel) Count(ctx context.Context, cond BoolExpression) (int64, err
return cnt.Cnt, nil
}
// UpdateUsername
func (m *usersModel) UpdateUsername(ctx context.Context, id int64, username string) error {
tbl := table.Users
stmt := tbl.
UPDATE(tbl.Username).
SET(String(username)).
WHERE(
tbl.ID.EQ(Int64(id)),
)
m.log.Infof("sql: %s", stmt.DebugSql())
if _, err := stmt.ExecContext(ctx, db); err != nil {
m.log.Errorf("error updating username: %v", err)
return err
}
return nil
}

View File

@@ -27,3 +27,11 @@ func Test_hassID(t *testing.T) {
})
})
}
func Test_username(t *testing.T) {
Convey("test_username", t, func() {
Convey("step 1", func() {
t.Logf("length: %s", len("你好"))
})
})
}

View File

@@ -75,4 +75,8 @@ Authorization: {{token}}
### get statistics
GET {{host}}/v1/admin/statistics HTTP/1.1
Authorization: {{token}}
### get user profile
GET {{host}}/v1/users/profile HTTP/1.1
Authorization: {{token}}

View File

@@ -0,0 +1,10 @@
import client from './client';
export const userApi = {
profile() {
return client.get(`/users/profile`);
},
update(profile) {
return client.put(`/users/profile`, { username: profile.username });
},
}

View File

@@ -0,0 +1,5 @@
{
"id": 1,
"created_at": "2025-04-15T20:53:07.107819Z",
"username": "u_K6YDZTMc"
}

View File

@@ -1,29 +1,74 @@
<script setup>
import { ref } from 'vue';
import { userApi } from '@/api/userApi';
import { onMounted, ref } from 'vue';
import {
BsBox2Fill,
BsGearFill,
BsPersonFill,
BsPinMapFill
} from 'vue-icons-plus/bs';
const userInfo = ref({
name: '用户名',
avatar: '',
// Add more user info as needed
})
const userInfo = ref({});
onMounted(async () => {
try {
const { data } = await userApi.profile();
userInfo.value = data;
console.log('User profile:', userInfo.value);
} catch (error) {
console.error('Failed to fetch user profile:', error);
}
});
const menuGroups = [
{
title: '账号与信息',
items: [
{ label: '个人资料', icon: BsPersonFill, link: '/profile/edit' },
]
},
{
title: '订单与服务',
items: [
{ label: '我的订单', icon: BsBox2Fill, link: '/orders' },
{ label: '我的地址', icon: BsPinMapFill, link: '/address' },
]
},
{
title: '设置',
items: [
{ label: '系统设置', icon: BsGearFill, link: '/settings' },
]
}
];
</script>
<template>
<div class="p-4">
<div class="bg-white rounded-lg shadow-sm p-4">
<div class="flex items-center gap-4 p-4 border-b border-gray-100">
<div class="w-16 h-16 rounded-full bg-gray-200 overflow-hidden">
<img v-if="userInfo.avatar" :src="userInfo.avatar" alt="头像" class="w-full h-full object-cover">
</div>
<h3 class="text-xl font-medium">{{ userInfo.name }}</h3>
</div>
<div class="grid grid-cols-3 gap-4 p-4">
<button v-for="(item, index) in ['我的收藏', '我的点赞', '订单列表']" :key="index"
class="py-3 text-center text-gray-700 hover:bg-gray-50 active:bg-gray-100 rounded-lg transition-colors">
{{ item }}
</button>
<div class="bg-white p-4 shadow-lg border-b border-gray-300">
<div class="flex items-center gap-4 p-4 border-gray-100">
<div class="w-16 h-16 rounded-full bg-gray-200 overflow-hidden"
:style="{ backgroundImage: `url(${userInfo.avatar})` }">
</div>
<div>
<h3 class="text-xl font-medium">{{ userInfo.username }}</h3>
<span class="text-gray-500">ID: {{ userInfo.id }}</span>
</div>
</div>
</div>
<div class="menus space-y-4 mt-4 px-4">
<div v-for="(group, groupIndex) in menuGroups" :key="groupIndex" class="bg-white rounded-lg overflow-hidden">
<div class="px-4 py-2 text-sm text-gray-500">{{ group.title }}</div>
<div class="divide-y divide-gray-100">
<div v-for="(item, itemIndex) in group.items" :key="itemIndex"
class="flex items-center px-4 py-3 hover:bg-gray-50 active:bg-gray-100 cursor-pointer">
<component :is="item.icon" class="mr-3 text-xl text-gray-600" />
<span class="flex-1">{{ item.label }}</span>
<span class="text-gray-400"></span>
</div>
</div>
</div>
</div>
</template>