feat: 处理冲突

This commit is contained in:
wangzhengkun 2022-08-11 23:15:05 +08:00
commit 9a0c83aa7e
32 changed files with 457 additions and 79 deletions

1
.gitignore vendored
View file

@ -15,3 +15,4 @@
# vendor/
.idea
backend/__debug_bin

View file

@ -9,5 +9,6 @@ type ApiGroup struct {
var ApiGroupApp = new(ApiGroup)
var (
userService = service.ServiceGroupApp.UserService
userService = service.ServiceGroupApp.UserService
operationService = service.ServiceGroupApp.OperationService
)

View file

@ -12,23 +12,23 @@ import (
"github.com/gin-gonic/gin"
)
func GeneratePaginationFromReq(c *gin.Context) (dto.PageInfo, bool) {
func GeneratePaginationFromReq(c *gin.Context) (*dto.PageInfo, bool) {
p, ok1 := c.GetQuery("page")
ps, ok2 := c.GetQuery("pageSize")
if !(ok1 && ok2) {
return dto.PageInfo{Page: 1, PageSize: 10}, false
return nil, false
}
page, err := strconv.Atoi(p)
if err != nil {
return dto.PageInfo{Page: 1, PageSize: 10}, false
return nil, false
}
pageSize, err := strconv.Atoi(ps)
if err != nil {
return dto.PageInfo{Page: 1, PageSize: 10}, false
return nil, false
}
return dto.PageInfo{Page: page, PageSize: pageSize}, false
return &dto.PageInfo{Page: page, PageSize: pageSize}, true
}
func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) {

View file

@ -0,0 +1,27 @@
package v1
import (
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/constant"
"github.com/gin-gonic/gin"
)
func (b *BaseApi) GetOperationList(c *gin.Context) {
pagenation, isOK := helper.GeneratePaginationFromReq(c)
if !isOK {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, constant.ErrPageGenerate)
return
}
total, list, err := operationService.Page(pagenation.Page, pagenation.PageSize)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, dto.PageResult{
Items: list,
Total: total,
})
}

View file

@ -36,6 +36,14 @@ func (b *BaseApi) Login(c *gin.Context) {
helper.SuccessWithData(c, user)
}
func (b *BaseApi) LogOut(c *gin.Context) {
if err := userService.LogOut(c); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) Captcha(c *gin.Context) {
captcha, err := captcha.CreateCaptcha()
if err != nil {

View file

@ -0,0 +1,25 @@
package dto
import (
"time"
)
type OperationLogBack struct {
Group string `json:"group"`
Source string `json:"source"`
Action string `json:"action"`
IP string `json:"ip"`
Path string `json:"path"`
Method string `json:"method"`
UserAgent string `json:"userAgent"`
Body string `json:"body"`
Resp string `json:"resp"`
Status int `json:"status"`
Latency time.Duration `json:"latency"`
ErrorMessage string `json:"errorMessage"`
Detail string `json:"detail"`
CreatedAt time.Time `json:"createdAt"`
}

View file

@ -8,20 +8,18 @@ import (
type OperationLog struct {
gorm.Model
Group string `gorm:"type:varchar(64)" json:"type"`
Group string `gorm:"type:varchar(64)" json:"group"`
Source string `gorm:"type:varchar(64)" json:"source"`
Action string `gorm:"type:varchar(64)" json:"action"`
IP string `gorm:"type:varchar(64)" json:"ip"`
Path string `gorm:"type:varchar(64)" json:"path"`
Method string `gorm:"type:varchar(64)" json:"method"`
UserAgent string `gorm:"type:text(65535)" json:"userAgent"`
Body string `gorm:"type:text(65535)" json:"body"`
Resp string `gorm:"type:text(65535)" json:"resp"`
UserAgent string `gorm:"type:varchar(256)" json:"userAgent"`
Body string `gorm:"type:longText" json:"body"`
Resp string `gorm:"type:longText" json:"resp"`
Status int `gorm:"type:varchar(64)" json:"status"`
Latency time.Duration `gorm:"type:varchar(64)" json:"latency"`
ErrorMessage string `gorm:"type:varchar(256)" json:"errorMessage"`
Latency time.Duration `gorm:"type:varchar(64)" json:"latency"`
Detail string `gorm:"type:longText" json:"detail"`
}

View file

@ -9,6 +9,7 @@ type OperationRepo struct{}
type IOperationRepo interface {
Create(user *model.OperationLog) error
Page(limit, offset int, opts ...DBOption) (int64, []model.OperationLog, error)
}
func NewIOperationService() IOperationRepo {
@ -18,3 +19,15 @@ func NewIOperationService() IOperationRepo {
func (u *OperationRepo) Create(user *model.OperationLog) error {
return global.DB.Create(user).Error
}
func (u *OperationRepo) Page(page, size int, opts ...DBOption) (int64, []model.OperationLog, error) {
var ops []model.OperationLog
db := global.DB.Model(&model.OperationLog{})
for _, opt := range opts {
db = opt(db)
}
count := int64(0)
db = db.Count(&count)
err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error
return count, ops, err
}

View file

@ -4,6 +4,7 @@ import "github.com/1Panel-dev/1Panel/app/repo"
type ServiceGroup struct {
UserService
OperationService
}
var ServiceGroupApp = new(ServiceGroup)

View file

@ -1,10 +1,20 @@
package service
import "github.com/1Panel-dev/1Panel/app/model"
import (
"encoding/json"
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/app/model"
"github.com/1Panel-dev/1Panel/constant"
"github.com/1Panel-dev/1Panel/global"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
type OperationService struct{}
type IOperationService interface {
Page(page, size int) (int64, interface{}, error)
Create(operation model.OperationLog) error
}
@ -15,3 +25,48 @@ func NewIOperationService() IOperationService {
func (u *OperationService) Create(operation model.OperationLog) error {
return operationRepo.Create(&operation)
}
func (u *OperationService) Page(page, size int) (int64, interface{}, error) {
total, ops, err := operationRepo.Page(page, size, commonRepo.WithOrderBy("created_at desc"))
var dtoOps []dto.OperationLogBack
for _, op := range ops {
var item dto.OperationLogBack
if err := copier.Copy(&item, &op); err != nil {
return 0, nil, errors.WithMessage(constant.ErrStructTransform, err.Error())
}
item.Body = filterSensitive(item.Body)
var res dto.Response
if err := json.Unmarshal([]byte(item.Resp), &res); err != nil {
global.LOG.Errorf("unmarshal failed, err: %+v", err)
dtoOps = append(dtoOps, item)
continue
}
item.Status = res.Code
if item.Status != 200 {
item.ErrorMessage = res.Msg
}
dtoOps = append(dtoOps, item)
}
return total, dtoOps, err
}
func filterSensitive(vars string) string {
var Sensitives = []string{"password", "Password"}
ops := make(map[string]string)
if err := json.Unmarshal([]byte(vars), &ops); err != nil {
return vars
}
for k := range ops {
for _, sen := range Sensitives {
if k == sen {
delete(ops, k)
continue
}
}
}
backStr, err := json.Marshal(ops)
if err != nil {
return ""
}
return string(backStr)
}

View file

@ -21,6 +21,7 @@ type IUserService interface {
Page(search dto.UserPage) (int64, interface{}, error)
Register(userDto dto.UserCreate) error
Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo, error)
LogOut(c *gin.Context) error
Delete(name string) error
Save(req model.User) error
Update(upMap map[string]interface{}) error
@ -33,7 +34,7 @@ func NewIUserService() IUserService {
func (u *UserService) Get(name string) (*dto.UserBack, error) {
user, err := userRepo.Get(commonRepo.WithByName(name))
if err != nil {
return nil, err
return nil, constant.ErrRecordNotFound
}
var dtoUser dto.UserBack
if err := copier.Copy(&dtoUser, &user); err != nil {
@ -56,14 +57,13 @@ func (u *UserService) Page(search dto.UserPage) (int64, interface{}, error) {
}
func (u *UserService) Register(userDto dto.UserCreate) error {
var user model.User
user, _ := userRepo.Get(commonRepo.WithByName(userDto.Name))
if user.ID != 0 {
return constant.ErrRecordExist
}
if err := copier.Copy(&user, &userDto); err != nil {
return errors.WithMessage(constant.ErrStructTransform, err.Error())
}
_ = global.DB.Where("name = ?", user.Name).First(&user).Error
if user.ID != 0 {
return errors.Wrap(constant.ErrRecordExist, "data exist")
}
return userRepo.Create(&user)
}
@ -111,6 +111,23 @@ func (u *UserService) Login(c *gin.Context, info dto.Login) (*dto.UserLoginInfo,
return &dto.UserLoginInfo{Name: user.Name}, err
}
func (u *UserService) LogOut(c *gin.Context) error {
sID, _ := c.Cookie(global.CONF.Session.SessionName)
if sID != "" {
c.SetCookie(global.CONF.Session.SessionName, "", -1, "", "", false, false)
}
sessionItem, err := global.SESSION.Get(c.Request, global.CONF.Session.SessionName)
if err != nil {
return err
}
sessionItem.Options.MaxAge = -1
if err := global.SESSION.Save(c.Request, c.Writer, sessionItem); err != nil {
return err
}
return nil
}
func (u *UserService) Delete(name string) error {
return userRepo.Delete(commonRepo.WithByName(name))
}

View file

@ -33,12 +33,11 @@ func Routers() *gin.Engine {
c.JSON(200, "ok")
})
}
PrivateGroup := Router.Group("/api/v1")
{
systemRouter.InitBaseRouter(PublicGroup) // 注册基础功能路由 不做鉴权
}
PrivateGroup := Router.Group("")
{
systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由
systemRouter.InitBaseRouter(PrivateGroup)
systemRouter.InitUserRouter(PrivateGroup)
systemRouter.InitOperationLogRouter(PrivateGroup)
}
return Router

View file

@ -2,10 +2,8 @@ package middleware
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
@ -18,26 +16,17 @@ import (
func OperationRecord() gin.HandlerFunc {
return func(c *gin.Context) {
var body []byte
if c.Request.Method != http.MethodGet {
var err error
body, err = ioutil.ReadAll(c.Request.Body)
if err != nil {
global.LOG.Errorf("read body from request failed, err: %v", err)
} else {
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}
if c.Request.Method == http.MethodGet || strings.Contains(c.Request.URL.Path, "search") {
c.Next()
return
}
var err error
body, err = ioutil.ReadAll(c.Request.Body)
if err != nil {
global.LOG.Errorf("read body from request failed, err: %v", err)
} else {
query := c.Request.URL.RawQuery
query, _ = url.QueryUnescape(query)
split := strings.Split(query, "&")
m := make(map[string]string)
for _, v := range split {
kv := strings.Split(v, "=")
if len(kv) == 2 {
m[kv[0]] = kv[1]
}
}
body, _ = json.Marshal(&m)
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
}
pathInfo := loadLogInfo(c.Request.URL.Path)
@ -62,8 +51,6 @@ func OperationRecord() gin.HandlerFunc {
c.Next()
latency := time.Since(now)
record.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()
record.Status = c.Writer.Status()
record.Latency = latency
record.Resp = writer.body.String()
@ -90,6 +77,7 @@ type pathInfo struct {
}
func loadLogInfo(path string) pathInfo {
path = strings.ReplaceAll(path, "/api/v1", "")
if !strings.Contains(path, "/") {
return pathInfo{}
}

View file

@ -3,6 +3,7 @@ package router
type RouterGroup struct {
BaseRouter
UserRouter
OperationLogRouter
}
var RouterGroupApp = new(RouterGroup)

View file

@ -0,0 +1,19 @@
package router
import (
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
"github.com/1Panel-dev/1Panel/middleware"
"github.com/gin-gonic/gin"
)
type OperationLogRouter struct{}
func (s *OperationLogRouter) InitOperationLogRouter(Router *gin.RouterGroup) {
operationRouter := Router.Group("operations")
operationRouter.Use(middleware.JwtAuth()).Use(middleware.SessionAuth())
baseApi := v1.ApiGroupApp.BaseApi
{
operationRouter.GET("", baseApi.GetOperationList)
}
}

View file

@ -14,6 +14,7 @@ func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) (R gin.IRoutes) {
baseApi := v1.ApiGroupApp.BaseApi
{
withRecordRouter.POST("login", baseApi.Login)
withRecordRouter.POST("logout", baseApi.LogOut)
baseRouter.GET("captcha", baseApi.Captcha)
}
return baseRouter

View file

@ -11,11 +11,8 @@ func Copy(to, from interface{}) error {
if err != nil {
return errors.Wrap(err, "marshal from data err")
}
err = json.Unmarshal(b, to)
if err != nil {
if err = json.Unmarshal(b, to); err != nil {
return errors.Wrap(err, "unmarshal to data err")
}
return nil
}

View file

@ -2,4 +2,4 @@
NODE_ENV = 'development'
# 本地环境接口地址
VITE_API_URL = '/api'
VITE_API_URL = '/api/v1'

View file

@ -6,7 +6,14 @@ import { ResultEnum } from '@/enums/http-enum';
import { checkStatus } from './helper/check-status';
import { ElMessage } from 'element-plus';
import router from '@/routers';
import i18n from '@/lang';
/**
* pinia 使
* https://github.com/vuejs/pinia/discussions/971
* https://github.com/vuejs/pinia/discussions/664#discussioncomment-1329898
* https://pinia.vuejs.org/core-concepts/outside-component-usage.html#single-page-applications
*/
// const globalStore = GlobalStore();
const axiosCanceler = new AxiosCanceler();
@ -34,10 +41,6 @@ class RequestHttp {
},
);
/**
* @description
* -> [] -> JS获取到信息
*/
this.service.interceptors.response.use(
(response: AxiosResponse) => {
const { data, config } = response;
@ -59,8 +62,7 @@ class RequestHttp {
async (error: AxiosError) => {
const { response } = error;
tryHideFullScreenLoading();
if (error.message.indexOf('timeout') !== -1)
ElMessage.error(i18n.global.t('commons.msg.requestTimeout'));
if (error.message.indexOf('timeout') !== -1) ElMessage.error('请求超时!请您稍后重试');
if (response) checkStatus(response.status);
if (!window.navigator.onLine) router.replace({ path: '/500' });
return Promise.reject(error);
@ -68,7 +70,6 @@ class RequestHttp {
);
}
// * 常用请求方法封装
get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
return this.service.get(url, { params, ..._object });
}

View file

@ -5,7 +5,7 @@ export interface Result {
}
// * 请求响应参数(包含data)
export interface ResultData<T = any> {
export interface ResultData<T> {
code: number;
message: string;
data: T;

View file

@ -0,0 +1,20 @@
import { DateTimeFormats } from '@intlify/core-base';
export interface ResOperationLog {
group: string;
source: string;
action: string;
ip: string;
path: string;
method: string;
userAgent: string;
body: string;
resp: string;
status: number;
latency: number;
errorMessage: string;
detail: string;
createdAt: DateTimeFormats;
}

View file

@ -8,3 +8,7 @@ export const loginApi = (params: Login.ReqLoginForm) => {
export const getCaptcha = () => {
return http.get<Login.ResCaptcha>(`/auth/captcha`);
};
export const logOutApi = () => {
return http.post<any>(`/auth/logout`);
};

View file

@ -0,0 +1,7 @@
import http from '@/api';
import { ResPage } from '../interface';
import { ResOperationLog } from '../interface/operation-log';
export const getOperationList = (currentPage: number, pageSize: number) => {
return http.get<ResPage<ResOperationLog>>(`/operations?page=${currentPage}&pageSize=${pageSize}`);
};

View file

@ -13,9 +13,7 @@
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- infoDialog -->
<InfoDialog ref="infoRef"></InfoDialog>
<!-- passwordDialog -->
<PasswordDialog ref="passwordRef"></PasswordDialog>
</template>
@ -26,32 +24,38 @@ import PasswordDialog from './password-dialog.vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { useRouter } from 'vue-router';
import { GlobalStore } from '@/store';
import { logOutApi } from '@/api/modules/login';
import i18n from '@/lang';
const router = useRouter();
const globalStore = GlobalStore();
// 退
const logout = () => {
ElMessageBox.confirm('您是否确认退出登录?', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
ElMessageBox.confirm(i18n.global.t('commons.msg.sureLogOut'), i18n.global.t('commons.msg.infoTitle'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'warning',
}).then(() => {
systemLogOut();
router.push({ name: 'login' });
globalStore.setUserInfo('');
globalStore.setLogStatus(false);
ElMessage({
type: 'success',
message: '退出登录成功!',
message: i18n.global.t('commons.msg.operationSuccess'),
});
});
};
const systemLogOut = async () => {
await logOutApi();
};
interface DialogExpose {
openDialog: () => void;
}
const infoRef = ref<null | DialogExpose>(null);
const passwordRef = ref<null | DialogExpose>(null);
//
const openDialog = (refName: string) => {
if (refName == 'infoRef') return infoRef.value?.openDialog();
passwordRef.value?.openDialog();

View file

@ -17,7 +17,7 @@ export const useDeleteData = <P = any, R = any>(
confirmType: HandleData.MessageType = 'error',
) => {
return new Promise((resolve, reject) => {
ElMessageBox.confirm(i18n.global.t(`${message}`) + '?', i18n.global.t('commons.msg.title'), {
ElMessageBox.confirm(i18n.global.t(`${message}`) + '?', i18n.global.t('commons.msg.deleteTitle'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: confirmType,

View file

@ -14,12 +14,13 @@ export default {
createdAt: 'Creation Time',
updatedAt: 'Update Time',
operate: 'Operations',
message: 'message',
},
msg: {
delete: 'This operation cannot be rolled back. Do you want to continue',
title: 'Delete',
deleteSuccess: 'Delete Success',
loginSuccss: 'Login Success',
loginSuccess: 'Login Success',
requestTimeout: 'The request timed out, please try again later',
},
login: {
@ -41,6 +42,7 @@ export default {
menu: {
home: 'Dashboard',
demo: 'Demo',
operations: 'Operation logs',
},
home: {
welcome: 'Welcome',
@ -66,4 +68,19 @@ export default {
changePassword: 'Change Password',
logout: 'Logout',
},
operations: {
detail: {
users: 'User',
auth: 'User',
login: ' login',
logout: ' logout',
post: ' create',
update: ' update',
delete: ' delete',
},
operatoin: 'operatoin',
status: 'status',
request: 'request',
response: 'response',
},
};

View file

@ -12,15 +12,20 @@ export default {
table: {
name: '名称',
createdAt: '创建时间',
date: '时间',
updatedAt: '更新时间',
operate: '操作',
message: '信息',
},
msg: {
delete: '此操作不可回滚,是否继续',
title: '删除',
deleteTitle: '删除',
deleteSuccess: '删除成功',
loginSuccss: '登陆成功',
loginSuccess: '登陆成功',
operationSuccess: '操作成功',
requestTimeout: '请求超时,请稍后重试',
infoTitle: '提示',
sureLogOut: '您是否确认退出登录?',
},
login: {
captchaHelper: '请输入验证码',
@ -41,6 +46,7 @@ export default {
menu: {
home: '概览',
demo: '样例',
operations: '操作记录',
},
home: {
@ -67,4 +73,19 @@ export default {
changePassword: '修改密码',
logout: '退出登录',
},
operations: {
detail: {
users: '用户',
auth: '用户',
post: '创建',
update: '更新',
delete: '删除',
login: '登录',
logout: '退出',
},
operatoin: '操作',
status: '状态',
request: '请求',
response: '响应',
},
};

View file

@ -0,0 +1,26 @@
import { Layout } from '@/routers/constant';
const operationRouter = {
sort: 2,
path: '/operations',
component: Layout,
redirect: '/operation',
meta: {
title: 'menu.operations',
icon: 'notebook',
},
children: [
{
path: '/operation',
name: 'OperationLog',
component: () => import('@/views/operation-log/index.vue'),
meta: {
keepAlive: true,
requiresAuth: true,
key: 'OperationLog',
},
},
],
};
export default operationRouter;

View file

@ -65,7 +65,6 @@ export const routes: RouteRecordRaw[] = [
},
...routerArray,
{
// 找不到路由重定向到404页面
path: '/:pathMatch(.*)',
redirect: { name: '404' },
},

View file

@ -97,7 +97,7 @@ const login = (formEl: FormInstance | undefined) => {
globalStore.setUserInfo(res.data.name);
globalStore.setLogStatus(true);
menuStore.setMenuList([]);
ElMessage.success(i18n.global.t('commons.msg.loginSuccss'));
ElMessage.success(i18n.global.t('commons.msg.loginSuccess'));
router.push({ name: 'home' });
} catch (error) {
loginVerify();

View file

@ -0,0 +1,128 @@
<template>
<LayoutContent :header="$t('menu.operations')">
<ComplexTable :pagination-config="paginationConfig" :data="data" @search="search">
<el-table-column :label="$t('operations.operatoin')" fix>
<template #default="{ row }">
{{ fmtOperation(row) }}
</template>
</el-table-column>
<el-table-column :label="$t('operations.status')" prop="status">
<template #default="{ row }">
<el-tag v-if="row.status == '200'" class="ml-2" type="success">{{ row.status }}</el-tag>
<div v-else>
<el-popover
placement="top-start"
:title="$t('commons.table.message')"
:width="400"
trigger="hover"
:content="row.errorMessage"
>
<template #reference>
<el-tag class="ml-2" type="warning">{{ row.status }}</el-tag>
</template>
</el-popover>
</div>
</template>
</el-table-column>
<el-table-column label="IP" prop="ip" />
<el-table-column align="left" :label="$t('operations.request')" prop="path">
<template #default="{ row }">
<div>
<el-popover :width="500" v-if="row.body" placement="left-start" trigger="click">
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
<pre class="pre">{{ fmtBody(row.body) }}</pre>
</div>
<template #reference>
<el-icon style="cursor: pointer"><warning /></el-icon>
</template>
</el-popover>
<span v-else></span>
</div>
</template>
</el-table-column>
<el-table-column align="left" :label="$t('operations.response')" prop="path">
<template #default="{ row }">
<div>
<el-popover :width="500" v-if="row.resp" placement="left-start" trigger="click">
<div style="word-wrap: break-word; font-size: 12px; white-space: normal">
<pre class="pre">{{ fmtBody(row.resp) }}</pre>
</div>
<template #reference>
<el-icon style="cursor: pointer"><warning /></el-icon>
</template>
</el-popover>
<span v-else></span>
</div>
</template>
</el-table-column>
<el-table-column
prop="createdAt"
:label="$t('commons.table.date')"
:formatter="dateFromat"
show-overflow-tooltip
/>
</ComplexTable>
</LayoutContent>
</template>
<script setup lang="ts">
import LayoutContent from '@/layout/layout-content.vue';
import ComplexTable from '@/components/complex-table/index.vue';
import { dateFromat } from '@/utils/util';
import { getOperationList } from '@/api/modules/operation-log';
import { onMounted, reactive, ref } from '@vue/runtime-core';
import { ResOperationLog } from '@/api/interface/operation-log';
import i18n from '@/lang';
const data = ref();
const paginationConfig = reactive({
currentPage: 1,
pageSize: 5,
total: 0,
});
const search = async () => {
const { currentPage, pageSize } = paginationConfig;
const res = await getOperationList(currentPage, pageSize);
data.value = res.data.items;
paginationConfig.total = res.data.total;
};
const fmtOperation = (row: ResOperationLog) => {
if (row.source == '' && row.action == '') {
return (
i18n.global.t('operations.detail.' + row.group.toLocaleLowerCase()) +
i18n.global.t('operations.detail.' + row.method.toLocaleLowerCase())
);
}
if (row.action == '') {
return (
i18n.global.t('operations.detail.' + row.group.toLocaleLowerCase()) +
i18n.global.t('operations.detail.' + row.source.toLocaleLowerCase())
);
}
return '';
};
const fmtBody = (value: string) => {
try {
return JSON.parse(value);
} catch (err) {
return value;
}
};
onMounted(() => {
search();
});
</script>
<style scoped lang="scss">
.pre {
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
}
</style>

View file

@ -41,10 +41,10 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
open: viteEnv.VITE_OPEN,
// https: false,
proxy: {
'/api': {
'/api/v1': {
target: 'http://localhost:9999',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
// rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},