初步完成wangeditor接入

dev
truthhun 2 years ago
parent 610a1547e1
commit 643cd69154

@ -0,0 +1,81 @@
syntax = "proto3";
import "google/protobuf/timestamp.proto";
import "gogoproto/gogo.proto";
// import "validate/validate.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
package api.v1;
option go_package = "moredoc/api/v1;v1";
option java_multiple_files = true;
option java_package = "api.v1";
message Article {
int64 id = 1;
string identifier = 2;
string author = 3;
int64 view_count = 4;
string title = 5;
string keywords = 6;
string description = 7;
string content = 8;
google.protobuf.Timestamp created_at = 9 [ (gogoproto.stdtime) = true ];
google.protobuf.Timestamp updated_at = 10 [ (gogoproto.stdtime) = true ];
}
message DeleteArticleRequest { repeated int64 id = 1; }
// ID
message GetArticleRequest {
int64 id = 1;
string identifier = 2;
}
message ListArticleRequest {
int64 page = 1;
int64 size = 2;
string wd = 3;
repeated string field = 4;
string order = 5;
}
message ListArticleReply {
int64 total = 1;
repeated Article article = 2;
}
service ArticleAPI {
rpc CreateArticle(Article) returns (google.protobuf.Empty) {
option (google.api.http) = {
post : '/api/v1/article',
body : '*',
};
}
rpc UpdateArticle(Article) returns (google.protobuf.Empty) {
option (google.api.http) = {
put : '/api/v1/article',
body : '*',
};
}
rpc DeleteArticle(DeleteArticleRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete : '/api/v1/article',
};
}
rpc GetArticle(GetArticleRequest) returns (Article) {
option (google.api.http) = {
get : '/api/v1/article',
};
}
rpc ListArticle(ListArticleRequest) returns (ListArticleReply) {
option (google.api.http) = {
get : '/api/v1/article/list',
};
}
}

@ -0,0 +1,164 @@
package biz
import (
"context"
pb "moredoc/api/v1"
"moredoc/middleware/auth"
"moredoc/model"
"moredoc/util"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"gorm.io/gorm"
)
type ArticleAPIService struct {
pb.UnimplementedArticleAPIServer
dbModel *model.DBModel
logger *zap.Logger
}
func NewArticleAPIService(dbModel *model.DBModel, logger *zap.Logger) (service *ArticleAPIService) {
return &ArticleAPIService{dbModel: dbModel, logger: logger.Named("ArticleAPIService")}
}
func (s *ArticleAPIService) checkPermission(ctx context.Context) (userClaims *auth.UserClaims, err error) {
return checkGRPCPermission(s.dbModel, ctx)
}
// CreateArticle 创建文章
func (s *ArticleAPIService) CreateArticle(ctx context.Context, req *pb.Article) (*emptypb.Empty, error) {
_, err := s.checkPermission(ctx)
if err != nil {
return nil, err
}
existArticle, _ := s.dbModel.GetArticleByIdentifier(req.Identifier, "id")
if existArticle.Id > 0 {
return nil, status.Errorf(codes.AlreadyExists, "文章标识符已存在")
}
article := &model.Article{}
err = util.CopyStruct(req, article)
if err != nil {
s.logger.Error("CreateArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "创建文章失败")
}
err = s.dbModel.CreateArticle(article)
if err != nil {
s.logger.Error("CreateArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "创建文章失败")
}
return &emptypb.Empty{}, nil
}
// UpdateArticle 更新文章。注意不支持更新identifier
func (s *ArticleAPIService) UpdateArticle(ctx context.Context, req *pb.Article) (*emptypb.Empty, error) {
_, err := s.checkPermission(ctx)
if err != nil {
return nil, err
}
article := &model.Article{}
err = util.CopyStruct(req, article)
if err != nil {
s.logger.Error("UpdateArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "更新文章失败")
}
err = s.dbModel.UpdateArticle(article)
if err != nil {
s.logger.Error("UpdateArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "更新文章失败")
}
return &emptypb.Empty{}, nil
}
// DeleteArticle 删除文章
func (s *ArticleAPIService) DeleteArticle(ctx context.Context, req *pb.DeleteArticleRequest) (*emptypb.Empty, error) {
_, err := s.checkPermission(ctx)
if err != nil {
return nil, err
}
err = s.dbModel.DeleteArticle(req.Id)
if err != nil {
s.logger.Error("DeleteArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "删除文章失败")
}
return &emptypb.Empty{}, nil
}
// GetArticle 根据id或文章标识获取文章
// 不需要鉴权
func (s *ArticleAPIService) GetArticle(ctx context.Context, req *pb.GetArticleRequest) (*pb.Article, error) {
var (
article model.Article
err error
)
if req.Id > 0 {
article, err = s.dbModel.GetArticle(req.Id)
if err != nil && err != gorm.ErrRecordNotFound {
s.logger.Error("GetArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "获取文章失败")
}
} else {
article, err = s.dbModel.GetArticleByIdentifier(req.Identifier)
if err != nil && err != gorm.ErrRecordNotFound {
s.logger.Error("GetArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "获取文章失败")
}
}
if article.Id == 0 {
return nil, status.Errorf(codes.NotFound, "文章不存在")
}
pbArticle := &pb.Article{}
util.CopyStruct(article, pbArticle)
return pbArticle, nil
}
// ListArticles 获取文章列表。
// 如果是有权限,则可以根据关键字来查询,否则只能简单查询
func (s *ArticleAPIService) ListArticle(ctx context.Context, req *pb.ListArticleRequest) (*pb.ListArticleReply, error) {
opt := &model.OptionGetArticleList{
Page: int(req.Page),
Size: int(req.Size_),
WithCount: true,
QueryLike: make(map[string][]interface{}),
}
_, err := s.checkPermission(ctx)
if err == nil {
opt.QueryLike["title"] = []interface{}{req.Wd}
opt.QueryLike["keywords"] = []interface{}{req.Wd}
opt.QueryLike["description"] = []interface{}{req.Wd}
opt.QueryLike["content"] = []interface{}{req.Wd}
}
articles, total, err := s.dbModel.GetArticleList(opt)
if err != nil && err != gorm.ErrRecordNotFound {
s.logger.Error("ListArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "获取文章列表失败")
}
var pbArticle []*pb.Article
err = util.CopyStruct(articles, &pbArticle)
if err != nil {
s.logger.Error("ListArticle", zap.Error(err))
return nil, status.Errorf(codes.Internal, "获取文章列表失败"+err.Error())
}
return &pb.ListArticleReply{
Total: total,
Article: pbArticle,
}, nil
}

@ -0,0 +1,148 @@
package model
import (
"time"
"go.uber.org/zap"
"gorm.io/gorm"
)
type Article struct {
Id int `form:"id" json:"id,omitempty" gorm:"primaryKey;autoIncrement;column:id;comment:;"`
Identifier string `form:"identifier" json:"identifier,omitempty" gorm:"column:identifier;type:varchar(64);size:64;index:identifier,unique;comment:文章标识,唯一;"`
Author string `form:"author" json:"author,omitempty" gorm:"column:author;type:varchar(64);size:64;comment:作者;"`
ViewCount int `form:"view_count" json:"view_count,omitempty" gorm:"column:view_count;type:int(11);size:11;default:0;comment:阅读;"`
Title string `form:"title" json:"title,omitempty" gorm:"column:title;type:varchar(255);size:255;comment:文章标题;"`
Keywords string `form:"keywords" json:"keywords,omitempty" gorm:"column:keywords;type:varchar(255);size:255;comment:关键字;"`
Description string `form:"description" json:"description,omitempty" gorm:"column:description;type:varchar(255);size:255;comment:摘要;"`
Content string `form:"content" json:"content,omitempty" gorm:"column:content;type:longtext;comment:内容;"`
CreatedAt time.Time `form:"created_at" json:"created_at,omitempty" gorm:"column:created_at;type:datetime;comment:创建时间;"`
UpdatedAt time.Time `form:"updated_at" json:"updated_at,omitempty" gorm:"column:updated_at;type:datetime;comment:更新时间;"`
}
func (Article) TableName() string {
return tablePrefix + "article"
}
// CreateArticle 创建Article
func (m *DBModel) CreateArticle(article *Article) (err error) {
err = m.db.Create(article).Error
if err != nil {
m.logger.Error("CreateArticle", zap.Error(err))
return
}
return
}
// UpdateArticle 更新Article如果需要更新指定字段则请指定updateFields参数
// 注意不支持更新identifier
func (m *DBModel) UpdateArticle(article *Article, updateFields ...string) (err error) {
db := m.db.Model(article)
tableName := Article{}.TableName()
updateFields = m.FilterValidFields(tableName, updateFields...)
if len(updateFields) > 0 { // 更新指定字段
db = db.Select(updateFields)
} else { // 更新全部字段,包括零值字段
db = db.Select(m.GetTableFields(tableName))
}
err = db.Where("id = ?", article.Id).Omit("identifier").Updates(article).Error
if err != nil {
m.logger.Error("UpdateArticle", zap.Error(err))
}
return
}
// GetArticle 根据id获取Article
func (m *DBModel) GetArticle(id interface{}, fields ...string) (article Article, err error) {
db := m.db
fields = m.FilterValidFields(Article{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
err = db.Where("id = ?", id).First(&article).Error
return
}
// GetArticleByIdentifier(identifier string, fields ...string) 根据唯一索引获取Article
func (m *DBModel) GetArticleByIdentifier(identifier string, fields ...string) (article Article, err error) {
db := m.db
fields = m.FilterValidFields(Article{}.TableName(), fields...)
if len(fields) > 0 {
db = db.Select(fields)
}
db = db.Where("identifier = ?", identifier)
err = db.First(&article).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetArticleByIdentifier", zap.Error(err))
return
}
return
}
type OptionGetArticleList struct {
Page int
Size int
WithCount bool // 是否返回总数
Ids []interface{} // id列表
SelectFields []string // 查询字段
QueryRange map[string][2]interface{} // map[field][]{min,max}
QueryIn map[string][]interface{} // map[field][]{value1,value2,...}
QueryLike map[string][]interface{} // map[field][]{value1,value2,...}
Sort []string
}
// GetArticleList 获取Article列表
func (m *DBModel) GetArticleList(opt *OptionGetArticleList) (articleList []Article, total int64, err error) {
tableName := Article{}.TableName()
db := m.db.Model(&Article{})
db = m.generateQueryRange(db, tableName, opt.QueryRange)
db = m.generateQueryIn(db, tableName, opt.QueryIn)
db = m.generateQueryLike(db, tableName, opt.QueryLike)
if len(opt.Ids) > 0 {
db = db.Where("id in (?)", opt.Ids)
}
if opt.WithCount {
err = db.Count(&total).Error
if err != nil {
m.logger.Error("GetArticleList", zap.Error(err))
return
}
}
opt.SelectFields = m.FilterValidFields(tableName, opt.SelectFields...)
if len(opt.SelectFields) > 0 {
db = db.Select(opt.SelectFields)
}
if len(opt.Sort) > 0 {
db = m.generateQuerySort(db, tableName, opt.Sort)
} else {
db = db.Order("id desc")
}
db = db.Offset((opt.Page - 1) * opt.Size).Limit(opt.Size)
err = db.Find(&articleList).Error
if err != nil && err != gorm.ErrRecordNotFound {
m.logger.Error("GetArticleList", zap.Error(err))
}
return
}
// DeleteArticle 删除数据
func (m *DBModel) DeleteArticle(ids []int64) (err error) {
err = m.db.Where("id in (?)", ids).Delete(&Article{}).Error
if err != nil {
m.logger.Error("DeleteArticle", zap.Error(err))
}
return
}

@ -145,6 +145,7 @@ func (m *DBModel) SyncDB() (err error) {
&Permission{},
&GroupPermission{},
&Logout{},
&Article{},
}
if err = m.db.AutoMigrate(tableModels...); err != nil {
m.logger.Fatal("SyncDB", zap.Error(err))

@ -103,5 +103,14 @@ func RegisterGRPCService(dbModel *model.DBModel, logger *zap.Logger, endpoint st
return
}
// 文章服务
articleAPIService := biz.NewArticleAPIService(dbModel, logger)
v1.RegisterArticleAPIServer(grpcServer, articleAPIService)
err = v1.RegisterArticleAPIHandlerFromEndpoint(context.Background(), gwmux, endpoint, dialOpts)
if err != nil {
logger.Error("RegisterArticleAPIHandlerFromEndpoint", zap.Error(err))
return
}
return
}

@ -0,0 +1,43 @@
import service from '~/utils/request'
export const createArticle = (data) => {
return service({
url: '/api/v1/article',
method: 'post',
data,
})
}
export const updateArticle = (data) => {
return service({
url: '/api/v1/article',
method: 'put',
data,
})
}
export const deleteArticle = (params) => {
return service({
url: '/api/v1/article',
method: 'delete',
params,
})
}
export const getArticle = (params) => {
return service({
url: '/api/v1/article',
method: 'get',
params,
})
}
export const listArticle = (params) => {
return service({
url: '/api/v1/article/list',
method: 'get',
params,
})
}

@ -0,0 +1,210 @@
<template>
<div class="com-form-article">
<el-form
ref="formArticle"
label-position="top"
label-width="80px"
:model="article"
>
<el-form-item
label="标题"
prop="title"
:rules="[
{ required: true, trigger: 'blur', message: '请输入文章标题' },
]"
>
<el-input
v-model="article.title"
placeholder="请输入文章标题"
clearable
></el-input>
</el-form-item>
<el-form-item
label="标识"
prop="identifier"
:rules="[
{
required: true,
trigger: 'blur',
message: '请输入文章标识,建议为字母和数字组合',
},
]"
>
<!-- 如果是编辑文章不允许修改文章标识 -->
<el-input
v-model="article.identifier"
placeholder="请输入文章标识,建议为字母和数字组合"
:disabled="article.id > 0"
clearable
></el-input>
</el-form-item>
<el-form-item label="作者">
<el-input
v-model="article.author"
placeholder="请输入文章作者,可为空,默认为当前登录用户"
></el-input>
</el-form-item>
<el-form-item label="关键字">
<el-input
v-model="article.keywords"
placeholder="请输入文章关键字,多个关键字用英文逗号分隔"
></el-input>
</el-form-item>
<el-form-item label="描述">
<el-input
v-model="article.description"
placeholder="请输入文章描述,可为空"
type="textarea"
rows="3"
></el-input>
</el-form-item>
<el-form-item label="内容" class="editor-item">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editor"
:default-config="toolbarConfig"
:mode="mode"
/>
<Editor
v-model="article.content"
style="height: 500px; overflow-y: hidden"
:default-config="editorConfig"
:mode="mode"
@onCreated="onCreated"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
class="btn-block"
icon="el-icon-check"
:loading="loading"
@click="onSubmit"
>提交</el-button
>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { Boot } from '@wangeditor/editor'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import markdownModule from '@wangeditor/plugin-md'
import { createArticle, updateArticle } from '~/api/article'
export default {
name: 'FormArticle',
components: {
Editor,
Toolbar,
},
props: {
initArticle: {
type: Object,
default: () => {
return {}
},
},
},
data() {
return {
loading: false,
article: {},
editor: null,
toolbarConfig: {},
editorConfig: { placeholder: '请输入内容...' },
mode: 'default', // 'default' or 'simple'
}
},
watch: {
initArticle: {
handler(val) {
this.article = { ...val }
},
immediate: true,
},
},
created() {
Boot.registerModule(markdownModule)
this.article = { ...this.initArticle }
},
beforeDestroy() {
const editor = this.editor
if (editor == null) return
editor.destroy() //
},
methods: {
onCreated(editor) {
this.editor = Object.seal(editor) // Object.seal()
},
onSubmit() {
this.$refs.formArticle.validate(async (valid) => {
if (!valid) {
return
}
this.loading = true
const article = { ...this.article }
if (this.article.id > 0) {
const res = await updateArticle(article)
if (res.status === 200) {
this.$message.success('修改成功')
this.resetFields()
this.$emit('success', res.data)
} else {
this.$message.error(res.data.message)
}
} else {
const res = await createArticle(article)
if (res.status === 200) {
this.$message.success('新增成功')
this.resetFields()
this.$emit('success', res.data)
} else {
this.$message.error(res.data.message)
}
}
this.loading = false
})
},
clearValidate() {
this.$refs.formArticle.clearValidate()
},
resetFields() {
this.article = {
id: 0,
title: '',
identifier: '',
keywords: '',
description: '',
content: '',
}
},
reset() {
this.resetFields()
this.clearValidate()
},
},
}
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>
<style lang="scss">
.com-form-article {
.editor-item {
.el-form-item__content {
overflow: hidden;
border: 1px solid #ccc;
border-radius: 4px;
line-height: unset;
}
h1 {
font-size: 1.7em;
}
}
.w-e-text-container [data-slate-editor] blockquote {
border-left-width: 4px;
}
.w-e-text-container [data-slate-editor] table th {
border-right-width: 1px !important;
}
}
</style>

@ -102,13 +102,13 @@ export default {
watch: {
initFriendlink: {
handler(val) {
this.friendlink = val
this.friendlink = { ...val }
},
immediate: true,
},
},
created() {
this.friendlink = { ...this.friendlink, ...this.initFriendlink }
this.friendlink = { ...this.initFriendlink }
},
methods: {
onSubmit() {
@ -144,7 +144,14 @@ export default {
this.$refs.formFriendlink.clearValidate()
},
resetFields() {
this.$refs.formFriendlink.resetFields()
this.friendlink = {
id: 0,
title: '',
link: '',
sort: 0,
enable: true,
description: '',
}
},
reset() {
this.resetFields()

@ -162,6 +162,11 @@ export default {
title: '横幅管理',
icon: 'el-icon-picture-outline',
},
{
page: '/admin/article',
title: '单页管理',
icon: 'el-icon-discover',
},
{
page: '/admin/friendlink',
title: '友链管理',

365
web/package-lock.json generated

@ -2236,6 +2236,11 @@
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
"dev": true
},
"@transloadit/prettier-bytes": {
"version": "0.0.7",
"resolved": "https://registry.npmmirror.com/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz",
"integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA=="
},
"@types/eslint": {
"version": "7.29.0",
"resolved": "https://registry.npmmirror.com/@types/eslint/-/eslint-7.29.0.tgz",
@ -2252,6 +2257,11 @@
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true
},
"@types/event-emitter": {
"version": "0.3.3",
"resolved": "https://registry.npmmirror.com/@types/event-emitter/-/event-emitter-0.3.3.tgz",
"integrity": "sha512-UfnOK1pIxO7P+EgPRZXD9jMpimd8QEFcEZ5R67R1UhGbv4zghU5+NE7U8M8G9H5Jc8FI51rqDWQs6FtUfq2e/Q=="
},
"@types/html-minifier-terser": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
@ -2477,6 +2487,53 @@
}
}
},
"@uppy/companion-client": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/@uppy/companion-client/-/companion-client-2.2.2.tgz",
"integrity": "sha512-5mTp2iq97/mYSisMaBtFRry6PTgZA6SIL7LePteOV5x0/DxKfrZW3DEiQERJmYpHzy7k8johpm2gHnEKto56Og==",
"requires": {
"@uppy/utils": "^4.1.2",
"namespace-emitter": "^2.0.1"
}
},
"@uppy/core": {
"version": "2.3.4",
"resolved": "https://registry.npmmirror.com/@uppy/core/-/core-2.3.4.tgz",
"integrity": "sha512-iWAqppC8FD8mMVqewavCz+TNaet6HPXitmGXpGGREGrakZ4FeuWytVdrelydzTdXx6vVKkOmI2FLztGg73sENQ==",
"requires": {
"@transloadit/prettier-bytes": "0.0.7",
"@uppy/store-default": "^2.1.1",
"@uppy/utils": "^4.1.3",
"lodash.throttle": "^4.1.1",
"mime-match": "^1.0.2",
"namespace-emitter": "^2.0.1",
"nanoid": "^3.1.25",
"preact": "^10.5.13"
}
},
"@uppy/store-default": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/@uppy/store-default/-/store-default-2.1.1.tgz",
"integrity": "sha512-xnpTxvot2SeAwGwbvmJ899ASk5tYXhmZzD/aCFsXePh/v8rNvR2pKlcQUH7cF/y4baUGq3FHO/daKCok/mpKqQ=="
},
"@uppy/utils": {
"version": "4.1.3",
"resolved": "https://registry.npmmirror.com/@uppy/utils/-/utils-4.1.3.tgz",
"integrity": "sha512-nTuMvwWYobnJcytDO3t+D6IkVq/Qs4Xv3vyoEZ+Iaf8gegZP+rEyoaFT2CK5XLRMienPyqRqNbIfRuFaOWSIFw==",
"requires": {
"lodash.throttle": "^4.1.1"
}
},
"@uppy/xhr-upload": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/@uppy/xhr-upload/-/xhr-upload-2.1.3.tgz",
"integrity": "sha512-YWOQ6myBVPs+mhNjfdWsQyMRWUlrDLMoaG7nvf/G6Y3GKZf8AyjFDjvvJ49XWQ+DaZOftGkHmF1uh/DBeGivJQ==",
"requires": {
"@uppy/companion-client": "^2.2.2",
"@uppy/utils": "^4.1.2",
"nanoid": "^3.1.25"
}
},
"@vue/babel-helper-vue-jsx-merge-props": {
"version": "1.4.0",
"resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz",
@ -2626,6 +2683,93 @@
}
}
},
"@wangeditor/basic-modules": {
"version": "1.1.6",
"resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.6.tgz",
"integrity": "sha512-wckcFm/kEAHpTn7dTmN0+7POFoygqt9bZdNHJUkdKObXtAerml8RdjrkHRcwJFCkSELbrNK63fvkwS0+FsabfA==",
"requires": {
"is-url": "^1.2.4"
}
},
"@wangeditor/code-highlight": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz",
"integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==",
"requires": {
"prismjs": "^1.23.0"
}
},
"@wangeditor/core": {
"version": "1.1.18",
"resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.18.tgz",
"integrity": "sha512-GZsW/8tm2Hc2hZKX8BZP9PO7vTZll9YXsOt+jKck3D2bj9nk4T0FZymuE5fq9ZHdo+MwLiXBHXS/D1SIGlVnGQ==",
"requires": {
"@types/event-emitter": "^0.3.3",
"event-emitter": "^0.3.5",
"html-void-elements": "^2.0.0",
"i18next": "^20.4.0",
"scroll-into-view-if-needed": "^2.2.28",
"slate-history": "^0.66.0"
}
},
"@wangeditor/editor": {
"version": "5.1.22",
"resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.22.tgz",
"integrity": "sha512-Bg+NakUvg6+UvkRT/xD9a0zMhPy/4kwhiv8Hp93csa4dg2u/dlZORRTjJCWaWmVK82PrtBG3VAcuw3rPdQCfag==",
"requires": {
"@uppy/core": "^2.1.1",
"@uppy/xhr-upload": "^2.0.3",
"@wangeditor/basic-modules": "^1.1.6",
"@wangeditor/code-highlight": "^1.0.3",
"@wangeditor/core": "^1.1.18",
"@wangeditor/list-module": "^1.0.5",
"@wangeditor/table-module": "^1.1.4",
"@wangeditor/upload-image-module": "^1.0.2",
"@wangeditor/video-module": "^1.1.4",
"dom7": "^3.0.0",
"is-hotkey": "^0.2.0",
"lodash.camelcase": "^4.3.0",
"lodash.clonedeep": "^4.5.0",
"lodash.debounce": "^4.0.8",
"lodash.foreach": "^4.5.0",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"lodash.toarray": "^4.4.0",
"nanoid": "^3.2.0",
"slate": "^0.72.0",
"snabbdom": "^3.1.0"
}
},
"@wangeditor/editor-for-vue": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-1.0.2.tgz",
"integrity": "sha512-BOENvAXJVtVXlE2X50AAvjV82YlCUeu5cbeR0cvEQHQjYtiVnJtq7HSoj85r2kTgGouI5OrpJG9BBEjSjUSPyA=="
},
"@wangeditor/list-module": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz",
"integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ=="
},
"@wangeditor/plugin-md": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/@wangeditor/plugin-md/-/plugin-md-1.0.0.tgz",
"integrity": "sha512-wpbusrhSNmb6keC5vmiaQ2nBBfiwwVWmfRp+zNB65nGrPHVRjaFQBNxTpGoL+rc02KCYdflzEbZAg+kzGtMXlQ=="
},
"@wangeditor/table-module": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz",
"integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w=="
},
"@wangeditor/upload-image-module": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz",
"integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA=="
},
"@wangeditor/video-module": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz",
"integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg=="
},
"@webassemblyjs/ast": {
"version": "1.9.0",
"resolved": "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.9.0.tgz",
@ -4098,6 +4242,11 @@
"vary": "~1.1.2"
}
},
"compute-scroll-into-view": {
"version": "1.0.17",
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
"integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
@ -4679,6 +4828,15 @@
"resolved": "https://registry.npmmirror.com/cyclist/-/cyclist-1.0.1.tgz",
"integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A=="
},
"d": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"requires": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmmirror.com/dashdash/-/dashdash-1.14.1.tgz",
@ -4903,6 +5061,14 @@
}
}
},
"dom7": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz",
"integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
"requires": {
"ssr-window": "^3.0.0-alpha.1"
}
},
"domain-browser": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/domain-browser/-/domain-browser-1.2.0.tgz",
@ -5179,6 +5345,35 @@
"is-symbol": "^1.0.2"
}
},
"es5-ext": {
"version": "0.10.62",
"resolved": "https://registry.npmmirror.com/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"requires": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"next-tick": "^1.1.0"
}
},
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"requires": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.1.1.tgz",
@ -5843,6 +6038,15 @@
"resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
},
"event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmmirror.com/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"requires": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -5929,6 +6133,21 @@
}
}
},
"ext": {
"version": "1.7.0",
"resolved": "https://registry.npmmirror.com/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"requires": {
"type": "^2.7.2"
},
"dependencies": {
"type": {
"version": "2.7.2",
"resolved": "https://registry.npmmirror.com/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
}
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz",
@ -7015,6 +7234,11 @@
"resolved": "https://registry.npmmirror.com/html-tags/-/html-tags-2.0.0.tgz",
"integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g=="
},
"html-void-elements": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz",
"integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A=="
},
"html-webpack-plugin": {
"version": "4.5.2",
"resolved": "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz",
@ -7223,6 +7447,14 @@
"ms": "^2.0.0"
}
},
"i18next": {
"version": "20.6.1",
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz",
"integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==",
"requires": {
"@babel/runtime": "^7.12.0"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -7270,6 +7502,11 @@
"resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ=="
},
"immer": {
"version": "9.0.16",
"resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.16.tgz",
"integrity": "sha512-qenGE7CstVm1NrHQbMh8YaSzTZTFNP3zPqr3YU0S0UY441j4bJTg4A2Hh5KAhwgaiU6ZZ1Ar6y/2f4TblnMReQ=="
},
"immutable": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.1.0.tgz",
@ -7623,6 +7860,11 @@
"integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
"dev": true
},
"is-hotkey": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/is-hotkey/-/is-hotkey-0.2.0.tgz",
"integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="
},
"is-lambda": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/is-lambda/-/is-lambda-1.0.1.tgz",
@ -7739,6 +7981,11 @@
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"dev": true
},
"is-url": {
"version": "1.2.4",
"resolved": "https://registry.npmmirror.com/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
},
"is-weakref": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/is-weakref/-/is-weakref-1.0.2.tgz",
@ -7996,11 +8243,31 @@
"resolved": "https://registry.npmmirror.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA=="
},
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
"lodash.foreach": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz",
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ=="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
},
"lodash.kebabcase": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
@ -8039,6 +8306,11 @@
"resolved": "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
},
"lodash.toarray": {
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
"integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw=="
},
"lodash.truncate": {
"version": "4.4.2",
"resolved": "https://registry.npmmirror.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
@ -8422,6 +8694,14 @@
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/mime-match/-/mime-match-1.0.2.tgz",
"integrity": "sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==",
"requires": {
"wildcard": "^1.1.0"
}
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
@ -8634,6 +8914,11 @@
"resolved": "https://registry.npmmirror.com/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
},
"namespace-emitter": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/namespace-emitter/-/namespace-emitter-2.0.1.tgz",
"integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g=="
},
"nan": {
"version": "2.16.0",
"resolved": "https://registry.npmmirror.com/nan/-/nan-2.16.0.tgz",
@ -8678,6 +8963,11 @@
"resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz",
@ -11625,6 +11915,11 @@
"uniq": "^1.0.1"
}
},
"preact": {
"version": "10.11.2",
"resolved": "https://registry.npmmirror.com/preact/-/preact-10.11.2.tgz",
"integrity": "sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw=="
},
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -11660,6 +11955,11 @@
"resolved": "https://registry.npmmirror.com/pretty-time/-/pretty-time-1.1.0.tgz",
"integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA=="
},
"prismjs": {
"version": "1.29.0",
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.29.0.tgz",
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz",
@ -12560,6 +12860,14 @@
"ajv-keywords": "^3.5.2"
}
},
"scroll-into-view-if-needed": {
"version": "2.2.29",
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.29.tgz",
"integrity": "sha512-hxpAR6AN+Gh53AdAimHM6C8oTN1ppwVZITihix+WqalywBeFcQ6LdQP5ABNl26nX8GTEL7VT+b8lKpdqq65wXg==",
"requires": {
"compute-scroll-into-view": "^1.0.17"
}
},
"scss-tokenizer": {
"version": "0.4.3",
"resolved": "https://registry.npmmirror.com/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz",
@ -12757,6 +13065,38 @@
"resolved": "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
},
"slate": {
"version": "0.72.8",
"resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz",
"integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==",
"requires": {
"immer": "^9.0.6",
"is-plain-object": "^5.0.0",
"tiny-warning": "^1.0.3"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
}
}
},
"slate-history": {
"version": "0.66.0",
"resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz",
"integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==",
"requires": {
"is-plain-object": "^5.0.0"
},
"dependencies": {
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
}
}
},
"slice-ansi": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz",
@ -12800,6 +13140,11 @@
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true
},
"snabbdom": {
"version": "3.5.1",
"resolved": "https://registry.npmmirror.com/snabbdom/-/snabbdom-3.5.1.tgz",
"integrity": "sha512-wHMNIOjkm/YNE5EM3RCbr/+DVgPg6AqQAX1eOxO46zYNvCXjKP5Y865tqQj3EXnaMBjkxmQA5jFuDpDK/dbfiA=="
},
"snapdragon": {
"version": "0.8.2",
"resolved": "https://registry.npmmirror.com/snapdragon/-/snapdragon-0.8.2.tgz",
@ -13077,6 +13422,11 @@
"tweetnacl": "~0.14.0"
}
},
"ssr-window": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz",
"integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA=="
},
"ssri": {
"version": "8.0.1",
"resolved": "https://registry.npmmirror.com/ssri/-/ssri-8.0.1.tgz",
@ -13802,6 +14152,11 @@
"resolved": "https://registry.npmmirror.com/timsort/-/timsort-0.3.0.tgz",
"integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz",
"integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
},
"tinycolor2": {
"version": "1.4.2",
"resolved": "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.4.2.tgz",
@ -13978,6 +14333,11 @@
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"dev": true
},
"type": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
@ -15437,6 +15797,11 @@
"string-width": "^4.0.0"
}
},
"wildcard": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/wildcard/-/wildcard-1.1.2.tgz",
"integrity": "sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng=="
},
"word-wrap": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.3.tgz",

@ -15,6 +15,9 @@
"dependencies": {
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/pwa": "^3.3.5",
"@wangeditor/editor": "^5.1.22",
"@wangeditor/editor-for-vue": "^1.0.2",
"@wangeditor/plugin-md": "^1.0.0",
"core-js": "^3.19.3",
"element-ui": "^2.15.6",
"hotkeys-js": "^3.10.0",

@ -0,0 +1,212 @@
<template>
<div>
<el-card shadow="never" class="search-card">
<FormSearch
:fields="searchFormFields"
:loading="loading"
:show-create="true"
:show-delete="true"
:disabled-delete="selectedRow.length === 0"
@onSearch="onSearch"
@onCreate="onCreate"
@onDelete="batchDelete"
/>
</el-card>
<el-card shadow="never" class="mgt-20px">
<TableList
:loading="loading"
:table-data="articles"
:fields="tableListFields"
:show-actions="true"
:show-view="false"
:show-edit="true"
:show-delete="true"
:show-select="true"
@selectRow="selectRow"
@editRow="editRow"
@deleteRow="deleteRow"
/>
</el-card>
<el-card shadow="never" class="mgt-20px">
<div class="text-right">
<el-pagination
background
:current-page="search.page"
:page-sizes="[10, 20, 50, 100]"
:page-size="search.size"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handlePageChange"
>
</el-pagination>
</div>
</el-card>
<el-dialog
:title="article.id ? '编辑文章' : '新增文章'"
:visible.sync="formArticleVisible"
>
<FormArticle
ref="articleForm"
:init-article="article"
@success="formSuccess"
/>
</el-dialog>
</div>
</template>
<script>
import { listArticle, deleteArticle, getArticle } from '~/api/article'
import TableList from '~/components/TableList.vue'
import FormSearch from '~/components/FormSearch.vue'
import FormArticle from '~/components/FormArticle.vue'
export default {
components: { TableList, FormSearch, FormArticle },
layout: 'admin',
data() {
return {
loading: false,
formArticleVisible: false,
search: {
wd: '',
page: 1,
status: [],
size: 10,
},
articles: [],
total: 0,
searchFormFields: [],
tableListFields: [],
selectedRow: [],
article: {
id: 0,
title: '',
identifier: '',
keywords: '',
description: '',
content: '',
},
}
},
async created() {
this.initSearchForm()
this.initTableListFields()
await this.listArticle()
},
methods: {
async listArticle() {
this.loading = true
const res = await listArticle(this.search)
if (res.status === 200) {
this.articles = res.data.article
this.total = res.data.total
} else {
this.$message.error(res.data.message)
}
this.loading = false
},
handleSizeChange(val) {
this.search.size = val
this.listArticle()
},
handlePageChange(val) {
this.search.page = val
this.listArticle()
},
onSearch(search) {
this.search = { ...this.search, page: 1, ...search }
this.listArticle()
},
onCreate() {
this.article = { id: 0 }
this.formArticleVisible = true
this.$nextTick(() => {
this.$refs.articleForm.reset()
})
},
async editRow(row) {
const res = await getArticle({ id: row.id })
if (res.status === 200) {
this.article = res.data
this.formArticleVisible = true
} else {
this.$message.error(res.data.message)
}
},
formSuccess() {
this.formArticleVisible = false
this.listArticle()
},
batchDelete() {
this.$confirm(
`您确定要删除选中的【${this.selectedRow.length}条】文章吗?删除之后不可恢复!`,
'温馨提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
const ids = this.selectedRow.map((item) => item.id)
const res = await deleteArticle({ id: ids })
if (res.status === 200) {
this.$message.success('删除成功')
this.listArticle()
} else {
this.$message.error(res.data.message)
}
})
.catch(() => {})
},
deleteRow(row) {
this.$confirm(
`您确定要删除文章【${row.title}】吗?删除之后不可恢复!`,
'温馨提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => {
const res = await deleteArticle({ id: row.id })
if (res.status === 200) {
this.$message.success('删除成功')
this.listArticle()
} else {
this.$message.error(res.data.message)
}
})
.catch(() => {})
},
selectRow(rows) {
this.selectedRow = rows
},
initSearchForm() {
this.searchFormFields = [
{
type: 'text',
label: '关键字',
name: 'wd',
placeholder: '请输入关键字',
},
]
},
initTableListFields() {
this.tableListFields = [
{ prop: 'id', label: 'ID', width: 80, type: 'number', fixed: 'left' },
{ prop: 'title', label: '标题', minWidth: 150, fixed: 'left' },
{ prop: 'identifier', label: '标识', width: 200 },
{ prop: 'view_count', label: '浏览', width: 80, type: 'number' },
{ prop: 'keywords', label: '关键字', width: 200 },
{ prop: 'description', label: '摘要', minWidth: 200 },
{ prop: 'created_at', label: '创建时间', width: 160, type: 'datetime' },
{ prop: 'updated_at', label: '更新时间', width: 160, type: 'datetime' },
]
},
},
}
</script>
<style></style>

@ -61,6 +61,12 @@ const cumstomPermissionMap = {
children: [],
pages: ['/admin/config'],
},
'api.v1.ArticleAPI': {
label: '单页管理',
path: 'ListArticle',
children: [],
pages: ['/admin/article'],
},
upload: {
id: 0,
label: '上传管理',

Loading…
Cancel
Save