组件(库)的概念 包含 控件(UI组件)、插件(基物上的组件)
jQuery
位置与尺寸
$(window).scrollTop() //鼠标滚动高度
$(window).height() //窗口高度
$(window).width() //窗口宽度
$(“#”).outerHeight() //元素高度
$(“#”).outerWidth() //元素宽度
$(“#”).offset().top //元素相对于文档的位置
$("#").offset().left //元素相对于文档的位置
$(“#”).position().top //元素相对于父元素的位置
$("#").position().left //元素相对于父元素的位置
$("").css("left") //元素相对于参考元素的位置
$("").css("top") //元素相对于参考元素的位置
事件
$(document).ready();
$(window).resize();
$(window).scroll(); // window 上的事件不好追踪,可以把滚动事件绑在DOM上
jQuery源码
jQuery源码的基本结构:
定义了jQuery函数,并使它成为全局变量 window.jQuery = window.$ = jQuery;
jQuery函数定义:
var jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
}
jQuery函数的原型:
jQuery.prototype = {
........................
length: 0,
sort: [].sort,
splice: [].splice
有了这几个属性,它就是一个类数组对象
}
jQuery初始化函数:
jQuery.fn.init = function(selector, context){
查找元素
return this;
}
jQuery函数有多个成员(JS里函数可以有成员,成员与函数体无关)其中:
jQuery.fn.init.prototype = jQuery.fn =jQuery.prototype;
jQuery.fn.extend=jQuery.extend;
几个使用过程的原理:
1、通过选择器获得jQuery对象 $("#id")
即调用了jQuery函数,返回jQuery.fn.init构造的对象,这个对象以jQuery.fn.init.prototype为原型,即以jQuery.fn 、jQuery.prototype为原型,因此jQuery对象也是一个类数组对象,并且有jQuery.fn的所有成员,数组的元素为DOM对象。
2、扩展jQuery(插件开发)
jQuery.fn.extend=jQuery.extend = function(object){把object的成员扩展到this}
$.fn.extend(object);方法体里的this指向jQuery.fn即jQuery.prototype,因此扩展到jQuery原型。
$.extend(object);方法体里的this指向$,因此扩展到jQuery函数。
jQuery使用
1、jQuery对象的结构:
[DOM对象]
{
length:DOM对象的长度
context:document对象(也是个DOM对象)
selector:选择器的字符串
__proto__:各种继承而来的jQuery方法
}
2、页面加载完成事件
$(function)等效于$(document).ready(function)或 $(window).on("load",function)
3、$也可以把非DOM数组打包成jQuery对象,从而可以使用jQuery对象的那些操作数组的函数。
4、jquery插件式的UI组件
UI 组件的对外接口应该包括:构建方法、事件、实例方法、属性值
(function($) {
// 给jquery对象添加一个成员,传入参数
// 这个插件方法 作为 UI 组件的构建方,使用:$('#id').plugin(options);
$.fn.plugin = function(options) {
// each 对jquery对象里的DOM对象一一处理,后,返回当前jquery对象,使得能被链式调用
return this.each(function() {
// 可以把一些数据绑到DOM对象上
this.pluginId = 1;
var $this = $(this);
// 插件逻辑
/**************************************************/
// 某种情况下触发事件,使用:$('#id').on('eventName',function(event,data){});
$this.trigger('eventName','data');
});
}
// 实例方法 和 属性值 也通过插件方法实现
})(jQuery);
//匿名立即执行函数,闭包
选择器
先后流行的选择器引擎有:getElementBySelector、cssQuery
jquery采用的选择器sizzle 的工作步骤: 1、切割选择符(便于后面,先通过选择符的后段查找,再以选择符前段过滤)2、查找(通过四大原生查找函数,最差的情况就是getElementByTagName(*)),过滤,去重
最新的浏览器自带原生选择器API:querySelecter、querySelecterAll
静态展示网站
考虑到SEO,不适合使用MVVM框架,但要考虑国际化问题
动态效果,用jQuery 和 CSS 实现
共用的页头页脚用 $(".header").load("header.html");
导入
可以在项目里嵌套一个MVVM框架用于开发表单和复杂交互的页面,配置 publicPath / base
富文本 wangEditor
GitHub - wangeditor-team/wangEditor: wangEditor —— 开源 Web 富文本编辑器
输出:html 片段
媒体文件:在 html 片段中插入 base64,或者 后端提供上传接口,html 片段中插入 上传后的url
wangEditor + Vue3
1、安装依赖
yarn add @wangeditor/editor
yarn add @wangeditor/editor-for-vue@next
2、使用
<template>
<div style="border: 1px solid #ccc">
<Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef"
:defaultConfig="toolbarConfig" mode="default" ref="toolbar"/>
<Editor style="height: 200px;" v-model="form.content"
:defaultConfig="editorConfig" mode="default" @onCreated="handleCreated"/>
</div>
</template>
<script>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import {Editor, Toolbar} from '@wangeditor/editor-for-vue'
import {DomEditor} from '@wangeditor/editor'
import {onBeforeUnmount, onMounted, ref, shallowRef} from 'vue'
export default {
name: "demo",
components: {Editor, Toolbar},
data() {
return {
form: {
content: ''
},
toolbarConfig: {
excludeKeys: ['fullScreen'] // 排除 全屏 按钮
},
editorConfig: {
placeholder: '请输入内容,字数2000字内',
maxLength: 2000,
autoFocus: false,
MENU_CONF: {
uploadImage: {
base64LimitSize: 10 * 1024, // 单位为byte,小于10kb则用base64
server: '/api/upload' // 后端提供的上传图片接口
},
uploadVideo: {
server: '/api/upload' // 后端提供的上传视频接口
}
}
}
};
},
setup() {
const editorRef = shallowRef();
onBeforeUnmount(() => {
if (editorRef.value) {
editorRef.value.destroy();
}
});
return {
editorRef,
handleCreated: (editor) => {
editorRef.value = editor;
}
};
},
methods: {
submit() {
// console.log(DomEditor.getToolbar(this.$refs.toolbar.editor)); // 查看toolbar全部配置
}
}
};
</script>
HTML内容导出为PDF
1、安装依赖
npm install html2canvas --save
npm install jspdf --save
2、下载 与 分页
<div id="pdfDom">
<div class="report_page" v-for="(page,index) in pages" :key="index">
<div :ref="'page_'+index)">
<table>
<tr>
</tr>
<tr v-for="(item,index) in page" :key="index">
</tr>
</table>
</div>
<div class="page_num">第 {{index+1}} 页</div>
</div>
</div>
.report_page {
box-sizing: border-box;
height: 1705px;
position: relative;
padding: 40px;
}
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
downloadPdf (title, selector) {
html2Canvas(document.querySelector(selector), {
allowTaint: true,
taintTest: false,
scale: '2',
dpi: '192',
background: '#fff'
}).then(function (canvas) {
let contentWidth = canvas.width;
let contentHeight = canvas.height;
let pageHeight = contentWidth / 592.28 * 841.89;
let leftHeight = contentHeight;
let position = 0;
let imgWidth = 595.28;
let imgHeight = 592.28 / contentWidth * contentHeight;
let pageData = canvas.toDataURL('image/jpeg', 1.0);
let PDF = new JsPDF('', 'pt', 'a4');
if (leftHeight < pageHeight) {
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
leftHeight -= pageHeight;
position -= 841.89;
if (leftHeight > 0) {
PDF.addPage()
}
}
}
PDF.save(title + '.pdf')
}
)
},
makePage(){ // 分页,将要显示的 items 加工成 二维数组 pages
if(this.pages.length === 0){ // 初始时 将所有 items 放到第一页
this.pages = [[].concat(this.items)];
this.$nextTick(()=>{
this.makePage();
});
}
// 倒数第二页太长则将倒数第二页的最后一个item推到倒数第一页
else if(this.pages.length-2 >=0 && this.$refs['page_'+(this.pages.length-2)].offsetHeight > 1625){
let lastItem = this.pages[this.pages.length-2].pop();
this.pages[this.pages.length-1].unshift(lastItem);
this.pages = [].concat(this.pages);
this.$nextTick(()=>{
this.makePage();
});
}
// 倒数第一页太长则新增一页,将最后一个item放到新增页
else if(this.$refs['page_'+(this.pages.length-1)].offsetHeight > 1625){
let lastItem = this.pages[this.pages.length-1].pop();
this.pages.push([lastItem]);
this.pages = [].concat(this.pages);
this.$nextTick(()=>{
this.makePage();
});
}
},
3、使用
// 将一部分 dom 导出为 pdf
this.downloadPdf("报告","#pdfDom");
HTML内容打印
1、生成PDF内容,参考上文
2、借用 iframe
<iframe ref="printIframe" style="display: none;" @load="iframeLoad" :src="pdfUrl"></iframe>
3、生成 iframe 用的 url
// PDF.output("datauristring") 得到的 DataURL 太长的话,iframe 不能正常加载;ObjectURL则无限制
this.report.pdfUrl = URL.createObjectURL(PDF.output('blob'));
4、使用
this.$refs.printIframe.contentWindow.print(); // 需保证 iframe 已经加载完成
另外,可以用 window.print() 来打印,但是无法控制布局 和 分页
let subWindow = window.open();
subWindow.document.body.innerHTML=window.document.getElementById("pdfDom").innerHTML;
subWindow.print();
subWindow.close();
浏览PDF
方式一、window.open(pdfUrl),浏览器默认方式
方式二、PDF.js,可以做一些定制
1、下载 PDF.js Prebuilt 包,放到项目中,作为单独的网页应用
2、打开 pdf 浏览器,传入pdfUrl,可以是 createObjectURL 产物
window.open('/pdfjs/web/viewer.html?file=' + pdfUrl);
Excel
SheetJS 导出 Excel
限制:设置单元格样式 需要交费购买SheetJS专业版
1、安装依赖 SheetJS
npm install xlsx --save
2、table 转 sheet 转 blob 转 ObjectUrl
import XLSX from 'xlsx'
makeExcel() {
let sheet = XLSX.utils.table_to_sheet(this.$refs.table);
let workbook = {
SheetNames: ['总览'],
Sheets: {
'总览': sheet
}
};
let wopts = {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
};
let wbout = XLSX.write(workbook, wopts);
let blob = new Blob([this.s2ab(wbout)], {type: "application/octet-stream"});
this.xlsxUrl = URL.createObjectURL(blob);
},
s2ab(s) {
let buf = new ArrayBuffer(s.length);
let view = new Uint8Array(buf);
for (let i = 0; i != s.length; ++i) {
view[i] = s.charCodeAt(i) & 0xFF;
}
return buf;
}
3、使用
<a :href="xlsxUrl" download="文件名.xlsx">文件下载</a>
ExcelJS 导出 Excel
1、安装依赖
npm install exceljs --save
2、生成
const ExcelJS = require('exceljs/dist/exceljs');
makeExcel() {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('工作簿1');
worksheet.columns = [
{header: '列头1', key: 'column_1', width: 50},
{header: '列头2', key: 'column_2', width: 50}
];
worksheet.addRow(["值1", "值2"]);
workbook.xlsx.writeBuffer().then((buffer)=>{
let blob = new Blob([buffer], {type: "application/octet-stream"});
this.xlsxUrl = URL.createObjectURL(blob);
});
},
3、使用
<a :href="xlsxUrl" download="报告.xlsx">下载</a>
国际化
1、main.js
import VueI18n from 'vue-i18n'
new Vue({
i18n: new VueI18n({
locale: localStorage.getItem('locale') || 'en-US',
messages: {}
}),
components: { App }
});
2、App.vue
watch: {
"$i18n.locale": function () {
this.fetchLang();
}
},
methods: {
fetchLang(){
// 异步加载国际化词条
if(this.$i18n.locale == 'en-US'){
import('element-ui/lib/locale/lang/en').then((lang)=>{
locale.use(lang);
});
import('./lang/en').then((lang)=>{
this.$i18n.setLocaleMessage('en-US', lang)
// 或者 mergeLocaleMessage
});
}
else{
import('element-ui/lib/locale/lang/zh-CN').then((lang)=>{
locale.use(lang);
});
import('./lang/zh').then((lang)=>{
this.$i18n.mergeLocaleMessage('zh-CN', lang)
});
}
},
changeLang(locale) {
this.$i18n.locale = locale;
localStorage.setItem('locale', locale); // 语言选择保存到本地
}
},
mounted() {
this.fetchLang();
}
3、词条文件
export const m = {
"common": {
"submit": "Submit"
}
}
4、使用
$t('m.common.submit')
vue-i18n@9
<script main.js>
import { createApp } from 'vue';
import App from './App.vue';
import { createI18n } from 'vue-i18n';
import zhCN from './assets/lang/zhCN';
import en from './assets/lang/en';
let locale = localStorage.getItem('locale') || 'zhCN';
const i18n = createI18n({
locale,
legacy: false,
messages: {
en,
zhCN,
},
});
createApp(App).use(i18n).mount('#app');
</script>
<script>
this.$t('dashboard.1');
this.$i18n.locale;
</script>
<script>
import { useI18n } from 'vue-i18n';
setup() {
const { t, locale } = useI18n();
t('dashboard.1');
}
</script>
<script setup>
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
t('dashboard.1');
</script>
<template>
<div>{{ $t('dashboard.1') }}{{$i18n.locale}}</div>
</template>
解压缩
解压 .zip
npm install jszip --save
import jszip from 'jszip';
jszip.loadAsync(file).then((zip) => {
// zip.files 里既有文件也有目录,而且是平铺而不是嵌套,name里包含路径
Object.values(zip.files).forEach((item) => {
if (!item.dir && item.name.endsWith('.***')) {
item.async('blob').then((blob) => {
let file = new File([blob], item.name.substring(item.name.lastIndexOf('.') + 1));
});
}
});
});
解压 .tar.gz
npm install pako --save
从 https://github.com/ankitrohatgi/tarballjs 下载 tarball.js
import pako from 'pako'
import { TarReader } from './tarball.js';
let reader = new FileReader();
reader.readAsArrayBuffer(tarGzFile);
reader.onload = () => {
let byteArray = new Uint8Array(reader.result);
byteArray = pako.ungzip(byteArray);
let treader = new TarReader();
treader.readFile(new File([byteArray], tarGzFile.name)).then((files) => {
files.forEach((item) => {
if (item.type === 'file' && item.name.endsWith('.***')) {
let blob = treader.getFileBlob(item.name);
let file = new File([blob], item.name.substring(item.name.lastIndexOf('.') + 1));
}
});
});
};
JSEncrypt
import JSEncrypt from 'jsencrypt';
// RSA 公钥
let publicKey = `-----BEGIN PUBLIC KEY-----
***
-----END PUBLIC KEY-----`;
let jse = new JSEncrypt();
jse.setPublicKey(publicKey);
jse.encrypt(source); // 加密
Axios
// 提交文件
let formData = new FormData();
formData.append('field', field);
formData.append('file', file);
axios.post('/url', formData, {
'Content-type': 'multipart/form-data',
});
// 下载文件
axios({
method: 'get',
url: `/url`,
responseType: 'blob',
}).then((res) => {
let blob = res.data;
let objectUrl = URL.createObjectURL(res.data);
let file = new File([res.data], 'filename', {type: 'application/xml', lastModified: Date.now()});
});
// 带 params 参数
axios({
method: 'get',
url: `/url`,
params: new URLSearchParams(location.search) // 解析URL参数
});
其他组件
时间:moment.js
代码编辑器:CodeMirror