目录
1 前言
2 用到的插件
2.1 截取图像 cropper
2.2 富文本编辑器 tinymce
3 项目结构
4 config.js
5 主页
5.1 iframe
5.2 页面的宽高
5.3 修改文章
6 个人中心-基本资料
7 个人中心-更换头像
8 个人中心-更换密码
9 文章管理-文章分类
10 文章管理-文章列表
11 文章管理-发布文章
12 文章列表-编辑文章
1 前言
前端视频地址 【前端第四阶段】前后端交互-大事件后台管理系统项目(第三部分)_哔哩哔哩_bilibili
前端代码地址
链接:百度网盘 请输入提取码 提取码:q0pq
视频中用layui快速写静态页面,我没有使用layui,部分功能使用了其他框架
在写的时候使用了插件 live server,这样能在修改后就得到反馈
在调试的时候使用了插件 express,后续要上传uploads,如果使用live server,每一次上传成功后都会刷新页面,这样就得不到我们想要的效果了
live server与express的用法可以看一下这个 附录5-vscode常用配置_Suyuoa的博客-CSDN博客
写前端的时候如果不用框架,工作量是十分巨大的,而且有时会出一些问题(且在样式上不会好看),比如传递base64字符串的时候,使用xhr的默认配置发送就不行,使用jQuery就可以直接发送
在iframe中,可以通过window.parent.主页面方法 来调用主页面的方法
base64字符串直接放在img的src中就能用,base64在前端中可以完全替代路径索引图片,好处是不需要进行额外的请求了,坏处是html文件会变大,经过Base64 编码后的文件体积一般比源文件大 30% 左右。
在这个网址上可以手动将 图像 转为base64 格式 图片在线转换Base64,图片编码base64
img src可以直接显示出存储的blob文件,在编辑文章的时候会有提到
请求的时候要不就一直用xhr请求,要不就一直用jQuery请求,如果既出现了xhr请求,又出现了jQuery请求,有时会有冲突
有的时候我们需要让JS执行完一些指令,再执行一些指令,试了好多个方法,总的来讲还是搞一个定时器最好用,但像我项目中的定时器方法只是一种取巧的方法,定时器给的时间越长,我们的程序看起来就越稳定,速度也越慢,如果要搞一个鲁棒性高的程序,还是要搞很多个回调函数,使用到很多的flag,那样就代表这要有更多的代码,开发与维护更加费时
在iframe页面中使用location.href改变的是iframe的src,总的来说iframe页面中使用的location是他自己,而不是iframe的父级页面
2 用到的插件
2.1 截取图像 cropper
cropper可以截取图像上的一部分,并预览
我们可以将预览内容转换为 base64或blob
可以用node搞下来 cropper - npm
也可以在github上搞下来 GitHub - fengyuanchen/cropperjs: JavaScript image cropper.
我是从github上搞下来的,解压后拿 dist/cropper.min.js dist/cropper.min.css
使用的时候需要看文档,当前的使用方式与视频已经不一致了
我简单做个demo
<!DOCTYPE html>
<html lang="en">
<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">
<title>Document</title>
<link rel="stylesheet" href="./cropperjs-main/dist/cropper.min.css">
<style>
.middle_img {
position: absolute;
left: 500px;
top: 150px;
display: inline-block;
}
.middle_img .w100 {
width: 100px;
height: 100px;
border-radius: 50%;
overflow: hidden;
}
.middle_img p {
margin-top: 10px;
text-indent: 20px;
}
.big_img {
position: absolute;
width: 400px;
height: 400px;
}
.big_img img {
display: block;
max-width: 100%;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="big_img">
<img src="./AJAX.jpg">
</div>
<div class="middle_img">
<div class="img-preview w100"></div>
<p class="size">100 x 100</p>
</div>
</body>
<script src="./cropperjs-main/dist/cropper.min.js"></script>
<script>
const image = document.querySelector('.big_img img');
const cropper = new Cropper(image, {
// aspectRatio: 16 / 9,
aspectRatio: 1 / 1,
// crop(event) {
// console.log(event.detail.x);
// console.log(event.detail.y);
// console.log(event.detail.width);
// console.log(event.detail.height);
// console.log(event.detail.rotate);
// console.log(event.detail.scaleX);
// console.log(event.detail.scaleY);
// },
preview: '.img-preview'
});
</script>
</html>
2.2 富文本编辑器 tinymce
富文本编辑器可以使用用tinymce,中文文档地址 TinyMCE中文文档中文手册 在这个文档中有下载地址
我简单做个demo
<!DOCTYPE html>
<html lang="en">
<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">
<title>Document</title>
</head>
<body>
<textarea name="" id="tinydemo2" cols="30" rows="10"></textarea>
</body>
<script src="./tinymce/js/tinymce/tinymce.min.js"></script>
<script>
tinymce.init({
selector: '#tinydemo2',
//skin:'oxide-dark',
language: 'zh_CN',
plugins: 'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount imagetools textpattern help emoticons autosave bdmap indent2em autoresize formatpainter axupimgs',
toolbar: 'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight formatpainter axupimgs',
height: 650, //编辑器高度
min_height: 400,
/*content_css: [ //可设置编辑区内容展示的css,谨慎使用
'/static/reset.css',
'/static/ax.css',
'/static/css.css',
],*/
fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',
font_formats: '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;',
link_list: [
{ title: '预置链接1', value: 'http://www.tinymce.com' },
{ title: '预置链接2', value: 'http://tinymce.ax-z.cn' }
],
image_list: [
{ title: '预置图片1', value: 'https://www.tiny.cloud/images/glyph-tinymce@2x.png' },
{ title: '预置图片2', value: 'https://www.baidu.com/img/bd_logo1.png' }
],
image_class_list: [
{ title: 'None', value: '' },
{ title: 'Some class', value: 'class-name' }
],
importcss_append: true,
//自定义文件选择器的回调内容
file_picker_callback: function (callback, value, meta) {
if (meta.filetype === 'file') {
callback('https://www.baidu.com/img/bd_logo1.png', { text: 'My text' });
}
if (meta.filetype === 'image') {
callback('https://www.baidu.com/img/bd_logo1.png', { alt: 'My alt text' });
}
if (meta.filetype === 'media') {
callback('movie.mp4', { source2: 'alt.ogg', poster: 'https://www.baidu.com/img/bd_logo1.png' });
}
},
toolbar_sticky: true,
autosave_ask_before_unload: false,
});
</script>
</html>
获取与设置内容的时候不能再使用textarea.value,而是要使用他内置的方法
- 获取 tinymce.activeEditor.getContent()
- 设置 tinymce.activeEditor.setContent(),括号内为要设置的内容
3 项目结构
入口为index.html或login.html,文件夹index_iframe存放的都是主页的子页面
4 config.js
在js文件夹中,有一个config.js作为配置文件,在配置文件中,做了请求的一些信息
比如请求根路径,统一请求根路径还是很有必要的,如果后端的端口换了,如果你不统一,你就得一个一个去改
还有就是身份校验,先从浏览器的locatStorage中拿token,去给后端校验,如果能校验成功就向后端请求用户信息,如果不成功就删除token,然后返回登陆页面
// 请求根路径
request_root = 'http://127.0.0.1'
// 身份校验
function wrong_login() {
localStorage.removeItem('token')
if (!(/\/login/.test(location.href))) {
location.replace('login.html')
}
}
function make_sure_token(callback) {
token = localStorage.getItem('token')
if (token) {
xhr = new XMLHttpRequest()
xhr.open('GET', request_root + '/my/userinfo')
xhr.setRequestHeader('Authorization', token)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
const response = JSON.parse(xhr.responseText)
const status = response.status
if (status == 0) {
userdata = response.data
callback()
}
else {
wrong_login()
}
}
}
xhr.onerror = function() {
wrong_login()
}
}
else {
wrong_login()
}
}
登陆页比较简单就不赘述了
5 主页
5.1 iframe
点击左侧不同的导航,在右面会出现不同的页面,主页默认的iframe是用echarts做的图表,在这里有介绍 附录3-大事件项目涉及到的图表_Suyuoa的博客-CSDN博客
我们只需要改变iframe的src属性就可以了,默认状态是首页的html
这里的sandbox目的只是为了让iframe的alert()能弹出来,在项目上实际并没有用到
5.2 页面的宽高
左侧导航栏的宽是固定的,高随着页面的变化而变化,顶部的高度是固定的60px,这里多减1px在视觉上差不多,会避免很多问题
内容部分也是一样
使用JS来动态影响组件宽高的时候,要在body中加入overflow:hidden,要不然又可能进度条会对效果产生影响,使用overflow:hidden就没有进度条了
5.3 修改文章
修改文章并没有在左侧的导航栏内出现,他的入口是文章列表中的编辑
文章列表本身是一个iframe,从文章列表发一个信号给父网页index.html
然后在index.html中接收,拿到接收信息后改变iframe的src,然后再发一个信号给 编辑文章的iframe
在编辑文章的网页中再进行接收,从而渲染到页面上
6 个人中心-基本资料
这里使用了一个网上的关于邮箱验证的字符串,测了测还可以,如果在别的项目中没有具体要求,我们就可以直接用这个正则
- !(/^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(.[a-zA-Z0-9_-]+)+$/.test(email_input.value))
修改完毕后reload()自己
在index.js中注册了一个时间,当iframe刷新的时候,重新获取头像与昵称,然后进行渲染
7 个人中心-更换头像
我们是点击上传按钮更换图像,实际上点击按钮的同时相当于点击了这个隐藏的input,通过这个input选择的文件
检测input的改变时间,通过事件对象拿到选择的文件,这个时候会有直接点×的情况,那么文件列表长度就是0,就算了,如果确实选择了文件,那么通过将文件转换为flie_url,然后用cropper的方法替换掉原来的
当点击确认更换头像后,通过cropper的getCroppedCanvas()方法,将被选中的区域变为指定大小的canvas,然后再用canvas内置方法toDataURL将canvas转变为base64字符串
在发送base64字符串的时候,用之前XHR方法直接发会很麻烦,这里建议使用$.ajax
8 个人中心-更换密码
这里是通过大量的空白占位符搓出来,如果事件不紧的话还是建议用独立的类名,然后text-indent
9 文章管理-文章分类
这里的添加类别是做了个样式
这个搞的并不是很好看,其实他不应该属于这一层,如果不用框架工作量有点儿大,我就简单搞了搞
这个原生JS拖动窗口可以看一下
10 文章管理-文章列表
搞了四个全局变量,通过传入不同的变量,来控制不同的结果
每次刷新的时候,先更新目录,再更新文章
因为后台给的只是分类id,而不是分类名称,我们需要动态去索引
当页数过多的时候,这里会无限往后排,中间...这种功能我没做
编辑文章在主页部分介绍过了
11 文章管理-发布文章
在这里用了一个中文包
处理blob()图像只能用这个方法,这个也是官方给的方法,canvas.toBlob()参数为一个函数,函数中有一个形参就是blob
发formdata的时候直接发就行了,拿tinymce的内容使用tinymce.activeEditor.getContent()
12 文章列表-编辑文章
编辑文章相对于发布文章,多了一个把信息渲染到页面上的功能
点的哪个编辑按钮,就会得到信息,tinymce用tinymce.activeEditor.setContent()加载信息
blob直接放在img是src中就可以了,数据库中存的信息是这样的
存的本地路径在这
所以我们不用进行处理,直接使用就可以了