feat: update
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
54
backend/app/http/users.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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("你好"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
10
frontend/wechat/src/api/userApi.js
Normal file
10
frontend/wechat/src/api/userApi.js
Normal 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 });
|
||||
},
|
||||
}
|
||||
5
frontend/wechat/src/api/user_profile.json
Normal file
5
frontend/wechat/src/api/user_profile.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"id": 1,
|
||||
"created_at": "2025-04-15T20:53:07.107819Z",
|
||||
"username": "u_K6YDZTMc"
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user