一、Web API
加载在线字体文件
function loadFont () {
let url = "https://resource.hellofont.cn/fonts/exhibition2/7240.ttf"
let font = "f-exhibition2-7240"
let prefont = new FontFace (font, `url(${ url })` )
prefont. load (). then ( function ( loaded_face ) {
document.fonts. add (loaded_face)
document.body.style.fontFamily = document.body.style.fontFamily + font
})
}
下载一个链接
function download ( link , name ) {
if ( ! name) {
name = link. slice (link. lastIndexOf ( "/" ) + 1 )
}
let eleLink = document. createElement ( "a" )
eleLink.download = name
eleLink.style.display = "none"
eleLink.href = link
document.body. appendChild (eleLink)
eleLink. click ()
document.body. removeChild (eleLink)
}
download ( "http://111.229.14.189/file/1.xlsx" )
设置手机桌面图标
< link rel = "apple-touch-icon" href = " < %= BASE_URL %>logo.png" />
< link rel = "apple-touch-icon-precomposed" href = " < %= BASE_URL %>logo.png" />
< link rel = "apple-touch-startup-image" href = " < %= BASE_URL %>logo.png" />
< link rel = "shortcut icon" type = "image/ico" href = " < %= BASE_URL %>logo.png" />
遮罩时,禁止背景滚动
< div class = "mask" @touchmove.self.prevent ></ div >
二三倍图 scss 方法
.bg-image( $url ) {
background : ~ "url(${url}@2x.png)" no-repeat center ;
background-size : 100 % ;
@media ( -webkit-min-device-pixel-ratio : 3 ), ( min-device-pixel-ratio : 3 ) {
background : ~ "url(${url}@3x.png)" no-repeat center ;
background-size : 100 % ;
}
}
vue 国际化 csv 转 i18n
vue-i18n 官网
yarn add vue-i18n@9
// main.js (Vue3)
import { createApp } from "vue"
import App from "./App.vue"
import i18n from "./i18n"
const app = createApp (App)
app. use (i18n)
app. mount ( "#app" )
// i18n.js (Vue3 + Composition API)
import { createI18n } from "vue-i18n"
import axios from "axios"
const i18n = createI18n ({
legacy: false ,
locale: localStorage. getItem ( "locale" ) || "zh-CN" ,
fallbackLocale: "en" ,
})
// 动态加载语言包
async function loadLanguage ( lang ) {
const res = await axios. get ( `/locales/${ lang }.json` )
i18n.global. setLocaleMessage (lang, res.data)
i18n.global.locale.value = lang
localStorage. setItem ( "locale" , lang)
document. querySelector ( "html" ). setAttribute ( "lang" , lang)
}
export default i18n
export { loadLanguage }
< template >
< div >
{{ $t ( "apple" ) }}
< button @ click = " handleChange ( 'en' ) " >English</ button >
< button @ click = " handleChange ( 'zh-CN' ) " >中文</ button >
</ div >
</ template >
< script setup >
import { useI18n } from "vue-i18n"
import { loadLanguage } from "./i18n"
const { locale } = useI18n ()
function handleChange ( lang ) {
loadLanguage (lang)
}
</ script >
// main.js
import i18n from "./i18n"
new Vue ({
i18n,
render : ( h ) => h (App),
}). $mount ( "#app" )
// i18n.js
import Vue from "vue" ;
import VueI18n from "vue-i18n" ;
import axios from "axios" ;
Vue. use (VueI18n);
const i18n = new VueI18n ({
locale: localStorage. getItem ( "locale" ) || "zh-cn" , //设置默认语言
});
i18n. setI18nLanguage = ( lang ) => {
i18n.locale = lang;
document. querySelector ( "html" ). setAttribute ( "lang" , lang); //设置html的语言
localStorage. setItem ( "locale" , lang);
};
class Translate {
constructor () {
this .messages = {};
this .loadedLanguages = [];
}
parseSheet ( data ) {
data. forEach (( row , rowIndex ) => {
let key;
row. forEach (( col , colIndex ) => {
// 第一行,文件名
if (rowIndex === 0 && colIndex !== 0 ) {
if ( ! (col in this .messages)) {
this .messages[col] = {};
this .loadedLanguages. push (col);
}
}
if (rowIndex >= 1 ) {
if (colIndex === 0 ) {
key = col;
} else {
const lang = this .loadedLanguages[colIndex - 1 ];
this .messages[lang][key] = col;
}
}
});
});
return this ;
}
}
axios. get ( "/language.csv" ). then (({ data }) => {
i18n. setI18nLanguage (i18n.locale);
let sheets = CSVToArray (data);
let translate = new Translate (). parseSheet (sheets);
for ( const key in translate.messages) {
if (Object.hasOwnProperty. call (translate.messages, key)) {
const message = translate.messages[key];
i18n. setLocaleMessage (key, message);
}
}
});
function CSVToArray ( strData , strDelimiter ) {
strDelimiter = strDelimiter || "," ;
let objPattern = new RegExp (
"( \" +
strDelimiter +
"| \r ? \n | \r |^)" +
'(?:"([^"]*(?:""[^"]*)*)"|' +
'([^" \' +
strDelimiter +
" \r\n ]*))" ,
"gi"
);
let arrData = [[]];
let arrMatches = null ;
let strMatchedValue = "" ;
while ((arrMatches = objPattern. exec (strData))) {
let strMatchedDelimiter = arrMatches[ 1 ];
if (strMatchedDelimiter. length && strMatchedDelimiter != strDelimiter) {
arrData. push ([]);
}
if (arrMatches[ 2 ]) {
strMatchedValue = arrMatches[ 2 ]. replace ( new RegExp ( '""' , "g" ), '"' );
} else {
strMatchedValue = arrMatches[ 3 ];
}
arrData[arrData. length - 1 ]. push (strMatchedValue);
}
return arrData;
}
export default i18n;
< template >
< div >
{{ $t ( "apple" ) }}
</ div >
</ template >
< script >
export default {
methods: {
// 切换语言
handleChange ( lang ) {
this .$i18n. setI18nLanguage (lang)
},
},
}
</ script >
vue 生成当日打包次数版本号
// vue.config.js
const fs = require ( "fs" )
const IS_PROD = process.env. NODE_ENV === "production"
process.env. VUE_APP_VERSION = require ( "./package.json" ).version
if ( IS_PROD ) {
const resetTodayBuildCount = ( root ) => {
return {
// 获取配置信息的日期和构建次数
getSettingData ( root , fileName ) {
let rgx = [ / const TODAY_BUILD_COUNT = \d * / , / const DATE = \d * / ]
return new Promise (( resolve ) => {
// 读取文件
fs. readFile (root, "utf-8" , ( emarr , data ) => {
let todayBuildCount = data. match (rgx[ 0 ])[ 0 ]
let date = data. match (rgx[ 1 ])[ 0 ]
resolve ({
content: data,
data: {
todayBuildCount,
date,
},
})
})
})
},
async run () {
/**
* 1.获取配置信息的日期和构建次数
* 2.判断当前日期是否小于写入的日期
* 3.将配置信息的内容替换
*/
let { content, data } = await this . getSettingData (root)
const date = + data.date. split ( "=" )[ 1 ]
const count = + data.todayBuildCount. split ( "=" )[ 1 ]
const TODAY_FORMATE = + getFormatDate ({
date: new Date (),
pattern: 4 ,
})
const todayBuildCount = new Date ( TODAY_FORMATE ) > new Date (date) ? 1 : count + 1
const newTodayBuildCount = `const TODAY_BUILD_COUNT = ${ todayBuildCount }`
const newDate = `const DATE = ${ TODAY_FORMATE }`
content = content
. replace (data.todayBuildCount, newTodayBuildCount)
. replace (data.date, newDate)
// 写入文件
fs. writeFile (root, content, ( err ) => {
console. log (err)
})
},
}
}
resetTodayBuildCount ( "./src/setting.js" ). run ()
}
// src/setting.js
const VERSION_FORMATE = process.env. VUE_APP_VERSION
const DATE = 230117
const TODAY_BUILD_COUNT = 1
export const VERSION = `V ${ VERSION_FORMATE } ${ DATE }-${ TODAY_BUILD_COUNT }` // 版本号
vue 全局方法的两种方式
// 第一种
// main.js 把方法挂到vue实例上(不推荐)会造成vue实例过大,业务逻辑和框架逻辑混在一起
import api from './api'
Vue . prototype .$api = api
// 第二种
// vue.config.js (推荐)
const webpack = require ( 'webpack' )
module . exports = {
configureWebpack: {
plugins: [
new webpack. ProvidePlugin ({
UTILS: [path. resolve (__dirname, './src/utils/Utils.js' ), 'default' ], // 定义的全局函数类
TOAST: [path. resolve (__dirname, './src/utils/Toast.js' ), 'default' ], // 定义的全局Toast弹框方法
LOADING: [path. resolve (__dirname, './src/utils/Loading.js' ), 'default' ] // 定义的全局Loading方法
API : [path. resolve (__dirname, './src/apis/apis.js' ), 'default' ] // 定义的全局API方法
})
]
},
}
// eslintrc.js
module . exports = {
"globals" : {
"UTILS" : true ,
"TOAST" : true ,
"LOADING" : true
}
// ...省略N行ESlint的配置
}
vue 拖拽悬浮按钮
// main.js
import "@/directive/drag"
// directive/drag/index.js
import Vue from "vue"
// 拖拽结束自动靠边停方法
import autoStop from "./methods"
// 保存初始状态的变量
let startX, startY, firstTime, lastTime, isApp
// 拖拽开始事件
const dragStart = ( event , el ) => {
firstTime = new Date (). getTime ()
// 1、如果元素上存在自定义的私有属性__VueDragTimer__,则清空该定时器
// 2、__VueDragTimer__:自定义定时器属性,首尾双下划线挂载于元素上,方便在解绑指令时(unbind)清除定时器,并可删除属性
if (el.__VueDragTimer__) clearInterval (el.__VueDragTimer__)
// 通过touch属性计算获取初始位置
const { pageX , pageY } = isApp ? event.touches[ 0 ] : event
const {
// 元素距离限制区域上左距离,此变量需要实时获取
offsetTop : eT ,
offsetLeft : eL ,
} = el
startX = parseInt (pageX - eL)
startY = parseInt (pageY - eT)
}
// 拖动事件
const dragMove = ( event , el , pW , pH , pT , pL , eW , eH ) => {
// 添加阻止默认事件,防止影响到元素点击事件
event. preventDefault ()
// 获取touch事件,计算并实时改变元素位置(拖拽效果)
const { pageX , pageY } = isApp ? event.touches[ 0 ] : event
let movePageX = parseInt (pageX - startX)
let movePageY = parseInt (pageY - startY)
// 超出限制区域,禁止越界
if (movePageX <= pL) {
movePageX = pL
} else if (movePageX >= pW - eW + pL) {
movePageX = pW - eW + pL
}
if (movePageY <= pT) {
movePageY = pT
} else if (movePageY >= pH - eH + pT) {
movePageY = pH - eH + pT
}
el.style.left = movePageX + "px"
el.style.top = movePageY + "px"
}
// 拖拽结束,自动停靠
const dragStop = ( event , el ) => {
lastTime = new Date (). getTime ()
autoStop (el)
if ( ! isApp) {
document.onmousemove = document.onmouseup = null
// 用时间差判断是拖拽还是点击
el. setAttribute ( "data-flag" , lastTime - firstTime > 200 )
}
}
Vue. directive ( "drag" , {
inserted ( el ) {
Vue. nextTick (() => {
const {
// 存在定位的父级元素及其宽高(即元素可移动限制区域,先假设为parentNode,不是相对父级定位可以再调整)
parentNode : {
clientWidth : pW ,
clientHeight : pH ,
offsetTop : pT ,
offsetLeft : pL ,
offsetWidth : xW ,
},
// 元素计算宽高(如果停靠不想完全靠边,可给元素添加对应padding)
offsetWidth : eW ,
offsetHeight : eH ,
dataset : { app },
} = el
isApp = app === "true"
if ( ! isApp) {
el.style.position = "absolute"
el.style.cursor = "move"
}
if (isApp) {
el. addEventListener ( "touchstart" , ( event ) => dragStart (event, el))
el. addEventListener ( "touchmove" , ( event ) => dragMove (event, el, pW, pH, pT, pL, eW, eH))
el. addEventListener ( "touchend" , ( event ) => dragStop (event, el))
} else {
el. onmousedown = ( event ) => {
dragStart (event, el)
document. onmousemove = ( event ) => dragMove (event, el, pW, pH, pT, pL, eW, eH)
document. onmouseup = ( event ) => dragStop (event, el)
}
}
})
},
})
// directive/drag/methods.js
// 元素移动方法
const autoMove = function ( el , changeAttr , endValue ) {
// 定义 起点、运动中当前位置 变量
let currentValue
const {
// 元素距离限制区域上左距离
offsetTop : eT ,
offsetLeft : eL ,
} = el
// 改变属性为 'left' 时,起点赋值为:元素与限制区左边的距离
if (changeAttr === "left" ) currentValue = eL
// 改变属性为 'top' 时,起点赋值为:元素与限制区上边的距离
if (changeAttr === "top" ) currentValue = eT
// 移动步距设置为 终点与起点 距离的 1 / 33,值的正负表示移动方向
const step = (endValue - currentValue) / 33
// 定时器存在则清除处理
if (el.__VueDragTimer__) clearInterval (el.__VueDragTimer__)
// 设置自动停靠定时器
el.__VueDragTimer__ = setInterval (() => {
// 若当前位置与终点差值 已经 小于步距,则直接定位到终点位置(js计算精度导致此判断不可少),否则缩小步距的距离
if (Math. abs (endValue - currentValue) < Math. abs (step)) {
el.style[changeAttr] = endValue + "px"
clearInterval (el.__VueDragTimer__)
delete el.__VueDragTimer__
} else {
currentValue += step
el.style[changeAttr] = currentValue + "px"
}
}, 5 )
}
/**
* 自动停靠方法:
* 停靠原则
* 1、默认左右停靠开关永久开启
* 2、上下停靠开关在距离上下<=50像素时开启
* 3、最终移动方向:从停靠开关开启的方向中,取需要移动距离最小的方向
*/
const autoStop = function ( el ) {
const {
// 存在定位的父级元素及其宽高(即元素可移动限制区域,先假设为parentNode,不是相对父级定位可以再调整)
parentNode : { clientWidth : pW , clientHeight : pH , offsetTop : pT , offsetLeft : pL },
// 元素距离限制区域上左距离
offsetTop : eT ,
offsetLeft : eL ,
// 元素计算宽高(如果停靠不想完全靠边,可给元素添加对应padding)
offsetWidth : eW ,
offsetHeight : eH ,
} = el
/**
* 停靠配置
* changeAttr: 当前配置方案改变的元素属性 'top'| 'left'
* endValue: 停靠动画结束时,需要改变的属性的值(终点)
* distance:当前配置,距离目标结束位置的距离
* toggle: 配置开启开关
*/
const stopConfigs = [
{
// 上移贴边配置
name: "top" ,
changeAttr: "top" ,
endValue: pT,
distance: eT - pT,
toggle: eT <= 50 + pT,
},
{
// 下移贴边配置
name: "bottom" ,
changeAttr: "top" ,
endValue: pH + pT - eH,
distance: pH - eT - eH + pT,
toggle: pH - eT - eH <= 50 - pT,
},
{
// 左移贴边配置
name: "left" ,
changeAttr: "left" ,
endValue: pL,
distance: eL - pL,
toggle: true ,
},
{
// 右移贴边配置
name: "right" ,
changeAttr: "left" ,
endValue: pW + pL - eW,
distance: pW - eL - eW + pL,
toggle: true ,
},
]
// 先重配置中选出开关为true的,然后按distance排序后选出其值最小的配置,并获取对应的changeAttr、endValue
const { changeAttr , endValue } = stopConfigs
. filter (( o ) => o.toggle)
. sort (( a , b ) => a.distance - b.distance)[ 0 ]
autoMove (el, changeAttr, endValue)
}
export default autoStop
<!-- 组件内使用 -->
< template >
< div class = "panel-center" v-if = " state " >
< div class = "wrap" >
< div
class = "box-darg"
v-drag
@ click = " say "
ref = "dragbtn"
@ mousedown = " isShowIframeDiv = true "
@ mouseup = " isShowIframeDiv = false "
: data-app = "`${ isApp }`"
></ div >
<!-- iframeDiv解决拖拽时卡顿 -->
< div >
< div class = "iframeDiv" v-show = " isShowIframeDiv " ></ div >
< iframe
id = "game_iframe"
src = "https://chencan123.gitee.io/"
style = " width : 100 % ; height : 100 vh "
frameborder = "0"
></ iframe >
</ div >
</ div >
</ div >
</ template >
< script >
/***
* 1.解决iframe拖拽时卡顿
* 2.解决拖拽时触发点击事件
* 3.兼容PC和APP模式
* 4.拖拽新增容器居中逻辑(调整限制边界和吸附边界)
*/
export default {
data () {
return {
state: false ,
isApp: true ,
isShowIframeDiv: false ,
}
},
methods: {
show () {
this .state = true
},
hide () {
this .state = false
},
say () {
const isClick = this .$refs.dragbtn. getAttribute ( "data-flag" )
isClick !== "true" && console. log ( "hello" )
},
},
}
</ script >
< style lang = "scss" scoped >
.panel-center {
align-items : center ;
display : flex ;
height : 100 % ;
justify-content : center ;
left : 0 ;
position : fixed ;
top : 0 ;
transition :
0.2 s cubic-bezier ( 0.25 , 0.8 , 0.25 , 1 ),
z-index 1 ms ;
width : 100 % ;
}
.wrap {
transform-origin : center center ;
max-width : 600 px ;
width : 100 % ;
overflow-y : auto ;
}
$num : 36 px ;
.box-darg {
width : $num ;
height : $num ;
position : absolute ;
display : flex ;
padding : 10 px ;
box-sizing : content-box ;
z-index : 99999 ;
background : blue ;
}
.iframeDiv {
width : 100 % ;
height : 100 % ;
position : absolute ;
}
</ style >
vue 雪碧图
<!-- 指定编号默认和激活情况 -->
< template >
< div : style = " bgPosition ( 1 , 50 ) " ></ div >
</ template >
< script >
export default {
data : () => ({
userSprite: {
1 : {
default: [ 152 , 486 ],
active: [ 302 , 482 ],
sid: 1 ,
},
2 : {
default: [ 152 , 636 ],
active: [ 606 , 152 ],
sid: 2 ,
},
},
sid: 2 ,
}),
methods: {
/**
* @param sid 指定图片id
* @param iconWidth icon宽度
* @param iconHeight icon高度
* @example bgPosition(1, 50)
*/
bgPosition ( sid , iconWidth = 146 , iconHeight = iconWidth) {
let [spriteIconX, spriteIconY] =
this .sid == sid ? this .userSprite[sid].active : this .userSprite[sid].default
let spriteWidth = 904
let spriteHeight = 900
let spriteIconWidth = 146
let spriteIconHeight = 146
return {
width: `${ iconWidth }px` ,
height: `${ iconHeight }px` ,
background: `url(${ require ( "@/assets/user.png" ) }) no-repeat` ,
"background-position" : `${
( spriteIconX / ( spriteWidth - spriteIconWidth )) * 100
}% ${ ( spriteIconY / ( spriteHeight - spriteIconHeight )) * 100 }%` ,
"background-size" : `${ spriteWidth * ( iconWidth / spriteIconWidth ) }px ${
spriteHeight * ( iconHeight / spriteIconHeight )
}px` ,
}
},
},
}
</ script >
// loadSprite.js
/**
* @param module 雪碧图文件夹下的图片名称
*/
export default function ( module ) {
const map = require ( `/public/sprite/${ module }` )
return {
//获取icon背景
/**
* @module map中的模块名【文件名】
* @param name 需要显示的图片名称
* @param iconWidth icon宽度
* @param iconHeight icon高度
* @example loadSprite('dots').get('free_email', 49, 47)
*/
get ( name , iconWidth , iconHeight ) {
const {
x : spriteIconX ,
y : spriteIconY ,
w : spriteIconWidth ,
h : spriteIconHeight ,
width : spriteWidth ,
height : spriteHeight ,
} = map[name]
return {
width: `${ spriteIconWidth * ( iconWidth / spriteIconWidth ) }px` ,
height: `${ spriteIconHeight * ( iconHeight / spriteIconHeight ) }px` ,
background: `url(${ require ( `/public/sprite/${ module }.png` ) }) no-repeat` ,
"background-position" : `${
( spriteIconX / ( spriteWidth - spriteIconWidth )) * 100
}% ${ ( spriteIconY / ( spriteHeight - spriteIconHeight )) * 100 }%` ,
"background-size" : `${ spriteWidth * ( iconWidth / spriteIconWidth ) }px ${
spriteHeight * ( iconHeight / spriteIconHeight )
}px` ,
}
},
}
}
分页
< template >
< view class = "content" >
< u-sticky >
< u-tabs-swiper
ref = "uTabs"
class = "u-border-bottom"
: list = " tabsList "
: current = " tabsCurrent "
: is-scroll = " false "
: active-color = " $u.color[ 'error' ] "
bar-width = "150"
bar-height = "4"
@ change = " $u. throttle (tabsChange, 1000 ) "
></ u-tabs-swiper >
</ u-sticky >
< u-empty v-show = " list. length === 0 && isComplete === true " mode = "list" ></ u-empty >
< u-cell-group v-show = " list. length > 0 && isComplete === true " : border = " false " >
< u-cell-item
v-for = " item in list "
: key = " item.id "
: title = " item.title "
: arrow = " false "
hover-class = "none"
></ u-cell-item >
< u-loadmore : status = " status " style = " line-height : 100 rpx " ></ u-loadmore >
</ u-cell-group >
</ view >
</ template >
< script >
export default {
data () {
return {
title: "Hello" ,
page: 0 ,
list: [],
status: "loadmore" ,
isComplete: false ,
// tabs
tabsCurrent: 0 ,
tabsList: [
{
name: "商品列表" ,
},
{
name: "商家信息" ,
},
{
name: "会员评价" ,
},
],
}
},
onReachBottom () {
this . loadData ()
},
onLoad ( options ) {
this . loadData ()
},
methods: {
tabsChange ( index ) {
this .$u.http.requestTask. abort () //tab切换时取消上次接口请求
this .list = []
this .page = 0
this .tabsCurrent = index
this . loadData ()
},
loadData () {
if ( this .status == "nomore" ) return
this .status = "loading"
this .$u.api
. goods ({ page: ++ this .page, keywords: "水" })
. then (( res ) => {
this .list = [ ... this .list, ... (res.data.list || [])]
this .status = res.data.list?. length
? res.data.list?. length < 10
? "nomore"
: "loadmore"
: "nomore"
})
. catch (( err ) => {
//
})
. finally (() => {
this .isComplete = true
})
},
},
}
</ script >
< style >
.content {
display : flex ;
flex-direction : column ;
align-items : center ;
justify-content : center ;
}
.logo {
width : 200 rpx;
height : 200 rpx;
margin : 200 rpx auto 50 rpx auto ;
}
.text-area {
display : flex ;
justify-content : center ;
}
.title {
font-size : 36 rpx;
color : #8f8f94 ;
}
</ style >