遗留-----

api/data_api/growth_data.go

Go 复制代码
package data_api

import (
	"blog_server/common/res"
	"blog_server/global"
	"blog_server/middlware"
	"blog_server/models"
	"blog_server/models/enum"
	"github.com/gin-gonic/gin"
	"strings"
	"time"
)

type GrowthDataRequest struct {
	Type int8 `form:"type",binding:"required,oneof=1 2 3"`
}
type GrowthDataResponse struct {
	GrowthRate int      `json:"growth_rate"` //增长率
	GrowthNum  int      `json:"growth_num"`  //增长数
	CountList  []int    `json:"count_list"`
	DateList   []string `json:"date_list"`
}

type Table struct {
	Date  string `gorm:"column:date"`
	Count int    `gorm:"column:count"`
}

func (DataApi) GrowthDataView(c *gin.Context) {
	cr := middlware.GetBind[GrowthDataRequest](c)
	now := time.Now()
	before7 := now.AddDate(0, 0, -7)

	var dataList []Table

	switch cr.Type {
	case 1:
		global.DB.Debug().Model(models.SiteFlowModel{}).Where("created_at >=? and created_at <?", before7.Format("2006-01-02")+":00:00:00", now.Format("2006-01-02 15:04:05")).
			Select("date(created_at) as date", "sum(count) as count").Group("date").Scan(&dataList)
	case 2:
		global.DB.Model(models.ArticleModel{}).Where("created_at >=? and created_at <? and status = ?", before7.Format("2006-01-02")+":00:00:00", now.Format("2006-01-02 15:04:05"), enum.ArticleStatusPublishes).
			Select("date(created_at) as date", "count(id) as count").Group("date").Scan(&dataList)
	case 3:
		global.DB.Debug().Model(models.UserModel{}).Where("created_at >=? and created_at <?", before7.Format("2006-01-02")+":00:00:00", now.Format("2006-01-02 15:04:05")).
			Select("date(created_at) as date", "count(id) as count").Group("date").Scan(&dataList)

	}
	var dateMap = map[string]int{}
	for _, model := range dataList {
		date := strings.Split(model.Date, "T")[0]
		dateMap[date] = model.Count
	}

	response := RegisterDataResponse{}
	for i := 0; i < 7; i++ {
		date := before7.AddDate(0, 0, i+1)
		dateF := date.Format("2006-01-02")
		count, _ := dateMap[dateF]
		response.CountList = append(response.CountList, count)
		response.DateList = append(response.DateList, dateF)
	}

	//算增长,找最后一个的前一个
	response.GrowthNum = response.CountList[6] - response.CountList[5]
	if response.CountList[5] == 0 {
		response.GrowthRate = 100
	} else {
		response.GrowthRate = int((float64(response.GrowthNum) / float64(response.CountList[5])) * 100)
	}

	res.OkWithData(response, c)
}

api/data_api/register_data.go

Go 复制代码
package data_api

import (
	"blog_server/common/res"
	"blog_server/global"
	"blog_server/models"
	"github.com/gin-gonic/gin"
	"time"
)

type RegisterDataResponse struct {
	GrowthRate int      `json:"growth_rate"` //增长率
	GrowthNum  int      `json:"growth_num"`  //增长数
	CountList  []int    `json:"count_list"`
	DateList   []string `json:"date_list"`
}

// 新增用户统计
func (DataApi) RegisterDataView(c *gin.Context) {
	now := time.Now()
	before7 := now.AddDate(0, 0, -7)
	var userList []models.UserModel
	global.DB.Debug().Find(&userList, "created_at >=? and created_at <?", before7.Format("2006-01-02")+":00:00:00", now.Format("2006-01-02 15:04:05"))
	var dateMap = map[string]int{}
	for _, model := range userList {
		date := model.CreatedAt.Format("2006-01-02")
		count, ok := dateMap[date]
		if !ok {
			dateMap[date] = 1
			continue
		}
		dateMap[date] = count + 1
	}

	response := RegisterDataResponse{}
	for i := 0; i < 7; i++ {
		date := before7.AddDate(0, 0, i+1)
		dateF := date.Format("2006-01-02")
		count, _ := dateMap[dateF]
		response.CountList = append(response.CountList, count)
		response.DateList = append(response.DateList, dateF)
	}

	//算增长,找最后一个的前一个
	response.GrowthNum = response.CountList[6] - response.CountList[5]
	response.GrowthRate = int((float64(response.GrowthNum) / float64(response.CountList[5])) * 100)
	res.OkWithData(response, c)
}

api/site_api/enter.go

Go 复制代码
package site_api

import (
	"blog_server/common/res"
	"blog_server/conf"
	"blog_server/core"
	"blog_server/global"
	"blog_server/middlware"
	"blog_server/services/redis_service/redis_site"
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"github.com/gin-gonic/gin"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"os"
)

type SiteApi struct {
}
type SiteInfoRequest struct {
	Name string `uri:"name" binding:"required"`
}
type QiNiu struct {
	Enable bool `json:"enable"`
}
type AI struct {
	Enable bool `json:"enable"`
}
type SiteInfoResponse struct {
	QiNiu QiNiu `json:"qi_niu"`
	AI    AI    `json:"ai"`
	conf.Site
}

func (SiteApi) SiteInfoView(c *gin.Context) {
	var cr SiteInfoRequest
	if err := c.ShouldBindUri(&cr); err != nil {
		res.FailWithError(err, c)
		return
	}

	if cr.Name == "site" {
		global.Config.Site.About.Version = global.Version
		redis_site.SetFlow()
		res.OkWithData(SiteInfoResponse{
			Site: global.Config.Site,
			QiNiu: QiNiu{
				Enable: global.Config.Qiniu.Enable,
			},
			AI: AI{
				Enable: global.Config.AI.Enable,
			},
		}, c)
		return
	}
	//判断是否是管理员
	middlware.AdminMiddleware(c)
	_, ok := c.Get("claims")
	if !ok {
		return
	}

	var data any
	switch cr.Name {
	case "email":
		rep := global.Config.Email
		rep.AuthCode = "******"
		data = rep
	case "qq":
		rep := global.Config.QQ
		rep.AppId = "******"
		data = rep
	case "qiniu":
		rep := global.Config.Qiniu
		rep.SecretKey = "******"
		data = rep
	case "ai":
		rep := global.Config.AI
		rep.SecretKey = "******"
		data = rep
	default:
		res.FailWithMsg("不存在的配置", c)
		return
	}

	res.OkWithData(data, c)
	return
}

func (SiteApi) SiteUpdateView(c *gin.Context) {

	var cr SiteInfoRequest
	err := c.ShouldBindUri(&cr)
	if err != nil {
		res.FailWithError(err, c)
		return
	}
	var rep any
	switch cr.Name {
	case "site":
		var data conf.Site
		err = c.ShouldBindJSON(&data)
		rep = data

	case "email":
		var data conf.Email
		err = c.ShouldBindJSON(&data)
		rep = data

	case "qq":
		var data conf.QQ
		err = c.ShouldBindJSON(&data)
		rep = data
	case "qiniu":
		var data conf.QiNiu
		err = c.ShouldBindJSON(&data)
		rep = data
	case "ai":
		var data conf.AI
		err = c.ShouldBindJSON(&data)
		rep = data
	default:
		res.FailWithMsg("不存在的配置", c)
		return
	}
	if err != nil {
		res.FailWithError(err, c)
		return
	}
	switch s := rep.(type) {
	case conf.Site:
		//判断站点信息更新前端文件部分
		err = UploadSite(s) //暂时没有处理函数,先设置为空,之后补充
		if err != nil {
			res.FailWithError(err, c)
			return
		}
		global.Config.Site = s
	case conf.Email:
		if s.AuthCode == "******" {
			s.AuthCode = global.Config.Email.AuthCode
		}
		global.Config.Email = s
	case conf.QQ:
		if s.AppId == "******" {
			s.AppId = global.Config.QQ.AppId
		}
		global.Config.QQ = s
	case conf.QiNiu:
		if s.SecretKey == "******" {
			s.SecretKey = global.Config.Qiniu.SecretKey
		}
		global.Config.Qiniu = s
	case conf.AI:
		if s.SecretKey == "******" {
			s.SecretKey = global.Config.Qiniu.SecretKey
		}
		global.Config.AI = s

	}

	core.SetConf()
	res.FailWithMsg("更新站成功", c)
	return
}

func (SiteApi) SiteInfoQQView(c *gin.Context) {
	res.OkWithData(global.Config.QQ.Url(), c)
}

func UploadSite(site conf.Site) error {
	if site.Project.Icon == "" && site.Project.Title == "" && site.Project.WebPath == "" && site.Seo.KeyWords == "" && site.Seo.Description == "" {
		return nil
	}
	if site.Project.WebPath == "" {
		return errors.New("请配置前端地址")
	}
	file, err := os.Open(site.Project.WebPath)
	if err != nil {
		return errors.New(fmt.Sprintf("%s 文件不存在", site.Project.WebPath))
	}
	doc, err := goquery.NewDocumentFromReader(file)
	if err != nil {
		logrus.Errorf("goquery 解析失败 %s", err)
		return errors.New("goquery 解析失败")
	}
	if site.Project.Title != "" {
		doc.Find("title").SetText(site.Project.Title)
	}
	if site.Project.Icon != "" {
		selection := doc.Find("link[rel='icon']").Length()
		if selection == 0 {
			//没有-》创建
			doc.Find("head").AppendHtml(fmt.Sprintf("<link rel=\"icon\" href=\"%s\">", site.Project.Icon))
		} else {
			//有修改
			doc.Find("link[rel='icon']").SetAttr("href", site.Project.Icon)
		}
	}
	if site.Seo.KeyWords != "" {
		selection := doc.Find("meta[name='keywords']").Length()
		if selection == 0 {
			//没有-》创建
			doc.Find("head").AppendHtml(fmt.Sprintf(" <meta name=\"keywords\" content=\"%s\">", site.Seo.KeyWords))
		} else {
			//有修改
			doc.Find("meta[name='keywords']").SetAttr("content", site.Seo.KeyWords)
		}
	}
	if site.Seo.Description != "" {
		selection := doc.Find("meta[name='description']").Length()
		if selection == 0 {
			//没有-》创建
			doc.Find("head").AppendHtml(fmt.Sprintf(" <meta name=\"description\" content=\"%s\">", site.Seo.Description))
		} else {
			//有修改
			doc.Find("meta[name='description']").SetAttr("content", site.Seo.Description)
		}
	}
	html, err := doc.Html()
	if err != nil {
		logrus.Errorf("生成html失败:%s", err)
		return errors.New("生成html失败")
	}
	err = os.WriteFile(site.Project.WebPath, []byte(html), 0666)
	if err != nil {
		logrus.Errorf("写入html失败:%s", err)
		return errors.New("写入html失败")
	}

	return nil
}

admin

src\components\common\g_card.vue

javascript 复制代码
<script setup lang="ts">
    interface Props {
      title: string
    }
    
    const props = defineProps<Props>()
    </script>
    
    <template>
      <div class="g_card">
        <div class="title">
          {{ props.title }}
        </div>
        <div class="body">
          <slot></slot>
        </div>
      </div>
    </template>
    
    <style lang="less">
    .g_card{
      background-color: var(--color-bg-1);
      border-radius: 5px;
      width: 100%;
      .title{
        padding: 20px;
        font-size: 18px;
        font-weight: 600;
        border-bottom: @g_border;
      }
      .body{
        padding: 20px;
      }
    }
    </style>

src\api\index.ts

javascript 复制代码
import axios from "axios";
import {Message} from "@arco-design/web-vue";
import {userStorei} from "@/stores/user_store";
import type {Ref} from "vue";
// 基础的响应类型,一般和后端项目的响应类型一致
export interface baseResponse<T> {
    code: number
    msg: string
    data: T
}
// 列表类型的响应类型
export interface listResponse<T> {
    list: T[]
    count: number
}
// URL的Query Parameters参数
export interface paramsType {
    key?: string
    limit?: number
    page?: number
    sort?: string
    [key: string]: any
}

export const useAxios = axios.create({
    timeout: 6000,
    baseURL: "", // 在使用前端代理的情况下,这里必须留空,不然会跨域
})
// axios请求拦截
useAxios.interceptors.request.use((config) => {
    const userStore = userStorei() //先去这个全局的store中拿到这个用户的token
    config.headers.set("Authorization", userStore.userInfo.token) //将token配置到请求头中
    return config
})
// axios响应拦截
useAxios.interceptors.response.use((res) => {
    // 响应拦截时先做一个判断,如果状态是200才是响应成功,将数据返回出去
    if (res.status === 200) {
        return res.data
    }
    return res
}, (res) => {
    // 如果有错误,将错误展示出来
    Message.error(res.message)
})
// 默认删除的接口,接受一个url,一个id_list列表,返回基础的响应数据(因为后端的删除接口的请求参数都是是id_list,所以可以放在前端作为一个默认的接口方便调用)
export function defaultDeleteApi(url: string, id_list: number[]): Promise<baseResponse<string>> {
    return useAxios.delete(url, {data: {id_list}})
}
// 默认post的接口,接受一个url,一个data,返回基础的响应数据
export function defaultPostApi(url: string, data: any): Promise<baseResponse<string>> {
    return useAxios.post(url, data)
}

export function defaultPutApi(url: string, data: any): Promise<baseResponse<string>> {
    return useAxios.put(url, data)
}
export interface optionsType {
    label: string
    value: number | string
}

export type optionsFunc = (params?: paramsType) => Promise<baseResponse<optionsType[]>>

export function getOptions(ref: Ref<optionsType[]>, func: optionsFunc, params?: paramsType){
    func(params).then((res)=>{
        ref.value = res.data
    })
}

// ========================================
export function articleCategoryOptionsApi(): Promise<baseResponse<optionsType[]>> {
    return useAxios.get("/api/category/options")
}

src\components\admin\g_list.vue

javascript 复制代码
<script setup lang='ts'>
import type { listResponse, paramsType, baseResponse,optionsType,optionsFunc } from "@/api/index";
// import {defaultDeleteApi}  from "@/api/index";
import { dateCurrentFormat, dateTemFormat } from "@/utils/date";
import { Message, type TableColumnData,type TableRowSelection,type TableData } from "@arco-design/web-vue";
import { reactive, ref } from "vue";


const loading = ref(false)
// 父组件传递给子组件的参数类型;
interface Props {
  url: (params?: paramsType) => Promise<baseResponse<listResponse<any>>>, //请求路由,返回的是一个函数,和接口的函数类型保持一致
  columns: TableColumnData[], //字段数据
  rowKey?:string //(主要使用为默认删除的字段名称)
  noDefaultDelete?: boolean // 不启用默认删除
  searchPlaceholder?: string //搜索框提示,
  addLabel?: string //添加按钮名称,
  editLabel?: string //编辑按钮名称
  deleteLabel?: string
  limit?: number
  noAdd?: boolean
  noEdit?: boolean
  noDelete?: boolean
  noActionGroup?:boolean
  noCheck?: boolean  //是否打开多选
  actionGroup?: actionGroupType[] //操作组
  noBatchDelete?: boolean // 是否没有批量删除
  filterGroup?: filterGroupType[]  //过滤组
  defaultParams?: Object
  noPage?:boolean
// 表单组件相关
  formList?:formListType[]
  addFormLabel?:string
  editFormLabel?:string
}
// 定义父组件传递给子组件的参数
const props = defineProps<Props>()

const {
  rowKey ='id',//如果rowkey没有传,就给默认值id,
  noDefaultDelete = false,
  searchPlaceholder = "搜索",//搜索框提示,默认是搜索
  addLabel = "添加",
  editLabel = "编辑",
  deleteLabel = "删除",
  limit = 10,
} = props 

// 表格组件的数据类型
const data = reactive<listResponse<any>>({
  list: [],
  count: 0,
})
// 请求参数类型
const params = reactive<paramsType>({
  limit: props.noPage ? -1 : limit,
})

// 获取数据的函数
async function getlist(newParams?: paramsType) {
  // 判断有没有默认的params
  if (props.defaultParams) {
    Object.assign(params, props.defaultParams)
  }
  loading.value = true
  if (newParams) {
    Object.assign(params, newParams)
  }
  const res = await props.url(params)
  loading.value = false
  if (res.code) {
    Message.error(res.msg)
    return
  }
  data.list = res.data.list || []
  data.count = res.data.count
}

// 打开多选的配置
const selectedKeys = ref([]);
const rowSelection = reactive<TableRowSelection>({
  type: 'checkbox',
  showCheckedAll: true,
  onlyCurrent: false,
});

const actionValue = ref()
export interface actionGroupType {
  label: string
  value?: number
  callback: (keyList: number[]) => void
}
const actionGroupOptions = ref<actionGroupType[]>([])
// 操作组函数:找到这个 actionGroupOptions 值,然后如果有的话就执行相对应的回调函数
  function actionGroupAction() {
  const action = actionGroupOptions.value.find((value) => value.value === actionValue.value)
  if (action) {
    action.callback(selectedKeys.value)
  }
}
// 初始化操作组,在开启默认批量删除时,添加一个批量删除的action,并执行回调函数删除对应的keyList的数据
function initActionGroup() {
  let index = 0
  if (!props.noBatchDelete) {
    actionGroupOptions.value.push({
      label: "批量删除",
      value: 1,
      callback: (keyList: number[]) => {
        baseDelete(keyList)
        selectedKeys.value = []
      }
    })
    index = 1
  }
  index++
  const actionGroup = props.actionGroup || []
  for (const action of actionGroup) {
    actionGroupOptions.value.push({
      label: action.label,
      value: index,
      callback: action.callback,
    })
  }
}

initActionGroup()
// 过滤组类型
export interface filterGroupType {
  label: string
  source: optionsType[] | optionsFunc  // 既可以处理options类型,也可以处理函数类型
  options?: optionsType[]
  column?: string
  params?: paramsType
  callback?: (value: number | string) => void
  width?: number
}
const filterGroups = ref<filterGroupType[]>([])



async function initFilterGroup() {
  filterGroups.value = []
  // 判断是否是函数类型,是的话,获取到数据,进行赋值
  for (const f of props.filterGroup || []) {
    if (typeof f.source === 'function') {
      const res = await f.source(f.params)
      if (res.code) {
        Message.error(res.msg)
        continue
      }
      f.options = res.data
    } else {
      f.options = f.source
    }
    if (!f.callback) {
      // 如果没有callback,那就走默认行为
      f.callback = (value) => {
        if (f.column) {
          const p: { [key: string]: any } = {}
          p[f.column] = value
          console.log(p)
          getlist(p)
        }
      }
    }

    filterGroups.value.push(f)
  }
}

initFilterGroup()


function edit(record: any) {
  if (props.formList?.length) {
    modalFormRef.value.setForm(record)
    visible.value = true
    return
  }
  emits("edit", record)
}

function add() {
  if (props.formList?.length) {
    visible.value = true
    return
  }

  emits("add")
}
function rowClick(record: TableData, ev: Event) {
  emits("row-click", record)
}
// 抛给父组件的时间
const emits = defineEmits<{
  (e: 'delete', keyList: number[] | string[]): void
  (e: 'add'): void
  (e: 'edit', record: any): void
  (e: "row-click", record: any): void //这个是table组件默认自带的点击行的触发函数,将其保留传到子组件中
}>()
// 删除逻辑进行封装
async function baseDelete(keyList: number[]) {
  if (noDefaultDelete) {
    // 不启用默认删除,将删除时间抛给父组件
    emits("delete", keyList)
    return
  }
// 拿到传递接口的路由,拿到路由路径
  const array = /\"(.*?)\"/.exec(props.url.toString()) 
  if (array?.length !== 2) {
    return
  }
  const url = array[1]

  const res = await defaultDeleteApi(url, keyList)

  if (res.code) {
    Message.error(res.msg)
    return
  }
  getlist()
  Message.success(res.msg)

}
// 刷新
function refresh() {
  getlist()
  Message.success("刷新成功")
}


async function remove(record: any) {
  const key = record[rowKey]
  baseDelete([key])
}
// 页数发生变化时
function pageChange(){
  getlist()
}
// 搜索
function search(){
  getlist()
}
getlist()


// 将父组件的数据函数抛出
defineExpose({
  getlist,
  data,
})


// ======================表单组件相关=====================
import G_modal_form from "./g_modal_form.vue";
import {defaultDeleteApi, defaultPostApi, defaultPutApi} from "@/api";
import type { formListType,emitFnType } from "./g_modal_form.vue";
const modalFormRef = ref()
const visible = ref(false)

// 默认删除和更新
async function create(form: any, fn?: emitFnType) {
  const array = /\"(.*?)\"/.exec(props.url.toString())
  if (array?.length !== 2) {
    return
  }
  const url = array[1]

  const res = await defaultPostApi(url, form)
  if (res.code) {
    Message.error(res.msg)
    return
  }
  getlist()
  if (fn) {
    fn(true)
  }
}

async function update(form: any, fn?: emitFnType) {
  const array = /\"(.*?)\"/.exec(props.url.toString())
  if (array?.length !== 2) {
    return
  }
  const url = array[1]

  const res = await defaultPutApi(url, form)
  if (res.code) {
    Message.error(res.msg)
    return
  }
  getlist()
  if (fn) {
    fn(true)
  }
}


</script>

<template>
  <div class="g_list_com">
    <!-- 将表格组件嵌套进来 -->
    <g_modal_form
    ref="modalFormRef"
    v-if="props.formList?.length"
    :add-label="props.addFormLabel"
    :edit-label="props.editFormLabel"
     @create="create"
     @update="update"
    v-model:visible="visible"
    :form-list="props.formList"
    
    ></g_modal_form>
    <div class="g_list_head">
      <div class="action_create" v-if="!noAdd">
        <a-button type="primary" @click="add">{{ addLabel }}</a-button>
      </div>
      <div class="action_group" v-if="!noActionGroup">
        <a-select style="width: 140px;" placeholder="操作" v-model="actionValue"
                  :options="actionGroupOptions"></a-select>
        <a-button type="primary" status="danger" @click="actionGroupAction" v-if="actionValue">执行</a-button>
      </div>
      <div class="actions_search">
        <a-input-search  :placeholder="searchPlaceholder" v-model="params.key" @search="search"></a-input-search>
      </div>
      <div class="action_search_slot">
        <slot name="search_other"></slot>
      </div>
        <div class="action_filter">
          <a-select v-for="item in filterGroups" :style="{width: item.width + 'px'}" allow-clear  @change="item.callback" :placeholder="item.label" :options="item.options" ></a-select>
       
      </div>
      <div class="action_refresh" @click="refresh">
        <icon-refresh></icon-refresh>
      </div>
    </div>
    <div class="g_list_body">
      <a-spin :loading="loading" tip="加载中...">
        <div class="g_list_table">
          <a-table  @row-click="rowClick" :data="data.list" :row-key="rowKey" v-model:selectedKeys="selectedKeys"
          :row-selection="props.noCheck ? undefined : rowSelection " >
             <!-- 通过 #columns 插槽和 <a-table-column> 组件可以使用模板的方法自定义列渲染 -->
            <template #columns>
            <template v-for="col in props.columns">
              <a-table-column v-if="col.dataIndex" v-bind="col"> </a-table-column>
              <a-table-column v-else-if="col.slotName" v-bind="col">
                <template #cell="data">
                  <!-- 如果是slotName是actin的话,显示操作按钮 -->
                  <div class="col_actions" v-if="col.slotName ==='action'">
                    <!-- action_left 预留在左边的按钮-->
                    <slot v-bind="data" name="action_left"></slot>
                    <!-- 编辑按钮 -->
                    <a-button type="primary" @click="edit(data.record)" v-if="!noEdit">{{ editLabel }}</a-button>
                    <!-- 删除按钮 -->
                    <a-popconfirm @ok="remove(data.record)" content="你确定要删除该记录吗?">
                      <a-button type="primary" status="danger" v-if="!noDelete">{{ deleteLabel }}</a-button>
                    </a-popconfirm>
                      <!-- action_left 预留在右边边的按钮-->
                    <slot v-bind="data" name="action_rigth"></slot>
                  </div>
                  <!-- 如果字段中有dateFormat就使用工具中的函数处理时间格式 -->
                  <div v-if="col.dateFormat">
                      {{ dateTemFormat(data.record[col.slotName],col.dateFormat) }}
                  </div>
                  <!-- 使用卡槽将传递给子组件,实现自定义 -->
                  <slot v-else :name="col.slotName" v-bind="data"></slot>
                </template>

              </a-table-column>
            </template>

          </template>
          </a-table>
        </div>
        <div class="g_list_page" v-if="!noPage">
          <!-- 分页 -->
          <a-pagination show-total @change="pageChange" :total="data.count" v-model:current="params.page" :page-size="params.limit"></a-pagination>
        </div>
      </a-spin>
    </div>
  </div>
</template>

<style lang='less'>
.g_list_com {
  .g_list_head {
    padding: 20px 20px 10px 20px;
    border-bottom: @g_border;
    display: flex;
    align-items: center;
    position: relative;

    .action_create,
    .action_group,
    .action_search,
    .action_search_slot {
      margin-right: 10px;
    }

    .action_group {
      display: flex;
      align-items: center;

      button {
        margin-left: 10px;
      }

    }

    .action_filter {
      .arco-select {
        margin-right: 10px;

        &:last-child {
          margin-right: 0;
        }
      }
    }


    .action_refresh {
      position: absolute;
      right: 20px;
      width: 30px;
      height: 30px;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: var(--color-fill-2);
      border-radius: 5px;
      cursor: pointer;
    }
  }

  .g_list_body {
    padding: 10px 20px 20px 20px;

    >.arco-spin {
      width: 100%;
    }

    .g_list_page {
      display: flex;
      justify-content: center;
      margin-top: 10px;
    }

    .col_actions {
      button {
        margin-right: 10px;

        &:last-child {
          margin-right: 0;
        }
      }
    }
  }
}
</style>

src\assets\publick.less

javascript 复制代码
/* WebKit内核浏览器(Chrome/Safari/Edge)专用语法 滚动条美化  只在scrollbar类id下生效,普通滚动条不受影响*/
.scrollbar::-webkit-scrollbar {
    width: 12px;        /* 垂直滚动条宽度 */
    height: 12px;       /* 水平滚动条高度 */
  }
  
  .scrollbar::-webkit-scrollbar-track {
    background:var(--color-fill-2);  /* 滚动条轨道背景 */
    border-radius: 6px;   /* 轨道圆角 */
  }
  
  .scrollbar::-webkit-scrollbar-thumb {
    background:var(--color-fill-1);  /* 滑块颜色 */
    border: 2px solid var(--color-fill-1);/* 白色边框营造悬浮感 */
    border-radius: 8px;   /* 滑块圆角 */
  }
  
  .scrollbar::-webkit-scrollbar-thumb:hover {
    background:var(--color-fill-4);  /* 悬停状态颜色 */
  }
  

src\main.ts

javascript 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import "@/assets/base.css"//引入初始化样式(放在最前面)
import "nprogress/nprogress.css";
import App from './App.vue'
import router from './router'
import "@/assets/iconfont.css" //引入iconfont图标
import ArcoVue from '@arco-design/web-vue'; //引入arco_design
import '@arco-design/web-vue/dist/arco.css';//引入arco_design 样式
import "@/assets/publick.less" //自定义样式
import ArcoVueIcon from '@arco-design/web-vue/es/icon'; //引入arco_design 图标
import {apiMock} from "@/mock"; //引入mock
import "@/assets/theme.css"
const app = createApp(App)
apiMock() //启动mock的启动函数
app.use(createPinia())
app.use(router)
app.use(ArcoVue); //注册arco_design
app.use(ArcoVueIcon);//注册arco_design图标
app.mount('#app')

管理員修改用户信息接口

api/user_api/admin_user_info_update.go

Go 复制代码
package user_api

import (
	"blog_server/common/res"
	"blog_server/global"
	"blog_server/models"
	"blog_server/models/enum"
	"blog_server/utils/mps"
	"github.com/gin-gonic/gin"
)

//1.可以更改新的用户名(30天内修改一次)不能那重复
//2.可以更新简介
//3.可以更改兴趣标签
//4.可以更新头像,昵称,(qq登录的不能修改)
//5.可以更新隐私设置
//6.可以更改主页样式

type AdminUserInfoUpdateRequest struct {
	UserID   uint           ` json:"user_id" binging:"required"`
	Username *string        ` json:"username" s-u:"username"`
	Nickname *string        ` json:"nickname" s-u:"nickname"`
	Avatar   *string        ` json:"avatar" s-u:"avatar"`
	Abstract *string        ` json:"abstract" s-u:"abstract"`
	Role     *enum.RoleType `json:"role" s-u:"role"`
}

func (UserApi) AdminUserInfoUpdateView(c *gin.Context) {
	var cr AdminUserInfoUpdateRequest
	err := c.ShouldBindJSON(&cr)
	if err != nil {
		res.FailWithError(err, c)
		return
	}

	userMap := mps.StructToMap(cr, "s-u")
	var user models.UserModel
	err = global.DB.Debug().Take(&user, cr.UserID).Error
	if err != nil {
		res.FailWithMsg("用户不存在", c)
		return
	}
	err = global.DB.Model(&user).Updates(userMap).Error
	if err != nil {
		res.FailWithMsg("用户配置信息修改失败", c)
		return
	}
	res.OkWithMsg("用户信息修改成功", c)

}

前端

==============banner=================================

1.修改index.ts中的idList为id_list 并关掉七牛enable

2.banner enter 修改添加type

3.src\components\web\f_cover_cutter.vue -->if (!store.siteInfo.qi_niu.enable)

4.banner enter 添加 Likes: []string{"cover", "href"},

====================================================

==========article===============

· 审核接口-》res.OkWithMsg("审核成功", c)

·// 用户置顶文章表

type UserTopArticleModel struct {

Model

UserID uint `gorm:"uniqueIndex:idx_name" json:"user_id"`

ArticleID uint `gorm:"uniqueIndex:idx_name" json:"article_id"`

UserModel UserModel `gorm:"foreignKey:user_id" json:"-"`

ArticleModel ArticleModel `gorm:"foreignKey:article_id" json:"-"`

}

· article列表接口添加admin置顶逻辑

·article_list.vue ->{title: "最后更新时间", dataIndex: 'updated_at', type: "date", dateFormat: "current"},form.user_id = record.id 修改userID为user_id

·user_list.vue ->{title: "发文数", dataIndex: 'article_count'},

=========================================

==========log============================

·log_api.ts将字段变为小写

·log接口-》res.FailWithMsg("日志读取成功", c)

·log_list.vue ->:class="{is_read: record.is_read}"

·将logType全部改为log_type

·src\views\admin\settings_manage\log_list.vue-><f_user v-if="record.user_id" :nickname="record.user_name" :avatar="record.avatar"></f_user>

=========================================

相关推荐
码农阿豪2 小时前
Django接金仓数据库:我踩过的坑和填坑指南
数据库·python·django
神仙别闹18 小时前
基于Python(Django)+MySQL 实现(Web)SQL智能检测系统的设计与实现
python·mysql·django
z小天才b1 天前
Django ORM、中间件与信号 — 完全指南
python·中间件·django
Mr数据杨2 天前
【Codex】前后端管理模块SOP自动化开发
django·codex·项目开发
烟雨孤舟2 天前
Django 后端项目企业级开发规范文档
后端·python·django
U盘失踪了2 天前
学习记录:requests Django登录测试脚本(解决CSRF、重定向问题)
笔记·python·学习·django·csrf
毕胜客源码3 天前
卷积神经网络的农作物识别系统(有技术文档)深度学习 图像识别 卷积神经网络 Django python 人工智能
人工智能·python·深度学习·cnn·django
ch_atu3 天前
序列化器的使用
django
计算机徐师兄3 天前
Python基于Django的创新实验室系统(附源码,文档说明)
python·django·创新实验室系统·python创新实验室系统·创新实验室·实验室系统·python实验室系统