backend增加租户简称登录

This commit is contained in:
2026-07-17 11:00:33 +08:00
parent dcf06db11b
commit 2232ef6f3c
14 changed files with 916 additions and 746 deletions
@@ -15,6 +15,16 @@
</div>
</div>
<div class="editor-toolbar">
<el-switch
v-model="pinned"
inline-prompt
active-text="置顶"
inactive-text="置顶"
:disabled="loading"
/>
</div>
<div class="editor-body">
<UmoEditor v-model="noteContent" />
</div>
@@ -38,6 +48,7 @@ const emit = defineEmits(['save-success', 'create-success']);
const noteTitle = ref('');
const noteContent = ref('');
const pinned = ref(false);
const loading = ref(false);
const isNew = ref(false);
@@ -47,6 +58,7 @@ const loadNote = async () => {
isNew.value = true;
noteTitle.value = '新建笔记';
noteContent.value = '';
pinned.value = false;
return;
}
@@ -58,6 +70,7 @@ const loadNote = async () => {
if (response.code === 200) {
noteTitle.value = response.data.title || '无标题';
noteContent.value = response.data.content || '';
pinned.value = Number(response.data.pinned) === 1;
} else {
ElMessage.error('加载笔记失败');
}
@@ -84,6 +97,7 @@ const handleSave = async () => {
const response = await createNotebook({
title: noteTitle.value,
content: noteContent.value,
pinned: pinned.value ? 1 : 0,
});
if (response.code === 200) {
@@ -98,6 +112,7 @@ const handleSave = async () => {
const response = await updateNotebook(props.noteId, {
title: noteTitle.value,
content: noteContent.value,
pinned: pinned.value ? 1 : 0,
});
if (response.code === 200) {
@@ -175,6 +190,15 @@ onMounted(() => {
}
}
.editor-toolbar {
min-height: 42px;
padding: 8px 20px;
display: flex;
align-items: center;
border-bottom: 1px solid var(--el-border-color-lighter);
background: var(--el-bg-color);
}
.editor-body {
flex: 1;
padding: 20px;
@@ -28,6 +28,15 @@
>
<div class="note-item-header">
<span class="note-title">{{ note.title || '无标题' }}</span>
<el-tag
v-if="Number(note.pinned) === 1"
class="pinned-tag"
size="small"
type="warning"
effect="plain"
>
置顶
</el-tag>
<el-dropdown trigger="click" @command="(cmd) => handleNoteAction(cmd, note)">
<el-icon class="note-more"><MoreFilled /></el-icon>
<template #dropdown>
@@ -305,6 +314,7 @@ onMounted(() => {
.note-title {
flex: 1;
min-width: 0;
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
@@ -313,6 +323,11 @@ onMounted(() => {
text-overflow: ellipsis;
}
.pinned-tag {
flex-shrink: 0;
margin-left: 8px;
}
.note-more {
margin-left: 8px;
color: var(--el-text-color-secondary);
+6 -1
View File
@@ -78,7 +78,7 @@ func (c *BackendNotebookController) List() {
total, _ := qs.Count()
var list []models.BackendNotebook
_, err = qs.OrderBy("-update_time", "-create_time").
_, err = qs.OrderBy("-pinned", "-id").
Limit(pageSize).Offset((page - 1) * pageSize).
All(&list)
if err != nil && err != orm.ErrNoRows {
@@ -137,6 +137,7 @@ func (c *BackendNotebookController) Create() {
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
Pinned int8 `json:"pinned"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
c.nbJsonErr(400, 400, "参数错误")
@@ -153,6 +154,7 @@ func (c *BackendNotebookController) Create() {
Tid: claims.TenantId,
Title: payload.Title,
Content: payload.Content,
Pinned: payload.Pinned,
UserID: &userID,
UserName: &claims.Username,
IsDeleted: 0,
@@ -190,6 +192,7 @@ func (c *BackendNotebookController) Update() {
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
Pinned int8 `json:"pinned"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
c.nbJsonErr(400, 400, "参数错误")
@@ -219,6 +222,7 @@ func (c *BackendNotebookController) Update() {
Update(map[string]interface{}{
"title": payload.Title,
"content": payload.Content,
"pinned": payload.Pinned,
"update_time": now,
})
if err != nil {
@@ -228,6 +232,7 @@ func (c *BackendNotebookController) Update() {
note.Title = payload.Title
note.Content = payload.Content
note.Pinned = payload.Pinned
note.UpdateTime = &now
c.nbOk(note)
}
+317 -312
View File
@@ -1,312 +1,317 @@
package controllers
import (
"encoding/json"
"io"
"server/models"
"server/pkg/jwtutil"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
type PlatformNotebookController struct {
beego.Controller
}
// requireAuth 验证平台用户权限
func requireNotebookAuth(c *beego.Controller) (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, orm.ErrNoRows
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, orm.ErrNoRows
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, err
}
if claims.UserType != "platform" {
return nil, orm.ErrNoRows
}
return claims, nil
}
// jsonResponse 统一JSON响应
func jsonResponse(c *beego.Controller, httpStatus, code int, msg string, data interface{}) {
c.Ctx.Output.SetStatus(httpStatus)
resp := map[string]interface{}{
"code": code,
"msg": msg,
}
if data != nil {
resp["data"] = data
}
c.Data["json"] = resp
_ = c.ServeJSON()
}
// List 获取笔记列表
// GET /platform/notebook/list
func (c *PlatformNotebookController) List() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
keyword := strings.TrimSpace(c.GetString("keyword"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
qs := models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID)
if keyword != "" {
qs = qs.Filter("title__icontains", keyword)
}
total, err := qs.Count()
if err != nil {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
return
}
var list []models.PlatformNotebook
_, err = qs.OrderBy("-update_time", "-create_time").
Limit(pageSize).
Offset((page - 1) * pageSize).
All(&list)
if err != nil && err != orm.ErrNoRows {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
return
}
if list == nil {
list = []models.PlatformNotebook{}
}
jsonResponse(&c.Controller, 200, 200, "success", map[string]interface{}{
"list": list,
"total": total,
})
}
// Detail 获取笔记详情
// GET /platform/notebook/detail/:id
func (c *PlatformNotebookController) Detail() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
jsonResponse(&c.Controller, 400, 400, "无效ID", nil)
return
}
var note models.PlatformNotebook
err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
if err == orm.ErrNoRows {
jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil)
} else {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
}
return
}
jsonResponse(&c.Controller, 200, 200, "success", note)
}
// Create 创建笔记
// POST /platform/notebook/create
func (c *PlatformNotebookController) Create() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
payload.Title = strings.TrimSpace(payload.Title)
if payload.Title == "" {
payload.Title = "无标题"
}
userID := uint64(claims.UserID)
note := &models.PlatformNotebook{
Title: payload.Title,
Content: payload.Content,
UserID: &userID,
UserName: &claims.Username,
IsDeleted: 0,
}
id, err := models.Orm.Insert(note)
if err != nil {
jsonResponse(&c.Controller, 500, 500, "创建失败", nil)
return
}
note.ID = uint64(id)
jsonResponse(&c.Controller, 200, 200, "创建成功", note)
}
// Update 更新笔记
// POST /platform/notebook/update/:id
func (c *PlatformNotebookController) Update() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
jsonResponse(&c.Controller, 400, 400, "无效ID", nil)
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
payload.Title = strings.TrimSpace(payload.Title)
if payload.Title == "" {
payload.Title = "无标题"
}
// 验证笔记是否存在且属于当前用户
var note models.PlatformNotebook
err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
if err == orm.ErrNoRows {
jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil)
} else {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
}
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"title": payload.Title,
"content": payload.Content,
"update_time": now,
})
if err != nil {
jsonResponse(&c.Controller, 500, 500, "更新失败", nil)
return
}
note.Title = payload.Title
note.Content = payload.Content
note.UpdateTime = &now
jsonResponse(&c.Controller, 200, 200, "更新成功", note)
}
// Delete 删除笔记(软删除)
// DELETE /platform/notebook/delete/:id
func (c *PlatformNotebookController) Delete() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
jsonResponse(&c.Controller, 400, 400, "无效ID", nil)
return
}
// 验证笔记是否存在且属于当前用户
var note models.PlatformNotebook
err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
if err == orm.ErrNoRows {
jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil)
} else {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
}
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"is_deleted": 1,
"delete_time": now,
})
if err != nil {
jsonResponse(&c.Controller, 500, 500, "删除失败", nil)
return
}
jsonResponse(&c.Controller, 200, 200, "删除成功", nil)
}
package controllers
import (
"encoding/json"
"io"
"server/models"
"server/pkg/jwtutil"
"strconv"
"strings"
"time"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
type PlatformNotebookController struct {
beego.Controller
}
// requireAuth 验证平台用户权限
func requireNotebookAuth(c *beego.Controller) (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, orm.ErrNoRows
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, orm.ErrNoRows
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, err
}
if claims.UserType != "platform" {
return nil, orm.ErrNoRows
}
return claims, nil
}
// jsonResponse 统一JSON响应
func jsonResponse(c *beego.Controller, httpStatus, code int, msg string, data interface{}) {
c.Ctx.Output.SetStatus(httpStatus)
resp := map[string]interface{}{
"code": code,
"msg": msg,
}
if data != nil {
resp["data"] = data
}
c.Data["json"] = resp
_ = c.ServeJSON()
}
// List 获取笔记列表
// GET /platform/notebook/list
func (c *PlatformNotebookController) List() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
keyword := strings.TrimSpace(c.GetString("keyword"))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
qs := models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID)
if keyword != "" {
qs = qs.Filter("title__icontains", keyword)
}
total, err := qs.Count()
if err != nil {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
return
}
var list []models.PlatformNotebook
_, err = qs.OrderBy("-pinned", "-id").
Limit(pageSize).
Offset((page - 1) * pageSize).
All(&list)
if err != nil && err != orm.ErrNoRows {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
return
}
if list == nil {
list = []models.PlatformNotebook{}
}
jsonResponse(&c.Controller, 200, 200, "success", map[string]interface{}{
"list": list,
"total": total,
})
}
// Detail 获取笔记详情
// GET /platform/notebook/detail/:id
func (c *PlatformNotebookController) Detail() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
jsonResponse(&c.Controller, 400, 400, "无效ID", nil)
return
}
var note models.PlatformNotebook
err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
if err == orm.ErrNoRows {
jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil)
} else {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
}
return
}
jsonResponse(&c.Controller, 200, 200, "success", note)
}
// Create 创建笔记
// POST /platform/notebook/create
func (c *PlatformNotebookController) Create() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
Pinned int8 `json:"pinned"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
payload.Title = strings.TrimSpace(payload.Title)
if payload.Title == "" {
payload.Title = "无标题"
}
userID := uint64(claims.UserID)
note := &models.PlatformNotebook{
Title: payload.Title,
Content: payload.Content,
Pinned: payload.Pinned,
UserID: &userID,
UserName: &claims.Username,
IsDeleted: 0,
}
id, err := models.Orm.Insert(note)
if err != nil {
jsonResponse(&c.Controller, 500, 500, "创建失败", nil)
return
}
note.ID = uint64(id)
jsonResponse(&c.Controller, 200, 200, "创建成功", note)
}
// Update 更新笔记
// POST /platform/notebook/update/:id
func (c *PlatformNotebookController) Update() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
jsonResponse(&c.Controller, 400, 400, "无效ID", nil)
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
var payload struct {
Title string `json:"title"`
Content string `json:"content"`
Pinned int8 `json:"pinned"`
}
if err := json.Unmarshal(raw, &payload); err != nil {
jsonResponse(&c.Controller, 400, 400, "参数错误", nil)
return
}
payload.Title = strings.TrimSpace(payload.Title)
if payload.Title == "" {
payload.Title = "无标题"
}
// 验证笔记是否存在且属于当前用户
var note models.PlatformNotebook
err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
if err == orm.ErrNoRows {
jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil)
} else {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
}
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"title": payload.Title,
"content": payload.Content,
"pinned": payload.Pinned,
"update_time": now,
})
if err != nil {
jsonResponse(&c.Controller, 500, 500, "更新失败", nil)
return
}
note.Title = payload.Title
note.Content = payload.Content
note.Pinned = payload.Pinned
note.UpdateTime = &now
jsonResponse(&c.Controller, 200, 200, "更新成功", note)
}
// Delete 删除笔记(软删除)
// DELETE /platform/notebook/delete/:id
func (c *PlatformNotebookController) Delete() {
claims, err := requireNotebookAuth(&c.Controller)
if err != nil {
jsonResponse(&c.Controller, 401, 401, "未登录或无权限", nil)
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
jsonResponse(&c.Controller, 400, 400, "无效ID", nil)
return
}
// 验证笔记是否存在且属于当前用户
var note models.PlatformNotebook
err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("user_id", claims.UserID).
One(&note)
if err != nil {
if err == orm.ErrNoRows {
jsonResponse(&c.Controller, 404, 404, "笔记不存在", nil)
} else {
jsonResponse(&c.Controller, 500, 500, "查询失败", nil)
}
return
}
now := time.Now()
_, err = models.Orm.QueryTable(new(models.PlatformNotebook)).
Filter("id", id).
Update(map[string]interface{}{
"is_deleted": 1,
"delete_time": now,
})
if err != nil {
jsonResponse(&c.Controller, 500, 500, "删除失败", nil)
return
}
jsonResponse(&c.Controller, 200, 200, "删除成功", nil)
}
+21 -20
View File
@@ -1,20 +1,21 @@
package models
import "time"
// PlatformNotebook 平台记事本表: yz_platform_notebook
type PlatformNotebook struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Title string `orm:"column(title);size(255)" json:"title"`
Content string `orm:"column(content);type(longtext);null" json:"content"`
UserID *uint64 `orm:"column(user_id);null" json:"user_id"`
UserName *string `orm:"column(user_name);size(100);null" json:"user_name"`
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *PlatformNotebook) TableName() string {
return "yz_platform_notebook"
}
package models
import "time"
// PlatformNotebook 平台记事本表: yz_platform_notebook
type PlatformNotebook struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Title string `orm:"column(title);size(255)" json:"title"`
Content string `orm:"column(content);type(longtext);null" json:"content"`
Pinned int8 `orm:"column(pinned);default(0)" json:"pinned"`
UserID *uint64 `orm:"column(user_id);null" json:"user_id"`
UserName *string `orm:"column(user_name);size(100);null" json:"user_name"`
IsDeleted int8 `orm:"column(is_deleted);default(0)" json:"is_deleted"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *PlatformNotebook) TableName() string {
return "yz_platform_notebook"
}
+24 -23
View File
@@ -1,23 +1,24 @@
package models
import "time"
type SystemTenant struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantCode string `orm:"column(tenant_code);size(32)" json:"tenant_code"`
TenantName string `orm:"column(tenant_name);size(128)" json:"tenant_name"`
ContactPerson *string `orm:"column(contact_person);size(64);null" json:"contact_person"`
ContactPhone *string `orm:"column(contact_phone);size(20);null" json:"contact_phone"`
ContactEmail *string `orm:"column(contact_email);size(128);null" json:"contact_email"`
Address *string `orm:"column(address);size(255);null" json:"address"`
Worktime *string `orm:"column(worktime);size(255);null" json:"worktime"`
Status int8 `orm:"column(status);default(1)" json:"status"`
Remark *string `orm:"column(remark);size(512);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *SystemTenant) TableName() string {
return "yz_system_tenant"
}
package models
import "time"
type SystemTenant struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantCode string `orm:"column(tenant_code);size(32)" json:"tenant_code"`
TenantName string `orm:"column(tenant_name);size(128)" json:"tenant_name"`
TenantShortName *string `orm:"column(tenant_short_name);size(128);null" json:"tenant_short_name"`
ContactPerson *string `orm:"column(contact_person);size(64);null" json:"contact_person"`
ContactPhone *string `orm:"column(contact_phone);size(20);null" json:"contact_phone"`
ContactEmail *string `orm:"column(contact_email);size(128);null" json:"contact_email"`
Address *string `orm:"column(address);size(255);null" json:"address"`
Worktime *string `orm:"column(worktime);size(255);null" json:"worktime"`
Status int8 `orm:"column(status);default(1)" json:"status"`
Remark *string `orm:"column(remark);size(512);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *SystemTenant) TableName() string {
return "yz_system_tenant"
}
+232 -232
View File
@@ -1,232 +1,232 @@
package services
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"strings"
"sync"
"time"
"server/models"
)
type loginCodeItem struct {
Code string
Channel string
ExpiredAt time.Time
}
var loginCodeStore sync.Map
func codeKey(account, channel string) string {
return strings.ToLower(strings.TrimSpace(account)) + "|" + strings.TrimSpace(channel)
}
func SendPlatformLoginCode(account, channel string) error {
account = strings.TrimSpace(account)
channel = strings.TrimSpace(channel)
if account == "" {
return errors.New("账号不能为空")
}
if channel != "sms" && channel != "email" {
return errors.New("仅支持短信或邮箱验证码")
}
var u models.AdminUser
if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("account", account).One(&u); err != nil {
return errors.New("用户不存在")
}
if u.Status == 0 {
return errors.New("账号已禁用")
}
if channel == "sms" && (u.Phone == nil || strings.TrimSpace(*u.Phone) == "") {
return errors.New("该账号未绑定手机号")
}
if channel == "email" && (u.Email == nil || strings.TrimSpace(*u.Email) == "") {
return errors.New("该账号未绑定邮箱")
}
rand.Seed(time.Now().UnixNano())
code := fmt.Sprintf("%06d", rand.Intn(1000000))
loginCodeStore.Store(codeKey(account, channel), loginCodeItem{
Code: code,
Channel: channel,
ExpiredAt: time.Now().Add(5 * time.Minute),
})
return nil
}
func VerifyPlatformLoginCode(account, channel, code string) error {
account = strings.TrimSpace(account)
channel = strings.TrimSpace(channel)
code = strings.TrimSpace(code)
if account == "" || code == "" {
return errors.New("验证码不能为空")
}
val, ok := loginCodeStore.Load(codeKey(account, channel))
if !ok {
return errors.New("验证码不存在或已失效")
}
item, ok := val.(loginCodeItem)
if !ok {
return errors.New("验证码状态异常")
}
if time.Now().After(item.ExpiredAt) {
loginCodeStore.Delete(codeKey(account, channel))
return errors.New("验证码已过期")
}
if item.Code != code {
return errors.New("验证码错误")
}
loginCodeStore.Delete(codeKey(account, channel))
return nil
}
func SendBackendLoginCode(tenantName, account, channel string) error {
tenantName = strings.TrimSpace(tenantName)
account = strings.TrimSpace(account)
channel = strings.TrimSpace(channel)
if tenantName == "" || account == "" {
return errors.New("租户名称和账号不能为空")
}
if channel != "sms" && channel != "email" {
return errors.New("仅支持短信或邮箱验证码")
}
var tenant models.SystemTenant
if err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("tenant_name", tenantName).One(&tenant); err != nil {
return errors.New("租户不存在")
}
rand.Seed(time.Now().UnixNano())
code := fmt.Sprintf("%06d", rand.Intn(1000000))
switch channel {
case "sms":
phone := account
var user models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tenant.ID).
Filter("phone", phone).
One(&user); err != nil {
return errors.New("该手机号非当前企业绑定号码,请重试")
}
if user.Status == 0 {
return errors.New("账号已禁用")
}
if user.Phone == nil || strings.TrimSpace(*user.Phone) == "" {
return errors.New("该手机号非当前企业绑定号码,请重试")
}
content := "短信验证码:" + code
if err := enqueueSMSTaskForLogin(tenant.ID, phone, content, code); err != nil {
return errors.New("短信发送失败,请重试")
}
case "email":
email := account
var user models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tenant.ID).
Filter("email", email).
One(&user); err != nil {
return errors.New("该账号未绑定邮箱")
}
if user.Status == 0 {
return errors.New("账号已禁用")
}
if user.Email == nil || strings.TrimSpace(*user.Email) == "" {
return errors.New("该账号未绑定邮箱")
}
}
loginCodeStore.Store(codeKey(tenantName+"#"+account, channel), loginCodeItem{
Code: code,
Channel: channel,
ExpiredAt: time.Now().Add(5 * time.Minute),
})
return nil
}
func VerifyBackendLoginCode(tenantName, account, channel, code string) error {
return VerifyPlatformLoginCode(tenantName+"#"+account, channel, code)
}
func getDefaultSystemSMSConfig() (backendURL string, apiKey string, err error) {
backendURL = models.GetPlatformSettingValue("sms_custom_url", "")
apiKey = models.GetPlatformSettingValue("sms_custom_key", "")
if backendURL == "" || apiKey == "" {
return "", "", fmt.Errorf("短信网关未配置")
}
return backendURL, apiKey, nil
}
// enqueueSMSTaskForLogin 入队短信任务到网关,并写入 yz_system_sms_tasks
func enqueueSMSTaskForLogin(tid uint64, phone, content, code string) error {
backendURL, apiKey, err := getDefaultSystemSMSConfig()
if err != nil {
return err
}
if backendURL == "" || apiKey == "" {
return errors.New("短信网关未配置")
}
enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks"
payload := map[string]interface{}{
"phone": phone,
"content": content,
}
bs, _ := json.Marshal(payload)
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs))
if err != nil {
return err
}
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
bodyStr := strings.TrimSpace(string(bodyBytes))
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("gateway http status: %d, body: %s", resp.StatusCode, bodyStr)
}
now := time.Now()
tidCopy := tid
contentPtr := content
var reportPtr *string
if bodyStr != "" {
reportPtr = &bodyStr
}
task := &models.SystemSMSTask{
Tid: &tidCopy,
ApiKey: apiKey,
Phone: phone,
Content: &contentPtr,
Status: 3,
Code: code,
ReportRaw: reportPtr,
CreateTime: &now,
UpdateTime: &now,
}
_, insertErr := models.Orm.Insert(task)
if insertErr != nil {
return nil
}
return nil
}
package services
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net/http"
"strings"
"sync"
"time"
"server/models"
)
type loginCodeItem struct {
Code string
Channel string
ExpiredAt time.Time
}
var loginCodeStore sync.Map
func codeKey(account, channel string) string {
return strings.ToLower(strings.TrimSpace(account)) + "|" + strings.TrimSpace(channel)
}
func SendPlatformLoginCode(account, channel string) error {
account = strings.TrimSpace(account)
channel = strings.TrimSpace(channel)
if account == "" {
return errors.New("账号不能为空")
}
if channel != "sms" && channel != "email" {
return errors.New("仅支持短信或邮箱验证码")
}
var u models.AdminUser
if err := models.Orm.QueryTable(new(models.AdminUser)).Filter("account", account).One(&u); err != nil {
return errors.New("用户不存在")
}
if u.Status == 0 {
return errors.New("账号已禁用")
}
if channel == "sms" && (u.Phone == nil || strings.TrimSpace(*u.Phone) == "") {
return errors.New("该账号未绑定手机号")
}
if channel == "email" && (u.Email == nil || strings.TrimSpace(*u.Email) == "") {
return errors.New("该账号未绑定邮箱")
}
rand.Seed(time.Now().UnixNano())
code := fmt.Sprintf("%06d", rand.Intn(1000000))
loginCodeStore.Store(codeKey(account, channel), loginCodeItem{
Code: code,
Channel: channel,
ExpiredAt: time.Now().Add(5 * time.Minute),
})
return nil
}
func VerifyPlatformLoginCode(account, channel, code string) error {
account = strings.TrimSpace(account)
channel = strings.TrimSpace(channel)
code = strings.TrimSpace(code)
if account == "" || code == "" {
return errors.New("验证码不能为空")
}
val, ok := loginCodeStore.Load(codeKey(account, channel))
if !ok {
return errors.New("验证码不存在或已失效")
}
item, ok := val.(loginCodeItem)
if !ok {
return errors.New("验证码状态异常")
}
if time.Now().After(item.ExpiredAt) {
loginCodeStore.Delete(codeKey(account, channel))
return errors.New("验证码已过期")
}
if item.Code != code {
return errors.New("验证码错误")
}
loginCodeStore.Delete(codeKey(account, channel))
return nil
}
func SendBackendLoginCode(tenantName, account, channel string) error {
tenantName = strings.TrimSpace(tenantName)
account = strings.TrimSpace(account)
channel = strings.TrimSpace(channel)
if tenantName == "" || account == "" {
return errors.New("租户名称和账号不能为空")
}
if channel != "sms" && channel != "email" {
return errors.New("仅支持短信或邮箱验证码")
}
tenant, err := findTenantByLoginName(tenantName)
if err != nil {
return errors.New("租户不存在")
}
rand.Seed(time.Now().UnixNano())
code := fmt.Sprintf("%06d", rand.Intn(1000000))
switch channel {
case "sms":
phone := account
var user models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tenant.ID).
Filter("phone", phone).
One(&user); err != nil {
return errors.New("该手机号非当前企业绑定号码,请重试")
}
if user.Status == 0 {
return errors.New("账号已禁用")
}
if user.Phone == nil || strings.TrimSpace(*user.Phone) == "" {
return errors.New("该手机号非当前企业绑定号码,请重试")
}
content := "短信验证码:" + code
if err := enqueueSMSTaskForLogin(tenant.ID, phone, content, code); err != nil {
return errors.New("短信发送失败,请重试")
}
case "email":
email := account
var user models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tenant.ID).
Filter("email", email).
One(&user); err != nil {
return errors.New("该账号未绑定邮箱")
}
if user.Status == 0 {
return errors.New("账号已禁用")
}
if user.Email == nil || strings.TrimSpace(*user.Email) == "" {
return errors.New("该账号未绑定邮箱")
}
}
loginCodeStore.Store(codeKey(tenantName+"#"+account, channel), loginCodeItem{
Code: code,
Channel: channel,
ExpiredAt: time.Now().Add(5 * time.Minute),
})
return nil
}
func VerifyBackendLoginCode(tenantName, account, channel, code string) error {
return VerifyPlatformLoginCode(tenantName+"#"+account, channel, code)
}
func getDefaultSystemSMSConfig() (backendURL string, apiKey string, err error) {
backendURL = models.GetPlatformSettingValue("sms_custom_url", "")
apiKey = models.GetPlatformSettingValue("sms_custom_key", "")
if backendURL == "" || apiKey == "" {
return "", "", fmt.Errorf("短信网关未配置")
}
return backendURL, apiKey, nil
}
// enqueueSMSTaskForLogin 入队短信任务到网关,并写入 yz_system_sms_tasks
func enqueueSMSTaskForLogin(tid uint64, phone, content, code string) error {
backendURL, apiKey, err := getDefaultSystemSMSConfig()
if err != nil {
return err
}
if backendURL == "" || apiKey == "" {
return errors.New("短信网关未配置")
}
enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks"
payload := map[string]interface{}{
"phone": phone,
"content": content,
}
bs, _ := json.Marshal(payload)
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs))
if err != nil {
return err
}
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
bodyStr := strings.TrimSpace(string(bodyBytes))
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("gateway http status: %d, body: %s", resp.StatusCode, bodyStr)
}
now := time.Now()
tidCopy := tid
contentPtr := content
var reportPtr *string
if bodyStr != "" {
reportPtr = &bodyStr
}
task := &models.SystemSMSTask{
Tid: &tidCopy,
ApiKey: apiKey,
Phone: phone,
Content: &contentPtr,
Status: 3,
Code: code,
ReportRaw: reportPtr,
CreateTime: &now,
UpdateTime: &now,
}
_, insertErr := models.Orm.Insert(task)
if insertErr != nil {
return nil
}
return nil
}
+155 -158
View File
@@ -1,158 +1,155 @@
package services
import (
"errors"
"strings"
"server/models"
"server/pkg/jwtutil"
"server/pkg/passwordutil"
)
type PlatformLoginUser struct {
ID uint64
Account string
Name string
Tid uint64
Rid uint64
Avatar string
RoleName string
}
func adminRoleNameByID(roleID uint64) string {
if roleID == 0 {
return ""
}
var role models.AdminRole
err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", roleID).One(&role)
if err != nil {
return ""
}
return role.Name
}
func toPlatformLoginUser(user *models.AdminUser) *PlatformLoginUser {
name := ""
if user.Name != nil {
name = *user.Name
}
avatar := ""
if user.Avatar != nil {
avatar = *user.Avatar
}
return &PlatformLoginUser{
ID: user.ID,
Account: user.Account,
Name: name,
Tid: 0,
Rid: user.RoleID,
Avatar: avatar,
RoleName: adminRoleNameByID(user.RoleID),
}
}
// PlatformAdminLogin 平台端登录:仅校验 yz_system_admin_user,不需要租户。
func PlatformAdminLogin(account, password string) (string, *PlatformLoginUser, error) {
account = strings.TrimSpace(account)
password = strings.TrimSpace(password)
if account == "" || password == "" {
return "", nil, errors.New("用户名或密码不能为空")
}
var user models.AdminUser
err := models.Orm.QueryTable(new(models.AdminUser)).
Filter("account", account).
One(&user)
if err != nil {
return "", nil, errors.New("用户名或密码错误")
}
if user.Status == 0 {
return "", nil, errors.New("账号已禁用")
}
if !passwordutil.Verify(user.Password, password) {
return "", nil, errors.New("用户名或密码错误")
}
const tenantID = 0
const userType = "platform"
token, err := jwtutil.GenerateToken(int(user.ID), user.Account, tenantID, userType)
if err != nil {
return "", nil, err
}
loginUser := toPlatformLoginUser(&user)
return token, loginUser, nil
}
// BackendLogin backend 登录:先校验租户,再校验租户下用户账号和密码。
func BackendLogin(tenantName, account, password string) (string, *PlatformLoginUser, error) {
tenantName = strings.TrimSpace(tenantName)
account = strings.TrimSpace(account)
password = strings.TrimSpace(password)
if tenantName == "" || account == "" || password == "" {
return "", nil, errors.New("租户名称、用户名或密码不能为空")
}
var tenant models.SystemTenant
err := models.Orm.QueryTable(new(models.SystemTenant)).
Filter("tenant_name", tenantName).
One(&tenant)
if err != nil {
return "", nil, errors.New("租户不存在")
}
if tenant.Status != 1 {
return "", nil, errors.New("租户已停用")
}
var tenantUser models.SystemTenantUser
err = models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tenant.ID).
Filter("account", account).
One(&tenantUser)
if err != nil {
return "", nil, errors.New("用户名或密码错误")
}
if tenantUser.Status == 0 {
return "", nil, errors.New("账号已禁用")
}
if tenantUser.Password == nil || !passwordutil.Verify(*tenantUser.Password, password) {
return "", nil, errors.New("用户名或密码错误")
}
tenantID := int(tenant.ID)
const userType = "backend"
token, err := jwtutil.GenerateToken(int(tenantUser.Uid), account, tenantID, userType)
if err != nil {
return "", nil, err
}
loginUser := &PlatformLoginUser{
ID: tenantUser.Uid,
Account: account,
Name: "",
Tid: tenant.ID,
Rid: 0,
Avatar: "",
RoleName: "",
}
if tenantUser.Account != nil && strings.TrimSpace(*tenantUser.Account) != "" {
loginUser.Account = strings.TrimSpace(*tenantUser.Account)
}
if tenantUser.Name != nil {
loginUser.Name = strings.TrimSpace(*tenantUser.Name)
}
return token, loginUser, nil
}
// PlatformGetCurrentUser 根据平台管理员用户 ID 返回登录用户信息(含角色名称)。
func PlatformGetCurrentUser(uid uint64) (*PlatformLoginUser, error) {
u, err := GetAdminUserByID(uid)
if err != nil {
return nil, errors.New("用户不存在")
}
if u.Status == 0 {
return nil, errors.New("账号已禁用")
}
return toPlatformLoginUser(u), nil
}
package services
import (
"errors"
"strings"
"server/models"
"server/pkg/jwtutil"
"server/pkg/passwordutil"
)
type PlatformLoginUser struct {
ID uint64
Account string
Name string
Tid uint64
Rid uint64
Avatar string
RoleName string
}
func adminRoleNameByID(roleID uint64) string {
if roleID == 0 {
return ""
}
var role models.AdminRole
err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", roleID).One(&role)
if err != nil {
return ""
}
return role.Name
}
func toPlatformLoginUser(user *models.AdminUser) *PlatformLoginUser {
name := ""
if user.Name != nil {
name = *user.Name
}
avatar := ""
if user.Avatar != nil {
avatar = *user.Avatar
}
return &PlatformLoginUser{
ID: user.ID,
Account: user.Account,
Name: name,
Tid: 0,
Rid: user.RoleID,
Avatar: avatar,
RoleName: adminRoleNameByID(user.RoleID),
}
}
// PlatformAdminLogin 平台端登录:仅校验 yz_system_admin_user,不需要租户。
func PlatformAdminLogin(account, password string) (string, *PlatformLoginUser, error) {
account = strings.TrimSpace(account)
password = strings.TrimSpace(password)
if account == "" || password == "" {
return "", nil, errors.New("用户名或密码不能为空")
}
var user models.AdminUser
err := models.Orm.QueryTable(new(models.AdminUser)).
Filter("account", account).
One(&user)
if err != nil {
return "", nil, errors.New("用户名或密码错误")
}
if user.Status == 0 {
return "", nil, errors.New("账号已禁用")
}
if !passwordutil.Verify(user.Password, password) {
return "", nil, errors.New("用户名或密码错误")
}
const tenantID = 0
const userType = "platform"
token, err := jwtutil.GenerateToken(int(user.ID), user.Account, tenantID, userType)
if err != nil {
return "", nil, err
}
loginUser := toPlatformLoginUser(&user)
return token, loginUser, nil
}
// BackendLogin backend 登录:先校验租户,再校验租户下用户账号和密码。
func BackendLogin(tenantName, account, password string) (string, *PlatformLoginUser, error) {
tenantName = strings.TrimSpace(tenantName)
account = strings.TrimSpace(account)
password = strings.TrimSpace(password)
if tenantName == "" || account == "" || password == "" {
return "", nil, errors.New("租户名称、用户名或密码不能为空")
}
tenant, err := findTenantByLoginName(tenantName)
if err != nil {
return "", nil, errors.New("租户不存在")
}
if tenant.Status != 1 {
return "", nil, errors.New("租户已停用")
}
var tenantUser models.SystemTenantUser
err = models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tenant.ID).
Filter("account", account).
One(&tenantUser)
if err != nil {
return "", nil, errors.New("用户名或密码错误")
}
if tenantUser.Status == 0 {
return "", nil, errors.New("账号已禁用")
}
if tenantUser.Password == nil || !passwordutil.Verify(*tenantUser.Password, password) {
return "", nil, errors.New("用户名或密码错误")
}
tenantID := int(tenant.ID)
const userType = "backend"
token, err := jwtutil.GenerateToken(int(tenantUser.Uid), account, tenantID, userType)
if err != nil {
return "", nil, err
}
loginUser := &PlatformLoginUser{
ID: tenantUser.Uid,
Account: account,
Name: "",
Tid: tenant.ID,
Rid: 0,
Avatar: "",
RoleName: "",
}
if tenantUser.Account != nil && strings.TrimSpace(*tenantUser.Account) != "" {
loginUser.Account = strings.TrimSpace(*tenantUser.Account)
}
if tenantUser.Name != nil {
loginUser.Name = strings.TrimSpace(*tenantUser.Name)
}
return token, loginUser, nil
}
// PlatformGetCurrentUser 根据平台管理员用户 ID 返回登录用户信息(含角色名称)。
func PlatformGetCurrentUser(uid uint64) (*PlatformLoginUser, error) {
u, err := GetAdminUserByID(uid)
if err != nil {
return nil, errors.New("用户不存在")
}
if u.Status == 0 {
return nil, errors.New("账号已禁用")
}
return toPlatformLoginUser(u), nil
}
+43
View File
@@ -0,0 +1,43 @@
package services
import (
"strings"
"github.com/beego/beego/v2/client/orm"
"server/models"
)
/* findTenantByLoginName 根据租户简称、短名或编码查找租户。
*
* 当前表结构中 tenant_name 的业务含义就是“租户简称”,同时兼容
* tenant_short_name 和 tenant_code 两个历史/备用登录标识。
*/
func findTenantByLoginName(loginName string) (*models.SystemTenant, error) {
loginName = strings.TrimSpace(loginName)
if loginName == "" {
return nil, orm.ErrNoRows
}
// tenant_name 的业务含义是租户简称。使用 TRIM 兼容历史数据中字段值
// 前后存在空格的情况;参数通过 SetArgs 绑定,避免 SQL 注入。
tenant := &models.SystemTenant{}
query := `
SELECT id, tenant_code, tenant_name, tenant_short_name,
contact_person, contact_phone, contact_email, address,
worktime, status, remark, create_time, update_time, delete_time
FROM yz_system_tenant
WHERE (TRIM(tenant_name) = TRIM(?) OR
TRIM(tenant_short_name) = TRIM(?) OR
TRIM(tenant_code) = TRIM(?))
AND status <> 0
ORDER BY id ASC
LIMIT 1`
err := models.Orm.Raw(query).
SetArgs(loginName, loginName, loginName).
QueryRow(tenant)
if err != nil {
return nil, err
}
return tenant, nil
}
@@ -15,6 +15,16 @@
</div>
</div>
<div class="editor-toolbar">
<el-switch
v-model="pinned"
inline-prompt
active-text="置顶"
inactive-text="置顶"
:disabled="loading"
/>
</div>
<div class="editor-body">
<UmoEditor v-model="noteContent" />
</div>
@@ -38,6 +48,7 @@ const emit = defineEmits(['save-success', 'create-success']);
const noteTitle = ref('');
const noteContent = ref('');
const pinned = ref(false);
const loading = ref(false);
const isNew = ref(false);
@@ -47,6 +58,7 @@ const loadNote = async () => {
isNew.value = true;
noteTitle.value = '新建笔记';
noteContent.value = '';
pinned.value = false;
return;
}
@@ -58,6 +70,7 @@ const loadNote = async () => {
if (response.code === 200) {
noteTitle.value = response.data.title || '无标题';
noteContent.value = response.data.content || '';
pinned.value = Number(response.data.pinned) === 1;
} else {
ElMessage.error('加载笔记失败');
}
@@ -84,6 +97,7 @@ const handleSave = async () => {
const response = await createNotebook({
title: noteTitle.value,
content: noteContent.value,
pinned: pinned.value ? 1 : 0,
});
if (response.code === 200) {
@@ -98,6 +112,7 @@ const handleSave = async () => {
const response = await updateNotebook(props.noteId, {
title: noteTitle.value,
content: noteContent.value,
pinned: pinned.value ? 1 : 0,
});
if (response.code === 200) {
@@ -175,6 +190,15 @@ onMounted(() => {
}
}
.editor-toolbar {
min-height: 42px;
padding: 8px 20px;
display: flex;
align-items: center;
border-bottom: 1px solid var(--el-border-color-lighter);
background: var(--el-bg-color);
}
.editor-body {
flex: 1;
padding: 20px;
@@ -28,6 +28,15 @@
>
<div class="note-item-header">
<span class="note-title">{{ note.title || '无标题' }}</span>
<el-tag
v-if="Number(note.pinned) === 1"
class="pinned-tag"
size="small"
type="warning"
effect="plain"
>
置顶
</el-tag>
<el-dropdown trigger="click" @command="(cmd) => handleNoteAction(cmd, note)">
<el-icon class="note-more"><MoreFilled /></el-icon>
<template #dropdown>
@@ -305,6 +314,7 @@ onMounted(() => {
.note-title {
flex: 1;
min-width: 0;
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
@@ -313,6 +323,11 @@ onMounted(() => {
text-overflow: ellipsis;
}
.pinned-tag {
flex-shrink: 0;
margin-left: 8px;
}
.note-more {
margin-left: 8px;
color: var(--el-text-color-secondary);
+14
View File
@@ -0,0 +1,14 @@
-- 记事本表增加置顶字段(兼容已有数据库)
-- 已存在字段时请跳过对应 ALTER 语句。
ALTER TABLE `yz_platform_notebook`
ADD COLUMN `pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否置顶 0-否 1-是' AFTER `content`;
ALTER TABLE `yz_platform_notebook`
ADD KEY `idx_pinned` (`pinned`);
ALTER TABLE `yz_backend_notebook`
ADD COLUMN `pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否置顶 0-否 1-是' AFTER `content`;
ALTER TABLE `yz_backend_notebook`
ADD KEY `idx_pinned` (`pinned`);
+2
View File
@@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS `yz_platform_notebook` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT '笔记标题',
`content` longtext COMMENT '笔记内容(HTML格式)',
`pinned` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否置顶 0-否 1-是',
`user_id` bigint(20) unsigned DEFAULT NULL COMMENT '创建用户ID',
`user_name` varchar(100) DEFAULT NULL COMMENT '创建用户名',
`is_deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除 0-否 1-是',
@@ -11,6 +12,7 @@ CREATE TABLE IF NOT EXISTS `yz_platform_notebook` (
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_pinned` (`pinned`),
KEY `idx_create_time` (`create_time`),
KEY `idx_is_deleted` (`is_deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台记事本表';
+24
View File
@@ -0,0 +1,24 @@
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/controllers [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/middleware [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/models [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/pkg/jwtutil [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/pkg/tokenprobe [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/routers [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/routers/api [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/routers/app [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/routers/backend [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/routers/index [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/routers/platform [setup failed]
FAIL _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/services [setup failed]
FAIL backend [setup failed]
FAIL docs [setup failed]
FAIL frontend [setup failed]
FAIL go [setup failed]
FAIL platform [setup failed]
FAIL sql [setup failed]
FAIL uniapp [setup failed]
? _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/pkg/passwordutil [no test files]
? _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/pkg/versionutil [no test files]
? _/E_/Demos/DemoOwns/Go/yunzerwebsiteallinone/go/version [no test files]
FAIL