Signed-off-by: 吴文峰 <kevin@lmve.net>
This commit is contained in:
2026-01-13 21:08:19 +08:00
parent 3acc19ffd8
commit 0b67a9bf09
16 changed files with 420 additions and 117 deletions
+6 -6
View File
@@ -24,12 +24,12 @@ user:
file: file:
maxSize: 52428800 maxSize: 52428800
pahts: pahts:
avatar: "./data/avatar/" avatar: "data/static/avatar/"
image: "./data/upload/image/" image: "data/upload/image/"
video: "./data/upload/video/" video: "data/upload/video/"
music: "./data/upload/music/" music: "data/upload/music/"
pdf: "./data/upload/pdf/" pdf: "data/upload/pdf/"
other: "./data/upload/other/" other: "data/upload/other/"
allowImageMime: allowImageMime:
image/jpeg: ".jpeg" image/jpeg: ".jpeg"
+6 -1
View File
@@ -9,6 +9,11 @@
"userEmailFormatError":-43, "userEmailFormatError":-43,
"userCookieError":-44, "userCookieError":-44,
"userCookieNotFund":-44, "userCookieNotFund":-44,
"userCookieExpired":-44 "userCookieExpired":-44,
"file_mime_err":-51,
"file_size_err":-52,
"file_name_err":-53,
"file_get_err":-54
} }
+11
View File
@@ -68,6 +68,17 @@ func main() {
models.ConfigAllInit() models.ConfigAllInit()
routers.ApiInit() routers.ApiInit()
//创建必要目录
for _, path := range models.ConfigsFile.Pahts {
fmt.Println(path)
err := os.MkdirAll(path, 0755)
if err != nil {
fmt.Printf("创建文件夹失败: %v\n", err)
panic("创建文件夹失败")
}
}
//启动gin服务 //启动gin服务
r := gin.Default() r := gin.Default()
-2
View File
@@ -4,8 +4,6 @@ import "github.com/mitchellh/mapstructure"
var Configs map[string]interface{} var Configs map[string]interface{}
//mime信息转换位拓展名
type ConfigsWeb_ struct { type ConfigsWeb_ struct {
Host string `mapstructure:"host"` Host string `mapstructure:"host"`
Port string `mapstructure:"port"` Port string `mapstructure:"port"`
+47 -1
View File
@@ -1,6 +1,13 @@
package models package models
import "os" import (
"crypto"
"encoding/hex"
"io"
"mime/multipart"
"net/http"
"os"
)
// 判断文件是否存在 // 判断文件是否存在
func FileExists(path string) bool { func FileExists(path string) bool {
@@ -10,3 +17,42 @@ func FileExists(path string) bool {
} }
return true return true
} }
// 计算文件的哈希
func SHA256HashFile(file_head *multipart.FileHeader) (string, error) {
// 打开文件
file, err := file_head.Open()
if err != nil {
return "foen error", err
}
defer file.Close()
hasher := crypto.SHA256.New()
// 从文件流中读取并计算哈希
_, err = io.Copy(hasher, file)
if err != nil {
return "", err
}
hashBytes := hasher.Sum(nil)
return hex.EncodeToString(hashBytes), nil
}
// 获取文件mime
func GetFileMime(file_head *multipart.FileHeader) (string, error) {
file, err := file_head.Open()
if err != nil {
return "foen error", err
}
defer file.Close()
// 读取前512字节用于MIME检测
buffer := make([]byte, 512)
io.ReadFull(file, buffer)
mimeType := http.DetectContentType(buffer)
return mimeType, nil
}
+1
View File
@@ -50,6 +50,7 @@ func SeparateData(ctx *gin.Context) (map[string]interface{}, string) {
func ApiRoot(r *gin.RouterGroup) { func ApiRoot(r *gin.RouterGroup) {
ApiStatic(r.Group("/static"))
ApiUser(r.Group("/users")) ApiUser(r.Group("/users"))
r.GET("/", func(ctx *gin.Context) { r.GET("/", func(ctx *gin.Context) {
+25
View File
@@ -0,0 +1,25 @@
package routers
import (
"ops/models"
"path"
"github.com/gin-gonic/gin"
)
//处理api的静态内容
func ApiStatic(r *gin.RouterGroup) {
r.GET("/avatar/:filename", func(ctx *gin.Context) {
filename := ctx.Param("filename")
dst := path.Join(models.ConfigsFile.Pahts["avatar"], filename)
if models.FileExists(dst) {
ctx.File(dst)
} else {
//找不到文件
ctx.String(404, "file not found")
}
})
}
+114 -1
View File
@@ -1,8 +1,10 @@
package routers package routers
import ( import (
"errors"
"fmt" "fmt"
"ops/models" "ops/models"
"path"
"strconv" "strconv"
"time" "time"
@@ -80,6 +82,33 @@ type From_user_changepass struct {
Newpass string `json:"newpass"` Newpass string `json:"newpass"`
} }
func AuthenticationAuthorityFromCookie(c string) (*models.TabUser_, error) {
if c != "" {
cookie := models.TabCookie_{
Value: c,
}
if models.DB.Where(&cookie).First(&cookie).Error == nil {
//找到cookie,验证cookie有效性,以及更新cookie
if models.CheckCookiesAndUpdate(&cookie) {
//cookie有效
//载入user
user := models.TabUser_{
ID: cookie.UserID,
}
models.DB.Where(&user).First(&user)
return &user, nil
} else {
return nil, errors.New("cookie 过期")
}
} else {
return nil, errors.New("cookie Not Fund")
}
} else {
return nil, errors.New("cookie 参数错误")
}
}
func AuthenticationAuthority(ctx *gin.Context) (bool, models.TabUser_, map[string]interface{}) { func AuthenticationAuthority(ctx *gin.Context) (bool, models.TabUser_, map[string]interface{}) {
var user models.TabUser_ var user models.TabUser_
@@ -187,8 +216,92 @@ func ApiUser(r *gin.RouterGroup) {
//修改用户头像 //修改用户头像
r.POST("/updateAvatar", func(ctx *gin.Context) { r.POST("/updateAvatar", func(ctx *gin.Context) {
cookie := ctx.PostForm("cookie")
user, err := AuthenticationAuthorityFromCookie(cookie)
if err == nil {
file, err := ctx.FormFile("file")
if err == nil {
if file.Filename != "" {
//限制文件大小
if file.Size > 512 {
//头像裁剪过限制1M应该差不多
if file.Size < 1048576 {
//判断mime
mimeType, err := models.GetFileMime(file)
if err == nil {
file_extname := models.ConfigsFile.AllowImageMime[mimeType]
if file_extname != "" {
//haxi文件
file_hashi_name, err := models.SHA256HashFile(file)
if err == nil {
dst := path.Join(models.ConfigsFile.Pahts["avatar"], file_hashi_name+file_extname)
var is_save_ok = false
//判断文件是否存在避免重复保存
if models.FileExists(dst) {
//fmt.Println("文件存在")
is_save_ok = true
ReturnJson(ctx, "apiOK", nil)
} else {
//fmt.Println("文件no存在")
ferr := ctx.SaveUploadedFile(file, dst)
if ferr == nil {
//文件保存成功
//fmt.Print("save_ok")
is_save_ok = true
ReturnJson(ctx, "apiOK", nil)
} else {
fmt.Print(ferr)
ReturnJson(ctx, "postErr", nil)
}
}
if is_save_ok {
//修改数据库内容
var user_info_fund models.TabUserInfo_
user_info_fund.UserID = user.ID
var user_update_avatar models.TabUserInfo_
user_update_avatar.AvatarPath = file_hashi_name + file_extname
models.DB.Where(&user_info_fund).Updates(&user_update_avatar)
}
} else {
ReturnJson(ctx, "postErr", nil)
}
} else {
ReturnJson(ctx, "file_mime_err", nil)
}
} else {
ReturnJson(ctx, "postErr", nil)
}
} else {
ReturnJson(ctx, "file_size_err", nil)
}
} else {
ReturnJson(ctx, "file_size_err", nil)
}
} else {
ReturnJson(ctx, "file_name_err", nil)
}
} else {
ReturnJson(ctx, "file_get_err", nil)
}
} else {
ReturnJson(ctx, "userCookieError", nil)
}
ReturnJson(ctx, "jsonErr", nil)
}) })
//更新用户info //更新用户info
+10
View File
@@ -22,3 +22,13 @@ func ReturnJson(ctx *gin.Context, errMsg string, data map[string]interface{}) {
//ctx.Abort() //ctx.Abort()
} }
func Return_file(ctx *gin.Context, file_path string, preview bool) {
if preview {
ctx.File(file_path)
} else {
//需要从数据库拉取原始文件名
//ctx.FileAttachment(file_info.Path, file_info.Name)
}
}
+9 -3
View File
@@ -1,4 +1,10 @@
{ {
"errorpage": {
"404_title": "404 Resource Not Found",
"404_msg_title": "Oops… You just found an error page",
"404_msg": "We are sorry but the page you are looking for was not found",
"404_back_home": "Take me home"
},
"appname": { "appname": {
"home": "Home", "home": "Home",
"login": "Login", "login": "Login",
@@ -106,9 +112,9 @@
}, },
"settings": { "settings": {
"cancel": "Cancel", "cancel": "Cancel",
"basic_information":"Basic Information", "basic_information": "Basic Information",
"contact_information":"Contact Information", "contact_information": "Contact Information",
"security_settings":"Security Settings", "security_settings": "Security Settings",
"account_settings": "Account Settings", "account_settings": "Account Settings",
"my_account": "My Account", "my_account": "My Account",
"profile_information": "Profile Information", "profile_information": "Profile Information",
+60 -54
View File
@@ -1,60 +1,66 @@
{ {
"errorpage": {
"404_title": "404 资源未找到",
"404_msg_title": "抱歉…您刚刚发现了一个错误页面",
"404_msg": "您所查找的页面不存在",
"404_back_home": "返回首页"
},
"appname": { "appname": {
"home": "主页", "home": "主页",
"login": "登录", "login": "登录",
"forgot_password": "忘记密码", "forgot_password": "忘记密码",
"register": "注册", "register": "注册",
"schedule":"日程", "schedule": "日程",
"purchase":"采购", "purchase": "采购",
"warehouse":"仓库" "warehouse": "仓库"
}, },
"cropper": { "cropper": {
"select_image": "选择图片", "select_image": "选择图片",
"select_File": "选择文件", "select_File": "选择文件",
"crop_image": "裁剪图片", "crop_image": "裁剪图片",
"cancel": "取消", "cancel": "取消",
"closs": "关闭" "closs": "关闭"
}, },
"purchase":{ "purchase": {
"purchase_list":"采购列表", "purchase_list": "采购列表",
"item_name":"物品名称", "item_name": "物品名称",
"purpose":"用途", "purpose": "用途",
"unit":"单位", "unit": "单位",
"quantity":"数量", "quantity": "数量",
"unit_price":"单价", "unit_price": "单价",
"total_price":"总价", "total_price": "总价",
"created_at":"创建日期", "created_at": "创建日期",
"updated_at":"更新日期", "updated_at": "更新日期",
"status":"状态", "status": "状态",
"completed":"已完成", "completed": "已完成",
"pending":"待处理", "pending": "待处理",
"show":"显示", "show": "显示",
"entries":"个物件", "entries": "个物件",
"search":"搜索", "search": "搜索",
"add_part":"添加订单", "add_part": "添加订单",
"exp_report":"生成报告" "exp_report": "生成报告"
}, },
"schedule": { "schedule": {
"my_schedule":"我的日程", "my_schedule": "我的日程",
"add_event":"添加事件", "add_event": "添加事件",
"event_title":"事件标题", "event_title": "事件标题",
"event_date":"事件日期", "event_date": "事件日期",
"event_time":"事件时间", "event_time": "事件时间",
"event_description":"事件描述", "event_description": "事件描述",
"save_event":"保存事件", "save_event": "保存事件",
"no_events":"没有找到事件", "no_events": "没有找到事件",
"delete_event":"删除事件", "delete_event": "删除事件",
"edit_event":"编辑事件", "edit_event": "编辑事件",
"tody":"今天", "tody": "今天",
"week":"本周", "week": "本周",
"month":"本月", "month": "本月",
"previous_month":"上月", "previous_month": "上月",
"next_month":"下月", "next_month": "下月",
"previous_year":"上年", "previous_year": "上年",
"next_year":"下年" "next_year": "下年"
}, },
"message": { "message": {
"functionality_not_yet_developed":"功能未开发", "functionality_not_yet_developed": "功能未开发",
"hello": "你好", "hello": "你好",
"welcome": "欢迎", "welcome": "欢迎",
"dark_mode": "深色模式", "dark_mode": "深色模式",
@@ -95,20 +101,20 @@
"user_settings": "个人资料", "user_settings": "个人资料",
"preferences": "偏好设置", "preferences": "偏好设置",
"administrator": "管理员", "administrator": "管理员",
"select_date":"选择日期", "select_date": "选择日期",
"save_ok":"保存成功", "save_ok": "保存成功",
"change_ok":"更改成功", "change_ok": "更改成功",
"type_old_pass":"输入旧密码", "type_old_pass": "输入旧密码",
"type_new_pass":"输入新密码", "type_new_pass": "输入新密码",
"type_cof_pass":"确认新密码", "type_cof_pass": "确认新密码",
"old_pass_incorrect":"旧密码不正确", "old_pass_incorrect": "旧密码不正确",
"confirm_password_incorrect":"确认密码不正确" "confirm_password_incorrect": "确认密码不正确"
}, },
"settings": { "settings": {
"cancel": "取消", "cancel": "取消",
"basic_information":"基本信息", "basic_information": "基本信息",
"contact_information":"联系信息", "contact_information": "联系信息",
"security_settings":"安全设置", "security_settings": "安全设置",
"account_settings": "个人设置", "account_settings": "个人设置",
"my_account": "我的账户", "my_account": "我的账户",
"profile_information": "个人信息", "profile_information": "个人信息",
@@ -122,7 +128,7 @@
"female": "女", "female": "女",
"birthday": "生日", "birthday": "生日",
"admin": "管理员", "admin": "管理员",
"website_settings":"网站设置", "website_settings": "网站设置",
"site_name": "网站名称", "site_name": "网站名称",
"logo_settings": "Logo设置", "logo_settings": "Logo设置",
"site_description": "网站描述", "site_description": "网站描述",
+14 -4
View File
@@ -13,6 +13,11 @@ const router = createRouter({
name: "home", name: "home",
component: HomeView, component: HomeView,
}, },
{
path: "/404",
name: "404",
component: () => import("../views/404.vue"),
},
{ {
path: "/settings/account", path: "/settings/account",
name: "settings account", name: "settings account",
@@ -61,7 +66,7 @@ const router = createRouter({
name: "admin", name: "admin",
component: () => import("../views/adminView.vue"), component: () => import("../views/adminView.vue"),
}, },
{ {
path: "/schedule", path: "/schedule",
name: "schedule", name: "schedule",
@@ -70,13 +75,18 @@ const router = createRouter({
{ {
path: "/purchase", path: "/purchase",
name: "purchase", name: "purchase",
component: () => import("../views/purchaseView.vue"), component: () => import("../views/purchase/purchase.vue"),
}, },
{ {
path: "/purchase/addorder",
name: "purchase",
component: () => import("../views/purchase/addorder.vue"),
},
{
path: "/warehouse", path: "/warehouse",
name: "warehouse", name: "warehouse",
component: () => import("../views/warehouse.vue"), component: () => import("../views/warehouse.vue"),
} },
], ],
}); });
+1 -1
View File
@@ -41,7 +41,7 @@ export const useUserStore = defineStore("user", () => {
const getUserAvatarPath = () => { const getUserAvatarPath = () => {
if (userInfo.value != null) { if (userInfo.value != null) {
if (userInfo.value.AvatarPath != "") { if (userInfo.value.AvatarPath != "") {
return userInfo.value.AvatarPath; return "/api/static/avatar/"+userInfo.value.AvatarPath;
} }
} }
return "/ava.svg"; return "/ava.svg";
+59
View File
@@ -0,0 +1,59 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useRouter } from "vue-router";
const { t, locale } = useI18n();
function functionupdataTitle() {
document.title = "Operations." + t("errorpage.404_title");
}
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
onMounted(() => {
//console.log("account mounted");
//username.value.value="Kevin";
functionupdataTitle();
});
</script>
<template>
<div>
<div class="page page-center">
<div class="container-tight py-4">
<div class="empty">
<div class="empty-header">404</div>
<p class="empty-title">{{ t("errorpage.404_msg_title") }}</p>
<p class="empty-subtitle text-secondary">
{{ t("errorpage.404_msg") }}
</p>
<div class="empty-action">
<router-link to="/" class="btn btn-primary">
<!-- Download SVG icon from http://tabler-icons.io/i/arrow-left -->
<svg
xmlns="http://www.w3.org/2000/svg"
class="icon"
width="24"
height="24"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 12l14 0" />
<path d="M5 12l6 6" />
<path d="M5 12l6 -6" />
</svg>
{{ t("errorpage.404_back_home") }}
</router-link>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -0,0 +1,21 @@
<script setup>
import { onMounted, watch, ref } from "vue";
import { useI18n } from "vue-i18n";
const { t, locale } = useI18n();
function functionupdataTitle() {
document.title = "Operations." + t("purchase.add_part");
}
onMounted(() => {
functionupdataTitle();
});
// 监听语言变化,更新标题
watch(locale, () => {
functionupdataTitle();
});
</script>
<template>
add_part
</template>
@@ -17,17 +17,15 @@ watch(locale, () => {
</script> </script>
<template> <template>
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">{{ t('purchase.purchase_list') }}</h3> <h3 class="card-title">{{ t("purchase.purchase_list") }}</h3>
</div> </div>
<div class="card-body border-bottom py-3"> <div class="card-body border-bottom py-3">
<div class="d-flex"> <div class="d-flex">
<div class="text-secondary"> <div class="text-secondary">
{{ t('purchase.show') }} {{ t("purchase.show") }}
<div class="mx-2 d-inline-block"> <div class="mx-2 d-inline-block">
<input <input
type="text" type="text"
@@ -37,17 +35,19 @@ watch(locale, () => {
aria-label="Invoices count" aria-label="Invoices count"
/> />
</div> </div>
{{ t('purchase.entries') }} {{ t("purchase.entries") }}
</div> </div>
<div class="ms-auto text-secondary"> <div class="ms-auto text-secondary">
<button class="btn btn-info m-1">{{ t('purchase.add_part') }}</button> <router-link to="/purchase/addorder" class="btn btn-info m-1">
<button class="btn m-1">{{ t('purchase.exp_report') }}</button> {{ t("purchase.add_part") }}
</router-link>
<button class="btn m-1">{{ t("purchase.exp_report") }}</button>
</div> </div>
<div class="ms-auto text-secondary"> <div class="ms-auto text-secondary">
{{ t('purchase.search') }} {{ t("purchase.search") }}
<div class="ms-2 d-inline-block mr-2"> <div class="ms-2 d-inline-block mr-2">
<input <input
type="text" type="text"
@@ -55,7 +55,6 @@ watch(locale, () => {
aria-label="Search invoice" aria-label="Search invoice"
/> />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -89,46 +88,39 @@ watch(locale, () => {
<path d="M6 15l6 -6l6 6" /> <path d="M6 15l6 -6l6 6" />
</svg> </svg>
</th> </th>
<th class="col-5">{{ t('purchase.item_name') }}</th> <th class="col-5">{{ t("purchase.item_name") }}</th>
<th class="col-1">{{ t('purchase.purpose') }}</th> <th class="col-1">{{ t("purchase.purpose") }}</th>
<th class="w-1">{{ t('purchase.unit') }}</th> <th class="w-1">{{ t("purchase.unit") }}</th>
<th class="w-1">{{ t('purchase.quantity') }}</th> <th class="w-1">{{ t("purchase.quantity") }}</th>
<th class="w-1">{{ t('purchase.unit_price') }}</th> <th class="w-1">{{ t("purchase.unit_price") }}</th>
<th class="w-1">{{ t('purchase.total_price') }}</th> <th class="w-1">{{ t("purchase.total_price") }}</th>
<th class="w-1">{{ t('purchase.created_at') }}</th> <th class="w-1">{{ t("purchase.created_at") }}</th>
<th class="w-1">{{ t('purchase.updated_at') }}</th> <th class="w-1">{{ t("purchase.updated_at") }}</th>
<th class="w-1">{{ t('purchase.status') }}</th> <th class="w-1">{{ t("purchase.status") }}</th>
<th class="w-1"></th> <th class="w-1"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td> <td>
<input <input
class="form-check-input m-0 align-middle" class="form-check-input m-0 align-middle"
type="checkbox" type="checkbox"
aria-label="Select invoice" aria-label="Select invoice"
/> />
</td> </td>
<td><span class="text-muted">001</span></td> <td><span class="text-muted">001</span></td>
<td> <td>办公室用纸</td>
办公室用纸 <td>办公用品</td>
</td> <td></td>
<td>办公用品</td> <td>10</td>
<td></td> <td>15.00</td>
<td>10</td> <td>150.00</td>
<td>15.00</td> <td>2024-06-01</td>
<td>150.00</td> <td>2024-06-05</td>
<td>2024-06-01</td> <td><span class="badge bg-success me-1"></span> 已完成</td>
<td>2024-06-05</td> <td class="text-end"></td>
<td>
<span class="badge bg-success me-1"></span> 已完成
</td>
<td class="text-end">
</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>