Merge branch 'master' of code.gitlink.org.cn:zolo2468216537/coeditor

master
joefalmko 2 weeks ago
commit 672415881c

1
.gitignore vendored

@ -8,3 +8,4 @@ GinSkeleton/.idea/
ckeditor5/node_modules/*
GinSkeleton/config/gorm_v2.yml
GinSkeleton/public/storage
node_modules/

@ -338,6 +338,7 @@ export default {
this.$refs.editorToolbarElement.appendChild(editor.ui.view.toolbar.element);
this.$refs.editorMenuBarElement.appendChild(editor.ui.view.menuBarView.element);
//
const pageContent = '<h2>Congratulations on setting up CKEditor 5! 🎉</h2>\n<p>\n You\'ve successfully created a CKEditor 5 project. This powerful text editor will enhance your application, enabling rich text editing\n capabilities that are customizable and easy to use.\n</p>\n<h3>What\'s next?</h3>\n<ol>\n <li>\n <strong>Integrate into your app</strong>: time to bring the editing into your application. Take the code you created and add to your\n application.\n </li>\n <li>\n <strong>Explore features:</strong> Experiment with different plugins and toolbar options to discover what works best for your needs.\n </li>\n <li>\n <strong>Customize your editor:</strong> Tailor the editor\'s configuration to match your application\'s style and requirements. Or even\n write your plugin!\n </li>\n</ol>\n<p>\n Keep experimenting, and don\'t hesitate to push the boundaries of what you can achieve with CKEditor 5. Your feedback is invaluable to us\n as we strive to improve and evolve. Happy editing!\n</p>\n<h3>Helpful resources</h3>\n<ul>\n <li>📝 <a href="https://orders.ckeditor.com/trial/premium-features">Trial sign up</a>,</li>\n <li>📕 <a href="https://ckeditor.com/docs/ckeditor5/latest/installation/index.html">Documentation</a>,</li>\n <li>⭐️ <a href="https://github.com/ckeditor/ckeditor5">GitHub</a> (star us if you can!),</li>\n <li>🏠 <a href="https://ckeditor.com">CKEditor Homepage</a>,</li>\n <li>🧑‍💻 <a href="https://ckeditor.com/ckeditor-5/demo/">CKEditor 5 Demos</a>,</li>\n</ul>\n<h3>Need help?</h3>\n<p>\n See this text, but the editor is not starting up? Check the browser\'s console for clues and guidance. It may be related to an incorrect\n license key if you use premium features or another feature-related requirement. If you cannot make it work, file a GitHub issue, and we\n will help as soon as possible!\n</p>\n';
// pageContent
// TODO

File diff suppressed because it is too large Load Diff

@ -8,9 +8,14 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@ckeditor/ckeditor5-vue": "^7.2.0",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.3",
"ckeditor5": "^43.3.1",
"core-js": "^3.8.3",
"element-plus": "^2.8.7",
"file-saver": "^2.0.5",
"html-docx-js-typescript": "^0.1.5",
"jquery": "^3.7.1",
"jwt-decode": "^4.0.0",
"vue": "^3.2.13",

@ -1,12 +1,10 @@
<template>
<div class="home">
<div class="container">
<div class="card">
<div class="card col-md-10 offset-md-1">
<div class="card-body">
<slot></slot>
</div>
</div>
</div>
</div>
</template>

@ -1,72 +0,0 @@
<template>
<ContentBase>
<div class="file-item" v-for="item in items" :key="item.name">
<div class="file-item">
<div @dblclick="handledouleclick(item.name,item.type)" class="file-item-icon">
<img v-if="item.type==='folder'" src="@/assets/folder_icon.png" alt="">
<img v-else src="@/assets/file_icon.png" alt="">
</div>
<div class="file-item-title">
{{ item.name }}
</div>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from './ContentBase';
export default {
name: 'ContentShow',
components:{
ContentBase,
},
props:{
items:{
type:Object,
required:true,
}
},
setup(props,context){
const handledouleclick = (name,type) => {
context.emit("handledoubleclick",name,type);
}
return {
handledouleclick,
}
},
}
</script>
<style scoped>
.file-item{
width: 8vw;
display: inline-block;
}
.file-item {
width: 8vw;
height: 8vw;
overflow: hidden;
display: inline-block;
margin: 0 0.1vw 1vw 0.1vw;
border: 0.1vh solid white;
}
.file-item-icon > img {
width: 6vw;
height: 6vw;
}
.file-item-title{
width: 6vw;
height: 4vw;
text-align: center;
overflow: hidden;
font-size: 1.2vw;
word-break: break-all;
}
</style>

@ -0,0 +1,86 @@
<template>
<div @click.stop="leftclick()" @dblclick="doubleclick()" @contextmenu.stop.prevent="rightclick($event)"
:class="{ 'file-item-selected' : item.is_selected }"
>
<div class="file-item-icon" >
<img src="@/assets/file_icon.png" alt="">
</div>
<FileTitle>
{{ item.name }}
</FileTitle>
</div>
</template>
<script>
import FileTitle from './FileTitle.vue';
export default {
name: 'FileFiled',
components:{
FileTitle,
},
props:{
item:{
type:Object,
required:true,
}
},
setup(props,context){
const leftclick = () => {
context.emit('fileleftclick',props.item);
}
const rightclick = (event) => {
context.emit('filerightclick',{
item:props.item,
menuposition:{
top: event.clientY + 'px',
left: event.clientX + 'px',
},
});
}
const doubleclick = () => {
context.emit('open_file');
}
return {
leftclick,
rightclick,
doubleclick,
}
}
}
</script>
<style scoped>
.file-item {
width: 8vw;
height: 8vw;
overflow: hidden;
display: inline-block;
margin: 0 0.1vw 1vw 0.1vw;
border: 0.1vh solid white;
}
.file-item-icon > img {
width: 6vw;
height: 6vw;
}
.file-item-title{
width: 6vw;
height: 4vw;
text-align: center;
overflow: hidden;
font-size: 1.2vw;
word-break: break-all;
}
.file-item-selected {
border: 0.1vh solid #99d1ff;
background: #cce8ff;
}
</style>

@ -0,0 +1,43 @@
<template>
<div class="file-item-title">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'FileTitle',
}
</script>
<style scoped>
.file-item {
width: 8vw;
height: 8vw;
overflow: hidden;
display: inline-block;
margin: 0 0.1vw 1vw 0.1vw;
border: 0.1vh solid white;
}
.file-item-icon > img {
width: 6vw;
height: 6vw;
}
.file-item-title{
width: 6vw;
height: 4vw;
text-align: center;
overflow: hidden;
font-size: 1.2vw;
word-break: break-all;
}
.file-item-selected {
border: 0.1vh solid #99d1ff;
background: #cce8ff;
}
</style>

@ -0,0 +1,90 @@
<template>
<div @click.stop="leftclick()" @dblclick="doubleclick()" @contextmenu.stop.prevent="rightclick($event)"
:class="{ 'file-item-selected' : item.is_selected }"
>
<div class="file-item-icon" >
<img src="@/assets/folder_icon.png" alt="">
</div>
<FileTitle>
{{ item.name }}
</FileTitle>
</div>
</template>
<script>
import FileTitle from './FileTitle.vue';
import { useStore } from 'vuex';
export default {
name: 'FolderFiled',
components:{
FileTitle,
},
props:{
item:{
type:Object,
required:true,
}
},
setup(props,context){
let store = useStore();
const leftclick = () => {
context.emit('fileleftclick',props.item);
}
const rightclick = (event) => {
context.emit('filerightclick',{
item:props.item,
menuposition:{
top: event.clientY + 'px',
left: event.clientX + 'px',
},
})
}
const doubleclick = () => {
store.commit('forwardPath',props.item.name + '/');
context.emit('ls');
}
return {
leftclick,
rightclick,
doubleclick,
}
}
}
</script>
<style scoped>
.file-item {
width: 8vw;
height: 8vw;
overflow: hidden;
display: inline-block;
margin: 0 0.1vw 1vw 0.1vw;
border: 0.1vh solid white;
}
.file-item-icon > img {
width: 6vw;
height: 6vw;
}
.file-item-title{
width: 6vw;
height: 4vw;
text-align: center;
overflow: hidden;
font-size: 1.2vw;
word-break: break-all;
}
.file-item-selected {
border: 0.1vh solid #99d1ff;
background: #cce8ff;
}
</style>

@ -1,21 +1,44 @@
<template>
<nav class="nav">
<a class="nav-link" href="#">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/>
</svg>
</a>
<a class="nav-link" href="#">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
</svg>
</a>
</nav>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<router-link class="navbar-brand" :to="{name: 'home'}">Coeditor</router-link>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav ms-auto" >
<li class="nav-item">
<router-link v-if="!$store.state.user.is_login" class="nav-link" :to="{name: 'login'}"></router-link>
<router-link v-else class="nav-link" :to="{name: 'userprofile',params:{userId: $store.state.user.id}}">{{ $store.state.user.username }}</router-link>
</li>
<li class="nav-item">
<router-link v-if="!$store.state.user.is_login" class="nav-link" :to="{name: 'register'}"></router-link>
<a v-else class="nav-link" style="cursor: pointer" @click="logout">退</a>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
import { useStore } from 'vuex';
import router from '../router/index.js';
export default ({
name:"NavBar",
setup(){
const store = useStore();
// store.commit('dd');
const logout = () => {
store.commit('logout');
router.push('/');
location.reload();
}
return {
store,
logout,
}
}
})
</script>

@ -0,0 +1,37 @@
<template>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav col-md-10 offset-md-1">
<li v-for="(path,index) in $store.state.user.path" :key="index" @click="jump(index)" class="nav-item">
<a class="nav-link" href="#">{{ path }}</a>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
import { useStore } from 'vuex'
export default {
name: 'PathBar',
setup(props,context){
let store = useStore();
const jump = (index) => {
while (store.state.user.path.length > index + 1) {
store.commit('backPath');
}
context.emit('ls');
}
return {
store,
jump,
}
}
}
</script>

@ -0,0 +1,120 @@
<template>
<div>
<ul v-if="menutype === 'filemenu'" class="file-menu" :style="{top:menuposition.top,left:menuposition.left}">
<li>
<div @click="open_file">
打开
</div>
</li>
<li>
<div>
复制
</div>
</li>
<li>
<div>
粘贴
</div>
</li>
<li>
<div>
重命名
</div>
</li>
<li>
<div>
删除
</div>
</li>
</ul>
<ul v-else class="context-menu" :style="{top:menuposition.top,left:menuposition.left}">
<li>
<div>
新建
</div>
</li>
<li>
<div>
刷新
</div>
</li>
<li>
<div>
粘贴
</div>
</li>
</ul>
</div>
</template>
<script>
// import { computed } from 'vue';
export default {
name:"RightMenu",
props:{
menuposition:{
type:Object,
required:true,
},
menutype:{
type:String,
required:true,
}
},
setup(props,context){
const open_file = () => {
context.emit('open_file');
}
return {
open_file,
}
}
}
</script>
<style scoped>
.file-menu {
/* 菜单样式 */
background-color: pink;
border: 1px solid #ccc;
position: fixed;
padding: 0; /* 移除默认的内边距 */
margin: 0;
list-style: none;
/* 其他样式 */
}
.file-menu > li {
padding: 10px; /* 添加内边距以确保内容不紧贴边框 */
text-align: left; /* 内容左对齐 */
width: 100%; /* 列表项占据父容器的全部宽度 */
box-sizing: border-box; /* 包含内边距和边框在内计算元素的总宽度 */
background-color: #f9f9f9; /* 可选:为列表项添加背景色 */
border-bottom: 1px solid #ddd; /* 可选:为列表项添加底部边框 */
cursor:pointer;
}
.context-menu {
/* 菜单样式 */
background-color: pink;
border: 1px solid #ccc;
position: fixed;
padding: 0; /* 移除默认的内边距 */
margin: 0;
list-style: none;
/* 其他样式 */
}
.context-menu > li {
padding: 10px; /* 添加内边距以确保内容不紧贴边框 */
text-align: left; /* 内容左对齐 */
width: 100%; /* 列表项占据父容器的全部宽度 */
box-sizing: border-box; /* 包含内边距和边框在内计算元素的总宽度 */
background-color: #f9f9f9; /* 可选:为列表项添加背景色 */
border-bottom: 1px solid #ddd; /* 可选:为列表项添加底部边框 */
cursor:pointer;
}
</style>

@ -0,0 +1,212 @@
import {
Plugin,
ButtonView,
createDropdown,
Collection,
addListToDropdown,
} from 'ckeditor5';
import { asBlob } from 'html-docx-js-typescript'
import { saveAs } from 'file-saver';
import {
getStyle,
getPageContent
} from './utils';
// 导出为docx插件
class Export2Word extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add('ExportToWord', () => {
// The button will be an instance of ButtonView.
const button = new ButtonView();
button.set({
label: '导出为docx',
// withText: true
tooltip: true,
// 图标 直接插入svg文件
icon: '<svg t="1730216969869" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2612" width="200" height="200"><path d="M576 832H224c-17.6 0-32-14.4-32-32V192h608c17.6 0 32 14.4 32 32v288c0 17.7 14.3 32 32 32s32-14.3 32-32V224c0-52.9-43.1-96-96-96H160c-17.7 0-32 14.3-32 32v640c0 52.9 43.1 96 96 96h352c17.7 0 32-14.3 32-32s-14.3-32-32-32z" p-id="2613"></path><path d="M951.7 757.5c0.2-0.2 0.4-0.5 0.6-0.7 0.1-0.2 0.3-0.3 0.4-0.5 0.2-0.3 0.4-0.6 0.7-0.8 0.1-0.1 0.2-0.3 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.2 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.3 0.2-0.4 0.2-0.3 0.4-0.6 0.5-0.9 0.1-0.1 0.2-0.3 0.3-0.4 0.2-0.3 0.3-0.6 0.4-0.9 0.1-0.2 0.2-0.4 0.3-0.5 0.1-0.3 0.2-0.5 0.4-0.8 0.1-0.2 0.2-0.4 0.3-0.7 0.1-0.2 0.2-0.5 0.3-0.7 0.1-0.3 0.2-0.5 0.3-0.8 0.1-0.2 0.1-0.4 0.2-0.6l0.3-0.9c0.1-0.2 0.1-0.4 0.2-0.6 0.1-0.3 0.2-0.6 0.3-1 0-0.2 0.1-0.3 0.1-0.5 0.1-0.3 0.2-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.5 0.1-0.3 0.1-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.6 0-0.3 0.1-0.6 0.1-0.9 0-0.3 0-0.5 0.1-0.8 0-0.2 0-0.5 0.1-0.7 0.1-1.1 0.1-2.1 0-3.2 0-0.3 0-0.5-0.1-0.7 0-0.3 0-0.5-0.1-0.8 0-0.3-0.1-0.6-0.1-0.9 0-0.2 0-0.4-0.1-0.6 0-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.4-0.1-0.5-0.1-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.3-0.1-0.5-0.1-0.3-0.2-0.6-0.3-1-0.1-0.2-0.1-0.4-0.2-0.6l-0.3-0.9c-0.1-0.2-0.1-0.4-0.2-0.6-0.1-0.3-0.2-0.5-0.3-0.8-0.1-0.2-0.2-0.5-0.3-0.7-0.1-0.2-0.2-0.4-0.3-0.7-0.1-0.3-0.2-0.5-0.4-0.8-0.1-0.2-0.2-0.4-0.3-0.5-0.1-0.3-0.3-0.6-0.4-0.9-0.1-0.2-0.2-0.3-0.3-0.4-0.2-0.3-0.3-0.6-0.5-0.9-0.1-0.1-0.2-0.3-0.2-0.4l-0.6-0.9c-0.1-0.1-0.2-0.2-0.3-0.4l-0.6-0.9c-0.1-0.1-0.2-0.3-0.3-0.4-0.2-0.3-0.4-0.6-0.7-0.8-0.1-0.2-0.3-0.3-0.4-0.5-0.2-0.2-0.4-0.5-0.6-0.7-0.2-0.2-0.4-0.5-0.7-0.7l-0.4-0.4-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l73.4 73.4H592c-17.7 0-32 14.3-32 32s14.3 32 32 32h258.7l-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3 6.2 6.2 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l128-128 0.4-0.4c0.4-0.6 0.6-0.9 0.8-1.1z" p-id="2614"></path><path d="M709.3 320.4c-17.4-2.9-33.9 8.9-36.8 26.3l-42.8 256.9-87.3-261.8c-4.4-13-16.6-21.8-30.4-21.8-13.8 0-26 8.8-30.4 21.9l-87.3 261.8-42.8-256.9c-2.9-17.4-19.4-29.2-36.8-26.3-17.4 2.9-29.2 19.4-26.3 36.8l64 384c2.4 14.5 14.4 25.5 29.1 26.6 14.7 1.1 28.2-7.8 32.9-21.8L512 453.2l66.4 199.1c3.9 11.8 15 19.8 27.4 19.8h52.9c14.1 0 26.2-10.2 28.5-24.1l48.4-290.6c2.9-17.6-8.9-34.1-26.3-37z" p-id="2615"></path></svg>'
});
// Execute a callback function when the button is clicked
button.on('execute', () => {
const pageContent = getPageContent();
const style = getStyle();
const page = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>@media print{@page{size:A4 portrait;margin:0cm 3cm}@page:left{margin-left:2.5cm;margin-right:2.7cm;}@page:right{margin-left:2.7cm;margin-right:2.5cm;}}</style>' + style + '</head><body>' + pageContent + '</body></html>'
// console.log(page);
asBlob(page).then(data => {
saveAs(data, 'file.docx') // save as docx file
}); // asBlob() return Promise<Blob|Buffer>
});
return button;
});
// 增加菜单栏? 不显示按钮
// editor.ui.extendMenuBar({
// menu: {
// menuId: 'export',
// label: '导出',
// groups: [
// {
// groupId: 'export',
// items: [
// 'ExportToWord'
// ]
// }
// ]
// },
// position: 'after:help'
// }
// );
}
}
// 导出为PDF插件
class Export2PDF extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add('ExportToPDF', () => {
// The button will be an instance of ButtonView.
const button = new ButtonView();
button.set({
label: '导出为PDF',
// withText: true
tooltip: true,
// 图标 直接插入svg文件
icon: '<svg t="1730309942693" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2589" width="200" height="200"><path d="M576 832H224c-17.6 0-32-14.4-32-32V192h608c17.6 0 32 14.4 32 32v288c0 17.7 14.3 32 32 32s32-14.3 32-32V224c0-52.9-43.1-96-96-96H160c-17.7 0-32 14.3-32 32v640c0 52.9 43.1 96 96 96h352c17.7 0 32-14.3 32-32s-14.3-32-32-32z" p-id="2590"></path><path d="M960 734.4c0-0.3 0-0.5-0.1-0.7 0-0.3 0-0.5-0.1-0.8 0-0.3-0.1-0.6-0.1-0.9 0-0.2 0-0.4-0.1-0.6 0-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.4-0.1-0.5-0.1-0.3-0.1-0.7-0.2-1 0-0.2-0.1-0.3-0.1-0.5-0.1-0.3-0.2-0.6-0.3-1-0.1-0.2-0.1-0.4-0.2-0.6l-0.3-0.9c-0.1-0.2-0.1-0.4-0.2-0.6-0.1-0.3-0.2-0.5-0.3-0.8-0.1-0.2-0.2-0.5-0.3-0.7-0.1-0.2-0.2-0.4-0.3-0.7-0.1-0.3-0.2-0.5-0.4-0.8-0.1-0.2-0.2-0.4-0.3-0.5-0.1-0.3-0.3-0.6-0.4-0.9-0.1-0.2-0.2-0.3-0.3-0.4-0.2-0.3-0.3-0.6-0.5-0.9-0.1-0.1-0.2-0.3-0.2-0.4l-0.6-0.9c-0.1-0.1-0.2-0.2-0.3-0.4l-0.6-0.9c-0.1-0.1-0.2-0.3-0.3-0.4-0.2-0.3-0.4-0.6-0.7-0.8-0.1-0.2-0.3-0.3-0.4-0.5-0.2-0.2-0.4-0.5-0.6-0.7-0.2-0.2-0.4-0.5-0.7-0.7l-0.4-0.4-128-128c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l73.4 73.4H592c-17.7 0-32 14.3-32 32s14.3 32 32 32h258.7l-73.4 73.4c-12.5 12.5-12.5 32.8 0 45.3 6.2 6.2 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l128-128 0.4-0.4 0.7-0.7c0.2-0.2 0.4-0.5 0.6-0.7 0.1-0.2 0.3-0.3 0.4-0.5 0.2-0.3 0.4-0.6 0.7-0.8 0.1-0.1 0.2-0.3 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.2 0.3-0.4l0.6-0.9c0.1-0.1 0.2-0.3 0.2-0.4 0.2-0.3 0.4-0.6 0.5-0.9 0.1-0.1 0.2-0.3 0.3-0.4 0.2-0.3 0.3-0.6 0.4-0.9 0.1-0.2 0.2-0.4 0.3-0.5 0.1-0.3 0.2-0.5 0.4-0.8 0.1-0.2 0.2-0.4 0.3-0.7 0.1-0.2 0.2-0.5 0.3-0.7 0.1-0.3 0.2-0.5 0.3-0.8 0.1-0.2 0.1-0.4 0.2-0.6l0.3-0.9c0.1-0.2 0.1-0.4 0.2-0.6 0.1-0.3 0.2-0.6 0.3-1 0-0.2 0.1-0.3 0.1-0.5 0.1-0.3 0.2-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.5 0.1-0.3 0.1-0.7 0.2-1 0-0.2 0.1-0.4 0.1-0.6 0-0.3 0.1-0.6 0.1-0.9 0-0.3 0-0.5 0.1-0.8 0-0.2 0-0.5 0.1-0.7-0.1-1.5-0.1-2.5-0.1-3.6zM352 320c-17.7 0-32 14.3-32 32v384c0 17.7 14.3 32 32 32s32-14.3 32-32V576h192c70.6 0 128-57.4 128-128s-57.4-128-128-128H352z m288 128c0 35.3-28.7 64-64 64H384V384h192c35.3 0 64 28.7 64 64z" p-id="2591"></path></svg>'
});
// Execute a callback function when the button is clicked
button.on('execute', () => {
const pageContent = getPageContent();
console.log(pageContent);
const style = getStyle();
// 去掉element中的 ck-focused ck-weight_selected消除页面和图片的蓝边
const page = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>@media print{@page{size:A4 portrait;margin:0cm 3cm}@page:left{margin-left:2.5cm;margin-right:2.7cm;}@page:right{margin-left:2.7cm;margin-right:2.5cm;}}</style>' + style + '</head><body>' + pageContent.replaceAll('ck-focused', 'ck-blurred').replaceAll('ck-weight_selected', '') + '</body></html>'
const newWindow = window.open('', 'PrintDocument', 'height=600,width=700,top=50,left=50');
newWindow.document.write(page);
newWindow.document.close();
newWindow.print();
newWindow.onafterprint = function () {
newWindow.close();
}
});
return button;
});
}
}
// 智能润色插件
class Translation extends Plugin {
init() {
// console.log('Translation initialized!');
this.editor.ui.componentFactory.add('translate', (locale) => {
const dropdownView = createDropdown(locale);
dropdownView.buttonView.set({
label: '智能助手',
withText: true,
});
const items = new Collection();
items.add({
type: 'button',
model: {
id: 'summary',
withText: true,
label: '摘要',
}
});
items.add({
type: 'button',
model: {
id: 'decoration',
withText: true,
label: '润色'
}
});
items.add({
type: 'button',
model: {
id: 'extension',
withText: true,
label: '续写'
}
});
items.add({
type: 'button',
model: {
id: 'correction',
withText: true,
label: '修改'
}
});
items.add({
type: 'button',
model: {
id: 'translation',
withText: true,
label: '翻译'
}
});
addListToDropdown(dropdownView, items);
dropdownView.on('execute', (eventInfo) => {
const { id, label } = eventInfo.source;
// 获取选中的文本,用来进行后续操作
const selectionText = window.getSelection().toString();
if (id === 'summary') {
// this.editor.execute('ExportToWord');
console.log('Object (en):', label, selectionText);
}
});
return dropdownView;
});
}
}
// 侧边栏
class ToggleSideBar extends Plugin{
// constructor(toggleSidebar) {
// super();
// this.toggleSidebar = toggleSidebar;
// }
init() {
const editor = this.editor;
editor.ui.componentFactory.add('SideBar', () => {
// The button will be an instance of ButtonView.
const button = new ButtonView();
button.set({
label: '侧边栏',
// withText: true
tooltip: true,
// 图标 直接插入svg文件
icon: '<svg t="1730903613822" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2614" width="200" height="200"><path d="M849.92 981.333333H175.658667A132.736 132.736 0 0 1 42.666667 848.597333V175.36A132.736 132.736 0 0 1 175.658667 42.666667H849.92C921.898667 42.666667 981.333333 102.016 981.333333 175.402667v673.152C981.333333 922.026667 921.898667 981.333333 849.92 981.333333zM175.658667 136.362667a38.698667 38.698667 0 0 0-39.125334 39.04v673.152c0 21.888 17.194667 39.082667 39.125334 39.082666H849.92a38.698667 38.698667 0 0 0 39.125333-39.04V175.36c-1.578667-21.845333-18.773333-39.04-39.125333-39.04H175.658667z" fill="#172B4D" p-id="2615"></path><path d="M374.314667 957.866667a46.08 46.08 0 0 1-46.933334-46.805334V111.36a46.08 46.08 0 0 1 46.933334-46.890667c26.624 0 46.933333 20.309333 46.933333 46.890667v799.658667a46.08 46.08 0 0 1-46.933333 46.848z" fill="#172B4D" p-id="2616"></path></svg>'
});
// Execute a callback function when the button is clicked
button.on('execute', () => {
// 打开sidebar
const bt = document.getElementById("toggleSidebarButton");
console.log(bt);
bt.click();
});
return button;
});
}
}
export { Export2Word, Export2PDF, Translation, ToggleSideBar };

@ -0,0 +1,143 @@
// utils.js
// 获取用户配置
export function getUserConfigFromBackend() {
// TODO 请求用户配置
const options = {};
// 字体、字号、样式
// TODO
const {
fontFamilyOptions = [
'default',
'宋体',
'新宋体',
'仿宋',
'楷体',
'微软雅黑',
'黑体',
'华文仿宋',
'华文楷体',
'华文隶书',
'华文宋体',
'华文细黑',
'华文新魏',
'华文行楷',
'华文中宋',
'隶书',
'苹方 常规',
'幼圆',
'Times New Roman'
],
// 五号,小四,四号,小三,三号,小二,二号
fontSizeOptions = [14, 'default', 16,18.6,20, 21.3,24,29.3],
styleDefinitions = [
{
name: 'Article category',
element: 'h3',
classes: ['category']
},
{
name: 'Title',
element: 'h2',
classes: ['document-title']
},
{
name: 'Subtitle',
element: 'h3',
classes: ['document-subtitle']
},
{
name: 'Info box',
element: 'p',
classes: ['info-box']
},
{
name: 'Side quote',
element: 'blockquote',
classes: ['side-quote']
},
{
name: 'Marker',
element: 'span',
classes: ['marker']
},
{
name: 'Spoiler',
element: 'span',
classes: ['spoiler']
},
{
name: 'Code (dark)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-dark']
},
{
name: 'Code (bright)',
element: 'pre',
classes: ['fancy-code', 'fancy-code-bright']
},
{
name: 'GradientBorder',
element: 'p',
classes: ['gradientborder']
}
]
} = options;
// 如果传入的options没有对应项使用默认值
return {
fontFamily: {
options: fontFamilyOptions
},
fontSize: {
options: fontSizeOptions
},
style: {
definitions: styleDefinitions
}
};
}
// 实现自动保存saveData方法将编辑内容发送至后端
export function saveData(data) {
// return new Promise( resolve => {
// setTimeout( () => {
// console.log( 'Saved', data );
// resolve();
// }, HTTP_SERVER_LAG );
// } );
console.log(data);
}
// 将当前页面的样式转为内联
export function getStyle() {
let str = '<head><meta charset="utf-8"></meta>';
const styles = document.querySelectorAll('style');
for (let i = 0; i < styles.length; i++) {
str += styles[i].outerHTML;
}
str += "<style>.results{width:100%!important;} .result .title{width:100%;}</style>";
str += "<style>table{border-collapse: collapse;table-layout: fixed}</style>"
// str += "<style>table thead tr{ background-color: #f3f4f9;}</style>"
str += "<style>table td,th{ font-size: 14px;padding: 1px 1px;border-width: 1px;border-style: solid;border-color: #d0d0d0;word-break: keep-all;white-space: nowrap;}</style>"
return str;
}
// 获取用户编辑的内容 <html>
export function getPageContent() {
// const pageContent = document.querySelector("#app > div > div > div > div.editor-container__editor-wrapper > div > div > div.ck.ck-reset.ck-editor.ck-rounded-corners > div.ck.ck-editor__main > div");
const pageContent = document.querySelector("#app > div > div > div > div.editor-container__editor-wrapper > div > div > div");
return pageContent.innerHTML;
}
// 获取并应用用户定义的样式
export function getAndApplyUserStyles() {
// 模拟从后端获取用户定义的样式
const response = fetch('/api/user-styles');
const styles = response.json();
const styleElement = document.createElement('style');
styleElement.innerHTML = styles;
document.head.appendChild(styleElement);
return styles;
}

@ -1,6 +1,13 @@
import { createApp } from 'vue'
import './public/style.css';
import App from './App.vue'
import store from './store'
import router from './router'
import { CkeditorPlugin } from '@ckeditor/ckeditor5-vue';
createApp(App).use(router).use(store).mount('#app')
const app = createApp(App);
app.use(router);
app.use(store);
app.use(CkeditorPlugin);
app.mount('#app');

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,281 @@
@import url('https://fonts.googleapis.com/css2?family=Oswald&family=PT+Serif:ital,wght@0,400;0,700;1,400&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@media print {
body {
margin: 0 !important;
}
}
.main-container {
--ckeditor5-preview-height: 650px;
position: relative;
font-family: '宋体';
width: fit-content;
height: 100%;
margin-left: auto;
margin-right: auto;
}
.ck-content {
font-family: '宋体';
font-size: 16px;
line-height: 1.6;
word-break: break-word;
}
.editor-container__editor-wrapper {
display: flex;
width: fit-content;
}
.editor-container_document-editor {
border: 1px solid var(--ck-color-base-border);
}
.editor-container_document-editor .editor-container__toolbar {
display: flex;
position: relative;
box-shadow: 0 2px 3px hsla(0, 0%, 0%, 0.078);
}
.editor-container_document-editor .editor-container__toolbar>.ck.ck-toolbar {
flex-grow: 1;
width: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-top: 0;
border-left: 0;
border-right: 0;
}
.editor-container_document-editor .editor-container__menu-bar>.ck.ck-menu-bar {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
border-top: 0;
border-left: 0;
border-right: 0;
}
.editor-container_document-editor .editor-container__editor-wrapper {
max-height: var(--ckeditor5-preview-height);
min-height: var(--ckeditor5-preview-height);
overflow-y: scroll;
background: var(--ck-color-base-foreground);
}
.editor-container_document-editor .editor-container__editor {
margin-top: 28px;
margin-bottom: 28px;
height: 100%;
}
.editor-container_document-editor .editor-container__editor .ck.ck-editor__editable {
box-sizing: border-box;
min-width: calc(210mm + 2px);
max-width: calc(210mm + 2px);
min-height: 297mm;
height: fit-content;
padding: 20mm 12mm;
border: 1px hsl(0, 0%, 82.7%) solid;
background: hsl(0, 0%, 100%);
box-shadow: 0 2px 3px hsla(0, 0%, 0%, 0.078);
flex: 1 1 auto;
margin-left: 72px;
margin-right: 72px;
}
/*侧边栏出现时其他右移*/
.main-container.sidebar-open .editor-container__menu-bar,
.main-container.sidebar-open .editor-container__toolbar,
.main-container.sidebar-open .editor-container__editor-wrapper {
transition: margin-left 0.3s ease;
margin-left: 350px;
}
/*用户定义的style插件样式*/
.ck-content h3.category {
font-family: 'Oswald';
font-size: 20px;
font-weight: bold;
color: #555;
letter-spacing: 10px;
margin: 0;
padding: 0;
}
.ck-content h2.document-title {
font-family: 'Oswald';
font-size: 50px;
font-weight: bold;
margin: 0;
padding: 0;
border: 0;
}
.ck-content h3.document-subtitle {
font-family: 'Oswald';
font-size: 20px;
color: #555;
margin: 0 0 1em;
font-weight: bold;
padding: 0;
}
.ck-content p.info-box {
--background-size: 30px;
--background-color: #e91e63;
padding: 1.2em 2em;
border: 1px solid var(--background-color);
background: linear-gradient(135deg,
var(--background-color) 0%,
var(--background-color) var(--background-size),
transparent var(--background-size)),
linear-gradient(135deg,
transparent calc(100% - var(--background-size)),
var(--background-color) calc(100% - var(--background-size)),
var(--background-color));
border-radius: 10px;
margin: 1.5em 2em;
box-shadow: 5px 5px 0 #ffe6ef;
}
.ck-content blockquote.side-quote {
font-family: 'Oswald';
font-style: normal;
float: right;
width: 35%;
position: relative;
border: 0;
overflow: visible;
z-index: 1;
margin-left: 1em;
}
.ck-content blockquote.side-quote::before {
content: '“';
position: absolute;
top: -37px;
left: -10px;
display: block;
font-size: 200px;
color: #e7e7e7;
z-index: -1;
line-height: 1;
}
.ck-content blockquote.side-quote p {
font-size: 2em;
line-height: 1;
}
.ck-content blockquote.side-quote p:last-child:not(:first-child) {
font-size: 1.3em;
text-align: right;
color: #555;
}
.ck-content span.marker {
background: yellow;
}
.ck-content span.spoiler {
background: #000;
color: #000;
}
.ck-content span.spoiler:hover {
background: #000;
color: #fff;
}
.ck-content pre.fancy-code {
border: 0;
margin-left: 2em;
margin-right: 2em;
border-radius: 10px;
}
.ck-content pre.fancy-code::before {
content: '';
display: block;
height: 13px;
background: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NCAxMyI+CiAgPGNpcmNsZSBjeD0iNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGMzZCNUMiLz4KICA8Y2lyY2xlIGN4PSIyNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGOUJFNEQiLz4KICA8Y2lyY2xlIGN4PSI0Ny41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiM1NkM0NTMiLz4KPC9zdmc+Cg==);
margin-bottom: 8px;
background-repeat: no-repeat;
}
.ck-content pre.fancy-code-dark {
background: #272822;
color: #fff;
box-shadow: 5px 5px 0 #0000001f;
}
.ck-content pre.fancy-code-bright {
background: #dddfe0;
color: #000;
box-shadow: 5px 5px 0 #b3b3b3;
}
.ck-content p.gradientborder {
--borderWidth: 12px;
--bRadius: 5px;
width: 60%;
height: 60%;
position: relative;
z-index: 0;
overflow: hidden;
padding: 2rem;
z-index: 0;
border-radius: --bRadius;
&::after,
&::before {
box-sizing: border-box;
}
&::before {
content: '';
position: absolute;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
z-index: -2;
background-repeat: no-repeat;
background-size: 50% 50%, 50% 50%;
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);
animation: rotate 4s linear infinite;
@keyframes rotate {
100% {
transform: rotate(1turn);
}
}
}
&::after {
content: '';
position: absolute;
z-index: -1;
left: calc(var(--borderWidth) / 2);
top: calc(var(--borderWidth) / 2);
width: calc(100% - var(--borderWidth));
height: calc(100% - var(--borderWidth));
background: white;
border-radius: --bRadius;
/* 这一行是为了方便查看原来的样子的 */
animation: opacityChange 3s infinite alternate;
}
@keyframes opacityChange {
50% {
opacity: 1;
}
100% {
opacity: .5;
}
}
}

@ -1,11 +1,45 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import LoginView from '../views/LoginView'
import RegisterView from '../views/RegisterView'
import NotFoundView from '../views/NotFoundView'
import UserProfileView from '../views/UserProfileView'
import CkeditorView from '../views/CkeditorView'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
component: HomeView,
},
{
path: '/login/',
name: 'login',
component: LoginView,
},
{
path: '/register/',
name: 'register',
component: RegisterView,
},
{
path: '/userprofile/',
name:'userprofile',
component:UserProfileView,
},
{
path: '/404/',
name: '404',
component: NotFoundView
},
{
path: '/ckeditor/',
name: 'ckeditor',
component: CkeditorView,
},
{
path: '/:catchAll(.*)',
redirect: '/404/',
}
]

@ -1,20 +1,75 @@
// import $ from 'jquery';
// import { jwtDecode } from 'jwt-decode';
import $ from 'jquery';
const ModulerUser = {
state: {
id:'',
username:'',
photo:'',
access: "",
refresh: "",
is_login: false,
path:[],
filecontent:'',
},
getters: {
},
mutations: {
updateUser(state,user){
state.username = user.username,
state.access = user.access,
state.refresh = user.refresh,
state.is_login = user.is_login;
},
logout(state){
state.username = '',
state.access = "",
state.refresh = "",
state.is_login = false,
state.path = []
},
forwardPath(state,item){
state.path.push(item);
},
backPath(state){
state.path.pop();
},
updateFileContent(state,content){
state.filecontent = content;
},
},
actions: {
login(context,data){
$.ajax({
url:'http://47.106.113.194:8000/token/',
type:'POST',
data:{
username:data.username,
password:data.password,
},
success:resp => {
const {access,refresh} = resp;
$.ajax({
url:' http://47.106.113.194:8000/getinfo/',
type:'GET',
headers:{
'Authorization': "Bearer " + access,
},
success(resp){
context.commit('updateUser',{
...resp,
access:access,
refresh:refresh,
is_login: true,
});
context.commit('forwardPath',"~/users/" + resp.username + '/'
);
data.success();
}
});
},
error(){
data.error();
}
});
}
},
modules: {
}

@ -0,0 +1,803 @@
<template>
<div>
<div :class="['main-container', { 'sidebar-open': isSidebarOpen }]">
<div class="editor-container editor-container_document-editor editor-container_include-style"
ref="editorContainerElement">
<div class="editor-container__menu-bar" ref="editorMenuBarElement"></div>
<div class="editor-container__toolbar" ref="editorToolbarElement"></div>
<div class="editor-container__editor-wrapper">
<div class="editor-container__editor">
<div ref="editorElement">
<ckeditor v-if="isLayoutReady" v-model="config.initialData" :editor="editor"
:config="config" @ready="onReady" />
</div>
</div>
</div>
</div>
<el-button type="primary" @click="toggleSidebar()" style="display:none"
id="toggleSidebarButton">打开/关闭侧边栏</el-button>
<div class="sidebar" :class="{ 'active': isSidebarOpen }">
<!-- 侧边栏内容 -->
<el-menu class="sidebar-menu" :class="{ 'active': isNavbarOpen }">
<el-sub-menu index="1">
<template #title>智能助手</template>
<el-menu-item index="1-1" @click="showContent('polish')"></el-menu-item>
<el-menu-item index="1-2" @click="showContent('rewrite')"></el-menu-item>
<el-menu-item index="1-3" @click="showContent('summary')"></el-menu-item>
<el-menu-item index="1-4" @click="showContent('edit')"></el-menu-item>
<el-menu-item index="1-5" @click="showContent('translate')"></el-menu-item>
</el-sub-menu>
<el-sub-menu index="2">
<template #title>图文转换</template>
<el-menu-item index="2-1" @click="showContent('ocr')">OCR</el-menu-item>
<el-menu-item index="2-2" @click="showContent('mindmap')"></el-menu-item>
</el-sub-menu>
<el-sub-menu index="3">
<template #title>样式生成</template>
<el-menu-item index="3-1" @click="showContent('manual')"></el-menu-item>
<el-menu-item index="3-2" @click="showContent('ai')"></el-menu-item>
</el-sub-menu>
</el-menu>
<!-- Content Sections -->
<div v-if="currentContent" class="content-section">
<el-button icon="el-icon-menu" @click="toggleNavBar()"></el-button>
<!-- Dynamic content based on navigation selection -->
<div v-if="currentContent === 'polish'"></div>
<div v-if="currentContent === 'rewrite'"></div>
<div v-if="currentContent === 'summary'"></div>
<div v-if="currentContent === 'edit'"></div>
<div v-if="currentContent === 'translate'"></div>
<div v-if="currentContent === 'ocr'">OCR</div>
<div v-if="currentContent === 'mindmap'"></div>
<div v-if="currentContent === 'manual'" class="p-3">
<!-- 手动输入表单 -->
<el-form @submit.prevent="submitForm">
<el-col :span="30">
<el-form-item label="字体颜色:">
<el-col :span="10">
<el-select v-model="formData.color" @change="updatePreview">
<el-option label="黑色" value="black"
style="background-color: black;">黑色</el-option>
<el-option label="红色" value="red"
style="background-color: red;">红色</el-option>
<el-option label="蓝色" value="blue"
style="background-color: blue;">蓝色</el-option>
<el-option label="绿色" value="green"
style="background-color: green;">绿色</el-option>
<el-option label="黄色" value="yellow"
style="background-color: yellow;">黄色</el-option>
<el-option label="紫色" value="purple"
style="background-color: purple;">紫色</el-option>
<el-option label="橙色" value="orange"
style="background-color: orange">橙色</el-option>
</el-select>
</el-col>
<el-col :span="8">
<el-color-picker v-model="formData.color"
@change="updatePreview"></el-color-picker>
</el-col>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="字体大小:">
<el-row :gutter="10">
<el-col :span="12">
<el-select v-model="formData.fontSize" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="29.3px" style="font-size: 29.3px;">二号</el-option>
<el-option value="24px" style="font-size: 24px;">小二</el-option>
<el-option value="21.3px" style="font-size: 21.3px;">三号</el-option>
<el-option value="20px" style="font-size: 20px;">小三</el-option>
<el-option value="18.6px" style="font-size: 18.6px;">四号</el-option>
<el-option value="16px" style="font-size: 16px;">小四</el-option>
<el-option value="14px" style="font-size: 14px;">五号</el-option>
<el-option value="12px" style="font-size: 12px;">小五</el-option>
<el-option value="10px" style="font-size: 10px;">六号</el-option>
<el-option value="8.6px" style="font-size: 8.6px;">小六</el-option>
</el-select>
</el-col>
<el-col :span="12">
<el-input v-model="formData.fontSize" placeholder="例如: 16px"
@input="updatePreview"></el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
<!-- 字体选择 -->
<el-row :gutter="8">
<el-col :span="12">
<el-form-item label="字体:">
<el-select v-model="formData.fontFamily" @change="updatePreview">
<!--英文字体-->
<el-option value="">请选择</el-option>
<el-option value="Arial" style="font-family: Arial;">Arial</el-option>
<el-option value="Courier New" style="font-family: 'Courier New';">Courier
New</el-option>
<el-option value="Georgia" style="font-family: Georgia;">Georgia</el-option>
<el-option value="Helvetica"
style="font-family: Helvetica;">Helvetica</el-option>
<el-option value="Times New Roman"
style="font-family: 'Times New Roman';">Times
New Roman</el-option>
<el-option value="serif" style="font-family: serif;">serif</el-option>
<el-option value="sans-serif"
style="font-family: sans-serif;">sans-serif</el-option>
<el-option value="monospace"
style="font-family: monospace;">monospace</el-option>
<el-option value="cursive" style="font-family: cursive;">cursive</el-option>
<el-option value="fantasy" style="font-family: fantasy;">fantasy</el-option>
<!--中文字体字体-->
<el-option value="SimSun" style="font-family: SimSun;">宋体</el-option>
<el-option value="SimHei" style="font-family: SimHei;">黑体</el-option>
<el-option value="Microsoft YaHei"
style="font-family: 'Microsoft YaHei';">微软雅黑</el-option>
<el-option value="FangSong" style="font-family: FangSong;">仿宋</el-option>
<el-option value="KaiTi" style="font-family: KaiTi;">楷体</el-option>
<el-option value="LiSu" style="font-family: LiSu;">隶书</el-option>
<el-option value="YouYuan" style="font-family: YouYuan;">幼圆</el-option>
<el-option value="STSong" style="font-family: STSong;">华文宋体</el-option>
<el-option value="STHeiti" style="font-family: STHeiti;">华文黑体</el-option>
<el-option value="STKaiti" style="font-family: STKaiti;">华文楷体</el-option>
<el-option value="STFangsong"
style="font-family: STFangsong;">华文仿宋</el-option>
<el-option value="STXihei" style="font-family: STXihei;">华文细黑</el-option>
<el-option value="STCaiyun" style="font-family: STCaiyun;">华文彩云</el-option>
<el-option value="STHupo" style="font-family: STHupo;">华文琥珀</el-option>
<el-option value="STXinwei" style="font-family: STXinwei;">华文新魏</el-option>
<el-option value="STXingkai"
style="font-family: STXingkai;">华文行楷</el-option>
<el-option value="FZShuTi" style="font-family: FZShuTi;">方正舒体</el-option>
<el-option value="FZYaoti" style="font-family: FZYaoti;">方正姚体</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="字体样式:">
<el-select v-model="formData.fontStyle" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="normal" style="font-style: normal;">正常</el-option>
<el-option value="italic" style="font-style: italic;">斜体</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="字体粗细:">
<el-select v-model="formData.fontWeight" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="normal" style="font-weight: normal;">正常</el-option>
<el-option value="bold" style="font-weight: bold;">加粗</el-option>
<el-option value="bolder" style="font-weight: bolder;">更粗</el-option>
<el-option value="lighter" style="font-weight: lighter;">更细</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="文本转换:">
<el-select v-model="formData.textTransform" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="none"></el-option>
<el-option value="uppercase">大写</el-option>
<el-option value="lowercase">小写</el-option>
<el-option value="capitalize">首字母大写</el-option>
<el-option value="full-width">全角</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="文本装饰:">
<el-select v-model="formData.textDecoration" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="none"></el-option>
<el-option value="underline">下划线</el-option>
<el-option value="overline">上划线</el-option>
<el-option value="line-through">删除线</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="字体阴影:">
<el-select v-model="formData.textShadow" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="1px 1px 2px pink"
style="text-shadow: 1px 1px 2px pink;">阴影1</el-option>
<el-option value="#FC0 1px 0 10px"
style="text-shadow: #FC0 1px 0 10px;">阴影2</el-option>
<el-option value="1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue"
style="text-shadow: 1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue;">阴影3</el-option>
<el-option value="2px 2px #777"
style="text-shadow: 2px 2px #777;">阴影4</el-option>
<el-option value="-1px -1px #000,-2px -2px #333,1px 1px #fff,2px 2px #eee"
style="text-shadow: -1px -1px #000,-2px -2px #333,1px 1px #fff,2px 2px #eee;">阴影5</el-option>
<el-option
value="1px 1px #BAE6EF,2px 2px #BAE6EF,3px 3px #BAE6EF,4px 4px #BAE6EF,5px 5px #BAE6EF"
style="text-shadow: 1px 1px #BAE6EF,2px 2px #BAE6EF,3px 3px #BAE6EF,4px 4px #BAE6EF,5px 5px #BAE6EF;">阴影6</el-option>
<el-option value="5px 5px #666,7px 7px #eee"
style="text-shadow: 5px 5px #666,7px 7px #eee;">阴影7</el-option>
<el-option
value="0 0 5px #FFFF66,1px 1px 1px #fff,-1px -1px 1px #fff,0 0 10px #FFFF99,0 0 20px #B9EB50"
style="text-shadow: 0 0 5px #FFFF66,1px 1px 1px #fff,-1px -1px 1px #fff,0 0 10px #FFFF99,0 0 20px #B9EB50;">阴影8</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="文本对齐:">
<el-select v-model="formData.textAlign" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="left">左对齐</el-option>
<el-option value="center">居中</el-option>
<el-option value="right">右对齐</el-option>
<el-option value="justify">两端对齐</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="行高:">
<el-select v-model="formData.lineHeight" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="1.0">1.0</el-option>
<el-option value="1.5">1.5</el-option>
<el-option value="2.0">2.0</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="字母间距:">
<el-select v-model="formData.letterSpacing" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="0">0</el-option>
<el-option value="1px">1px</el-option>
<el-option value="2px">2px</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单词间距:">
<el-select v-model="formData.wordSpacing" @change="updatePreview">
<el-option value="">请选择</el-option>
<el-option value="0">0</el-option>
<el-option value="1px">1px</el-option>
<el-option value="2px">2px</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item><el-button type="success" native-type="submit"
class="w-100">提交</el-button></el-form-item>
</el-form>
<div class="preview mt-3 p-3 border rounded" :style="previewStyle">
Aa Bb Cc 上下 左右 12345 Aa Bb Cc 上下 左右 12345 Aa Bb Cc 上下 左右 12345
</div>
</div>
<div v-if="currentContent === 'ai'" class="p-3">
<!-- AI对话框 -->
<div class="chat-box border rounded p-3">
<p v-for="message in messages" :key="message.id" class="mb-1">{{ message.text }}</p>
<el-input v-model="aiInput" @keyup.enter="sendMessageToAI" placeholder="请输入消息"
class="w-100"></el-input>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style>
/*侧边栏样式*/
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 350px;
height: 100%;
overflow-y: hidden;
transition: transform 0.3s ease;
transform: translateX(-100%);
background-color: #f9f9f9;
padding-left: 8px;
/* 调整内边距,使文字右移 */
padding-right: 8px;
/* 调整内边距,使文字右移 */
z-index: 9;
}
.sidebar.active {
transform: translateX(0);
}
.sidebar-menu {
display: none;
flex-direction: column;
justify-content: space-between;
}
.sidebar-menu.active {
display: block;
}
.sidebar-menu .el-menu-item {
flex: 1;
text-align: left;
}
.sidebar-menu .el-menu-item span {
display: block;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
margin: 5px;
transition: background-color 0.3s;
}
.sidebar-menu .el-menu-item span:hover {
background-color: #e0e0e0;
}
.chat-box {
max-height: 350px;
overflow-y: auto;
}
.preview {
margin-top: 20px;
/* 调整预览区与表单之间的距离 */
border: 1px solid #ccc;
/* 添加边框 */
padding: 10px;
/* 添加内边距 */
border-radius: 4px;
/* 添加圆角 */
}
.form-item {
margin-bottom: 1px;
/* 减小表单项之间的距离 */
}
</style>
<script>
import {
DecoupledEditor,
// ClassicEditor,
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
Base64UploadAdapter,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Markdown,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromMarkdownExperimental,
PasteFromOffice,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo,
} from 'ckeditor5';
import translations from 'ckeditor5/translations/zh-cn.js';
import 'ckeditor5/ckeditor5.css';
import { ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu } from 'element-plus';
// import {getUserConfigFromBackend,saveData,getPageContent,getAndApplyUserStyles} from './components/utils';
import { getUserConfigFromBackend, saveData, getPageContent } from '../components/utils';
import { Export2PDF, Export2Word, ToggleSideBar, Translation } from '../components/plugins';
import { useStore } from 'vuex';
import router from '../router/index.js';
export default {
name: 'CkeditorView',
data() {
return {
isLayoutReady: false,
config: null, // CKEditor needs the DOM tree before calculating the configuration.
editor: DecoupledEditor,
// editor: ClassicEditor,
store:useStore(),
isSidebarOpen: false,//
isNavbarOpen: false,//
currentContent: '', //
formData: {
fontFamily: '',
color: '',
fontSize: '',
fontStyle: '',
fontWeight: '',
textTransform: '',
textDecoration: '',
textShadow: '',
textAlign: '',
lineHeight: '',
letterSpacing: '',
wordSpacing: ''
},
previewStyle: {},
aiInput: '', // AI
messages: [], //
};
},
mounted() {
//
const userConfig = getUserConfigFromBackend();
//
// getAndApplyUserStyles();
this.config = {
toolbar: {
items: [
'undo',
'redo',
'|',
'heading',
'style',
'|',
'fontSize',
'fontFamily',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'underline',
'|',
'link',
'insertImage',
'insertTable',
'highlight',
'codeBlock',
'blockquote',
'|',
'alignment',
'bulletedList',
'numberedList',
'outdent',
'indent',
'|', 'ExportToWord', 'ExportToPDF', 'translate', 'SideBar'
],
shouldNotGroupWhenFull: true
},
plugins: [
AccessibilityHelp,
Alignment,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
Base64UploadAdapter,
BlockQuote,
Bold,
Code,
CodeBlock,
Essentials,
FindAndReplace,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
GeneralHtmlSupport,
Heading,
Highlight,
HorizontalLine,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
Markdown,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromMarkdownExperimental,
PasteFromOffice,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Strikethrough,
Style,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Undo,
Export2Word, Translation, Export2PDF, ToggleSideBar
],
balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList'],
//
fontFamily: {
//
options: userConfig.fontFamily.options,
//
supportAllValues: true,
},
fontSize: {
//
options: userConfig.fontSize.options,
supportAllValues: true
},
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph'
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1'
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2'
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3'
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4'
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5'
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6'
}
]
},
htmlSupport: {
allow: [
{
name: /^.*$/,
styles: true,
attributes: true,
classes: true
}
]
},
image: {
toolbar: [
'toggleImageCaption',
'imageTextAlternative',
'|',
'imageStyle:inline',
'imageStyle:wrapText',
'imageStyle:breakText',
'|',
'resizeImage'
]
},
initialData:
'',
language: 'zh-cn',
link: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
decorators: {
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file'
}
}
}
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true
}
},
mention: {
feeds: [
{
marker: '@',
feed: [
/* See: https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html */
]
}
]
},
menuBar: {
isVisible: true,
removeItems: ['help'],
},
placeholder: 'Type or paste your content here!',
//
style: {
definitions: userConfig.style.definitions
},
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties']
},
autosave: {
waitingTime: 180000, // (in ms) 3minutes
save() {
// TODO save
return saveData(getPageContent());
}
},
translations: [translations]
};
this.isLayoutReady = true;
},
methods: {
onReady(editor) {
Array.from(this.$refs.editorToolbarElement.children).forEach(child => child.remove());
Array.from(this.$refs.editorMenuBarElement.children).forEach(child => child.remove());
this.$refs.editorToolbarElement.appendChild(editor.ui.view.toolbar.element);
this.$refs.editorMenuBarElement.appendChild(editor.ui.view.menuBarView.element);
//
const pageContent = this.store.state.user.filecontent;
// const pageContent = '<h2>Congratulations on setting up CKEditor 5! 🎉</h2>\n<p>\n You\'ve successfully created a CKEditor 5 project. This powerful text editor will enhance your application, enabling rich text editing\n capabilities that are customizable and easy to use.\n</p>\n<h3>What\'s next?</h3>\n<ol>\n <li>\n <strong>Integrate into your app</strong>: time to bring the editing into your application. Take the code you created and add to your\n application.\n </li>\n <li>\n <strong>Explore features:</strong> Experiment with different plugins and toolbar options to discover what works best for your needs.\n </li>\n <li>\n <strong>Customize your editor:</strong> Tailor the editor\'s configuration to match your application\'s style and requirements. Or even\n write your plugin!\n </li>\n</ol>\n<p>\n Keep experimenting, and don\'t hesitate to push the boundaries of what you can achieve with CKEditor 5. Your feedback is invaluable to us\n as we strive to improve and evolve. Happy editing!\n</p>\n<h3>Helpful resources</h3>\n<ul>\n <li>📝 <a href="https://orders.ckeditor.com/trial/premium-features">Trial sign up</a>,</li>\n <li>📕 <a href="https://ckeditor.com/docs/ckeditor5/latest/installation/index.html">Documentation</a>,</li>\n <li> <a href="https://github.com/ckeditor/ckeditor5">GitHub</a> (star us if you can!),</li>\n <li>🏠 <a href="https://ckeditor.com">CKEditor Homepage</a>,</li>\n <li>🧑💻 <a href="https://ckeditor.com/ckeditor-5/demo/">CKEditor 5 Demos</a>,</li>\n</ul>\n<h3>Need help?</h3>\n<p>\n See this text, but the editor is not starting up? Check the browser\'s console for clues and guidance. It may be related to an incorrect\n license key if you use premium features or another feature-related requirement. If you cannot make it work, file a GitHub issue, and we\n will help as soon as possible!\n</p>\n';
// pageContent
// TODO
editor.setData(pageContent);
},
// sidebar
// /
toggleSidebar() {
this.isSidebarOpen = !this.isSidebarOpen;
this.isNavbarOpen = this.isSidebarOpen;
},
toggleNavBar() {
this.isNavbarOpen = !this.isNavbarOpen;
},
//
showContent(formType) {
this.currentContent = formType;
this.isNavbarOpen=false;
},
//
submitForm() {
const selectedStyles = {};
for (const key in this.formData) {
if (this.formData[key]) {
selectedStyles[key] = this.formData[key];
}
}
const className = prompt("请输入类名:");
if (className) {
let cssClass = `.ck-content p.${className} {\n`;
for (const key in selectedStyles) {
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
cssClass += ` ${cssKey}: ${selectedStyles[key]};\n`;
}
cssClass += `}`;
console.log(cssClass); // cssClass
alert(cssClass);
} else {
alert("类名不能为空!");
}
},
//
updatePreview() {
this.previewStyle = {};
for (const key in this.formData) {
if (this.formData[key]) {
this.previewStyle[key] = this.formData[key];
}
}
},
// API
sendMessageToAI() {
// APIAI
this.messages.push({ text: this.aiInput });
this.aiInput = ''; //
// AI
setTimeout(() => {
this.messages.push({ text: '这是来自AI的回复' });
}, 1000);
},
},
components: {
//
ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu
},
created(){
if(!this.store.state.user.is_login){
router.push({name:'login'});
}
}
};
</script>

@ -1,49 +1,167 @@
<template>
<ContentShow @handledoubleclick="handledoubleclick" :items="items">
</ContentShow>
<PathBar @ls="ls" />
<ContentBase @click="leftclick" @contextmenu.prevent="rightclick($event)">
<div v-for="item in items" :key="item.name" class="file-item">
<FolderFiled @select_item='select_item' @filerightclick="filerightclick" @fileleftclick="fileleftclick" @ls="ls" v-if="item.type==='folder'" :item="item" />
<FileFiled @select_item='select_item' @filerightclick="filerightclick" @fileleftclick="fileleftclick" @open_file='open_file' v-if="item.type==='file'" :item="item" />
</div>
<RightMenu v-if="menuvisible" @open_file="open_file" @turn_back="turn_back" :menutype="menutype" :menuposition="menuposition" />
</ContentBase>
</template>
<script>
import $ from 'jquery';
import ContentShow from '@/components/ContentShow';
import ContentBase from '../components/ContentBase';
import FileFiled from '../components/FileFiled';
import FolderFiled from '../components/FolderFiled';
import RightMenu from '../components/RightMenu.vue';
import PathBar from '../components/PathBar.vue';
import { ref } from 'vue';
import { useStore } from 'vuex';
import router from '@/router/index.js';
export default {
name: 'HomeView',
components:{
ContentShow,
ContentBase,
FileFiled,
FolderFiled,
RightMenu,
PathBar,
},
setup(){
let store = useStore();
let items = ref([]);
let path = ['~/'];
let menuvisible = ref(false);
let menutype = ref('filemenu');
let menuposition = ref({
top:"",
left:"",
});
let selected_item = null;
const ls = () => {
menuvisible.value = false;
$.ajax({
url:'https://app7179.acapp.acwing.com.cn/filesystem/ls/',
url:' http://47.106.113.194:8000/operation/command/ls/',
type:'get',
data:{
'path':path.join(""),
'path':store.state.user.path.join(""),
},
success:resp => {
items.value = resp.items;
let tmp = [];
for (let i = 0;i < resp.items.length;i++) {
let item = resp.items[i];
item.is_selected = false;
tmp.push(item);
}
items.value = tmp;
}
});
}
ls();
const handledoubleclick = (name,type) => {
if (type === "folder"){
path.push(name + "/");
const select_item = (item) => {
let tmp = items.value;
for (let i = 0;i < tmp.length;i++){
if (tmp[i] === item){
tmp[i].is_selected = true;
}
else {
tmp[i].is_selected = false;
}
}
items.value = tmp;
selected_item = item;
}
const open_file = () => {
menuvisible.value = false;
menutype.value = "filemenu";
if (!selected_item){
return;
}
if (selected_item.type === 'folder'){
store.commit('forwardPath',selected_item.name + '/'
);
ls();
}
else {
console.log(name);
$.ajax({
url:' http://47.106.113.194:8000/operation/open/',
type:'get',
data:{
'path':store.state.user.path.join("") + selected_item.name,
},
success:resp => {
store.commit('updateFileContent',resp.content);
console.log(resp);
router.push({name:'ckeditor'});
}
});
}
}
// const add_file = () => {
// }
const turn_back = () => {
console.log('turn_back',store.state.user.path.join(""));
store.commit('backPath');
ls();
}
const leftclick = () => {
menuvisible.value = false;
select_item(null);
}
const rightclick = (event) => {
menuvisible.value = true;
menutype.value = "contextmenu";
menuposition.value = {
top: event.clientY + 'px',
left: event.clientX + 'px',
};
select_item(null);
}
const fileleftclick = (item) => {
menuvisible.value = false;
select_item(item);
}
const filerightclick = (data) => {
menutype.value = "filemenu";
menuposition.value = data.menuposition;
menuvisible.value = true;
select_item(data.item);
}
if (store.state.user.is_login){
ls();
}
else {
router.push({name:'login'});
}
return {
items,
selected_item,
ls,
handledoubleclick,
select_item,
fileleftclick,
filerightclick,
leftclick,
rightclick,
open_file,
turn_back,
menuvisible,
menuposition,
menutype,
}
},
}
@ -52,3 +170,36 @@ export default {
<style scoped>
</style>
<style scoped>
.file-item {
width: 8vw;
height: 8vw;
overflow: hidden;
display: inline-block;
margin: 0 0.1vw 1vw 0.1vw;
border: 0.1vh solid white;
}
.file-item-icon > img {
width: 6vw;
height: 6vw;
}
.file-item-title{
width: 6vw;
height: 4vw;
text-align: center;
overflow: hidden;
font-size: 1.2vw;
word-break: break-all;
}
.file-item-selected {
border: 0.1vh solid #99d1ff;
background: #cce8ff;
}
</style>

@ -0,0 +1,73 @@
<template>
<ContentBase>
<div class="row justify-content-md-center">
<div class="col-3">
<form @submit.prevent="login">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input v-model="username" type="text" class="form-control" id="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input v-model="password" type="password" class="form-control" id="password">
</div>
<div class="error-message">{{ error_message }}</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from '../components/ContentBase';
import { ref } from 'vue';
import { useStore } from 'vuex';
import router from '@/router/index.js';
export default {
name: 'LoginView',
components: {
ContentBase,
},
setup(){
let store = useStore();
let username = ref('123');
let password = ref('123');
let error_message = ref('');
const login = () => {
// http://47.106.113.194:8000/token/
store.dispatch('login',{
username:username.value,
password:password.value,
success(){
error_message.value = '';
router.push({name:'home'});
},
error(){
error_message.value = "用户名或密码错误";
}
});
};
return {
username,
password,
error_message,
login,
}
}
}
</script>
<style scoped>
button {
width: 100%;
}
.error-message {
color: red;
}
</style>

@ -0,0 +1,19 @@
<template>
<ContentBase>
404
</ContentBase>
</template>
<script>
import ContentBase from '../components/ContentBase';
export default {
name: 'NotFoundView',
components: {
ContentBase,
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,96 @@
<template>
<ContentBase>
<div class="row justify-content-md-center">
<div class="col-3">
<form @submit.prevent="register">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input v-model="username" type="text" class="form-control" id="username">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input v-model="password" type="password" class="form-control" id="password">
</div>
<div class="mb-3">
<label for="password" class="form-label">确认密码</label>
<input v-model="password_confirm" type="password" class="form-control" id="password_confirm">
</div>
<div class="error-message">{{ error_message }}</div>
<button type="submit" class="btn btn-primary">注册</button>
</form>
</div>
</div>
</ContentBase>
</template>
<script>
import ContentBase from '../components/ContentBase';
import { ref } from 'vue';
import { useStore } from 'vuex';
import $ from 'jquery';
import router from '@/router/index.js';
export default {
name: 'RegisterView',
components: {
ContentBase,
},
setup(){
let store = useStore();
let username = ref('');
let password = ref('');
let password_confirm = ref('');
let error_message = '';
const register = () => {
$.ajax({
url:'http://47.106.113.194:8000/register/',
type:'post',
data:{
username:username.value,
password:password.value,
password_confirm:password_confirm.value,
},
success(resp){
console.log(resp);
if (resp.result === 'success'){
store.dispatch('login',{
username:username.value,
password:password.value,
success(){
error_message = '';
router.push({name:'home'});
},
error(){
error_message = "系统异常";
}
});
}
else {
error_message = resp.result;
}
}
});
};
return {
username,
password,
password_confirm,
error_message,
register,
}
}
}
</script>
<style scoped>
button {
width: 100%;
}
.error-message {
color: red;
}
</style>

@ -0,0 +1,33 @@
<template>
<ContentBase>
<div>
{{ $store.state.user.username }}
</div>
</ContentBase>
</template>
<script>
import ContentBase from '../components/ContentBase';
import { useStore } from 'vuex';
import router from '../router/index.js';
export default {
name: 'UserProfileView',
components: {
ContentBase,
},
setup(){
let store = useStore();
if (!store.state.user.is_login){
router.push({name:'login'});
}
return {
store,
}
}
}
</script>
<style scoped>
</style>

@ -1,4 +1,21 @@
const { defineConfig } = require('@vue/cli-service')
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
module.exports = defineConfig({
transpileDependencies: true
transpileDependencies: true,
configureWebpack: {
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()]
}),
Components({
resolvers: [ElementPlusResolver()]
}),
]
}
})

Loading…
Cancel
Save