判断页面是横屏还是竖屏
window.addEventListener("load", rotate, false);
window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", rotate, false);
function rotate() {
if (window.orientation == 180 || window.orientation == 0) {
console.log('竖屏')
}
if (window.orientation == 90 || window.orientation == -90) {
console.log('横屏')
}
}
如果是横屏自刷新页面 (在App.vue
)写入
<template>
<div id="app">
<keep-alive>
<router-view v-if="$route.meta.keepAlive || isRouterAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive || isRouterAlive" />
</div>
</template>
<script>
export default {
name: 'App',
provide() {
// 在祖先组件中通过 provide 提供变量
return {
reload: this.reload, // 声明一个变量
}
},
data() {
return {
isRouterAlive: true, // 控制 router-view 是否显示达到刷新效果
}
},
methods: {
// provide中声明的变量
reload() {
// 通过 this.isRouterAlive 控制 router-view 达到刷新效果
this.isRouterAlive = false
this.$nextTick(function () {
this.isRouterAlive = true
})
},
},
}
</script>
在你需要自刷新的页面中写入
<template>
<div v-cloak id="main" class="transform">
</div>
</template>
<script>
export default {
// inject: ['reload'], // 使用 inject 注入 reload 变量
data() {
return {
}
},
mounted() {
window.addEventListener('load', rotate, false)
window.addEventListener(
'onorientationchange' in window ? 'orientationchange' : 'resize',
rotate,
false
)
// 与app交互的 横屏方法
this.getOrientation()
function rotate() {
if (window.orientation == 180 || window.orientation == 0) {
console.log('竖屏')
}
if (window.orientation == 90 || window.orientation == -90) {
console.log('横屏')
// 自刷新
this.reload() // 直接使用
}
}
},
通过js来判断是否是横屏如果是就自刷新页面解决横屏之后只有屏幕一半宽度的问题
css 旋转90度来强制横屏方法
#main {
width: 100%;
height: 100%;
}
@media screen and (orientation: portrait) {
#main {
position: absolute;
width: 100vh;
height: 100vw;
top: 0;
left: 100vw;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
transform-origin: 0% 0%;
}
}
@media screen and (orientation: landscape) {
#main {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
}
Vue自创插件发布到npm以及使用方法
目标:创建my-popup-selector下拉框组件,并发布到npm,效果如下图:
禁用时样式:
①创建vue项目:
my-popup-selector
②项目目录结构截图如下:
③在项目根目录新建 packages/ 文件夹用于存放组件,由于 packages/ 是新增的,webpack无法获取编译,因此需要在webpack配置文件里加上相关配置
④在项目根目录新建 vue.config.js 配置文件,并写入一下配置:
module.exports = {
css: { // 是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript 中的 inline 代码)。
extract: false, // 生产环境下是 true,开发环境下是 false
},
// 扩展 webpack 配置,使 packages 加入编译
// chainWebpack 是一个函数,会接收一个基于 webpack-chain 的 ChainableConfig 实例。允许对内部的 webpack 配置进行更细粒度的修改
chainWebpack: config => {
config.module
.rule('js')
.include
.add(__dirname + 'packages') // 注意这里需要绝对路径,所以要拼接__dirname
.end()
.use('babel')
.loader('babel-loader')
.tap(options => {
// 修改它的选项...
return options
})
}
}
⑤在 packages/ 文件夹下创建组件,新建 vue-popup-selector/ 文件夹和 index.js 文件,截图如下:
⑥在 my-popup-selector/ 文件加下新建 VueAmazingSelector.vue 组件文件和 index.js 文件,截图如下:
⑦在 VueAmazingSelector.vue 中编写组件代码:
<template>
<div class="m-amazing-select" :style="`height: ${height}px;`">
<div
:class="['m-select-wrap', {'hover': !disabled, 'focus': showOptions, 'disabled': disabled}]"
:style="`width: ${width - 2}px; height: ${height - 2}px;`"
tabindex="0"
@mouseenter="onInputEnter"
@mouseleave="onInputLeave"
@blur="activeBlur && !disabled ? onBlur() : e => e.preventDefault()"
@click="disabled ? e => e.preventDefault() : openSelect()">
<div
:class="['u-select-input', {'placeholder': !selectedName}]"
:style="`line-height: ${height - 2}px;width: ${width - 37}px; height: ${height - 2}px;`"
:title="selectedName"
>{{ selectedName || placeholder }}</div>
<svg :class="['triangle', {'rotate': showOptions, 'show': !showClose}]" viewBox="64 64 896 896" data-icon="down" aria-hidden="true" focusable="false"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg>
<svg @click.stop="onClear" :class="['close', {'show': showClose}]" focusable="false" data-icon="close-circle" aria-hidden="true" viewBox="64 64 896 896"><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"></path></svg>
</div>
<transition name="fade">
<div
v-show="showOptions"
class="m-options-panel"
@mouseenter="onEnter"
@mouseleave="onLeave"
:style="`top: ${height + 6}px; line-height: ${height - 12}px; max-height: ${ num * (height - 2) }px; width: ${width}px;`">
<p
v-for="(option, index) in options" :key="index"
:class="['u-option', {'option-selected': option[label]===selectedName, 'option-hover': !option.disabled&&option[value]===hoverValue, 'option-disabled': option.disabled }]"
:title="option[label]"
@mouseenter="onHover(option[value])"
@click="option.disabled ? e => e.preventDefault() : onChange(option[value], option[label], index)">
{{ option[label] }}
</p>
</div>
</transition>
</div>
</template>
<script>
export default {
name: 'VueAmazingSelector',
model: {
prop: 'selectedValue',
event: 'model'
},
props: {
options: { // 选项数据
type: Array,
default: () => []
},
label: { // 选择器字典项的文本字段名
type: String,
default: 'label'
},
value: { // 选择器字典项的值字段名
type: String,
default: 'value'
},
placeholder: { // 选择框默认文字
type: String,
default: '请选择'
},
disabled: { // 是否禁用下拉
type: Boolean,
default: false
},
allowClear: { // 是否支持清除
type: Boolean,
default: false
},
width: { // 选择框宽度
type: Number,
default: 200
},
height: { // 选择框高度
type: Number,
default: 36
},
num: { // 下拉面板最多能展示的下拉项数,超过后滚动显示
type: Number,
default: 6
},
selectedValue: { // 当前选中的option条目(v-model)
type: [Number, String],
default: null
}
},
data () {
return {
selectedName: null,
hoverValue: null, // 鼠标悬浮项的value值
activeBlur: true, // 是否激活blur事件
showClose: false, // 清除按钮显隐
showOptions: false // options面板
}
},
watch: {
options () {
this.initSelector()
console.log('options')
},
selectedValue () {
this.initSelector()
console.log('selectedValue')
}
},
mounted () {
this.initSelector()
},
methods: {
initSelector () {
if (this.selectedValue) {
const target = this.options.find(option => option[this.value] === this.selectedValue)
if (target) {
this.selectedName = target[this.label]
this.hoverValue = target[this.value]
} else {
this.selectedName = this.selectedValue
this.hoverValue = null
}
} else {
this.selectedName = null
this.hoverValue = null
}
},
onBlur () {
if (this.showOptions) {
this.showOptions = false
}
},
onInputEnter () {
// console.log('input enter')
if (this.allowClear && this.selectedName) {
this.showClose = true
}
},
onInputLeave () {
// console.log('input leave')
if (this.allowClear && this.showClose) {
this.showClose = false
}
},
onHover (value) {
this.hoverValue = value
},
onEnter () {
this.activeBlur = false
},
onLeave () {
this.hoverValue = null
this.activeBlur = true
},
openSelect () {
this.showOptions = !this.showOptions
if (!this.hoverValue && this.selectedName) {
const target = this.options.find(option => option[this.label] === this.selectedName)
this.hoverValue = target ? target[this.value] : null
}
},
onClear () {
this.showClose = false
this.selectedName = null
this.hoverValue = null
},
onChange (value, label, index) { // 选中下拉项后的回调
if (this.selectedValue !== value) {
this.selectedName = label
this.hoverValue = value
this.$emit('model', value)
this.$emit('change', value, label, index)
}
this.showOptions = false
}
}
}
</script>
<style lang="less" scoped>
@themeColor: #1890ff; // 自定义主题色
P {
margin: 0;
}
.m-amazing-select {
position: relative;
display: inline-block;
font-size: 14px;
font-weight: 400;
color: rgba(0,0,0,.65);
}
.fade-enter-active, .fade-leave-active {
transform: scaleY(1);
transform-origin: 0% 0%;
opacity: 1;
transition: all .3s;
}
.fade-enter {
transform: scaleY(0.8);
transform-origin: 0% 0%;
opacity: 0;
}
.fade-leave-to {
transform: scaleY(1);
opacity: 0;
}
.m-select-wrap {
position: relative;
display: inline-block;
border: 1px solid #d9d9d9;
border-radius: 4px;
cursor: pointer;
transition: all .3s cubic-bezier(.645,.045,.355,1);
.u-select-input {
display: block;
text-align: left;
margin-left: 11px;
margin-right: 24px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.placeholder {
color: #bfbfbf;
}
.triangle {
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
right: 12px;
width: 12px;
height: 12px;
fill: rgba(0,0,0,.25);
opacity: 0;
pointer-events: none;
transition: all 0.3s ease-in-out;
}
.rotate {
transform: rotate(180deg);
-webkit-transform: rotate(180deg);
}
.close {
opacity: 0;
pointer-events: none;
transition: all 0.3s ease-in-out;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
right: 12px;
width: 12px;
height: 12px;
fill: rgba(140, 140, 140, 0.6);
&:hover {
fill: rgba(100, 100, 100,.8);
}
}
.show {
opacity: 1;
pointer-events: auto;
}
}
.hover { // 悬浮时样式
&:hover {
border-color: @themeColor;
}
}
.focus { // 激活时样式
border-color: @themeColor;
box-shadow: 0 0 0 2px fade(@themeColor, 20%);
}
.disabled { // 下拉禁用样式
color: rgba(0,0,0,.25);
background: #f5f5f5;
user-select: none;
cursor: not-allowed;
}
.m-options-panel {
position: absolute;
z-index: 999;
overflow: auto;
background: #FFF;
padding: 4px 0;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,15%);
.u-option { // 下拉项默认样式
text-align: left;
position: relative;
display: block;
padding: 5px 12px;
font-weight: 400;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
cursor: pointer;
transition: background .3s ease;
}
.option-selected { // 被选中的下拉项样式
font-weight: 600;
background: #fafafa;
}
.option-hover { // 悬浮时的下拉项样式
background: #e6f7ff;
// background: saturate(fade(@themeColor, 12%), 30%);
}
.option-disabled { // 禁用某个下拉选项时的样式
color: rgba(0,0,0,.25);
user-select: none;
cursor: not-allowed;
}
}
</style>
⑧在 my-popup-selector/index.js 中导出组件
// 引入组件
import VueAmazingSelector from './VueAmazingSelector.vue'
// 为组件提供 install 安装方法,供按需引入
VueAmazingSelector.install = (Vue) => {
Vue.component(VueAmazingSelector.name, VueAmazingSelector)
}
// 导出组件
export default VueAmazingSelector
⑨在 packages/index.js 文件中对整个组件库进行导出
import VueAmazingSelector from './vue-amazing-selector'
// 存储组件列表
const components = [
VueAmazingSelector
]
/*
定义install 方法,接收Vue作为参数,如果使用use注册插件,则所有的组件都将被注册
*/
const install = function (Vue) {
// 判断是否安装
if (install.installed) { return }
// 遍历所有组件
components.map(component => {
Vue.component(component.name, component)
})
}
// 判断是否引入文件
if (typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
export {
VueAmazingSelector
}
export default {
install
}
在 src/main.js 中导入刚创建的组件,检测是否正常可用
import VueAmazingSelector from '../packages'
Vue.use(VueAmazingSelector)
// 在 App.vue 中引用,并启动项目查看
app.vue代码
<template>
<VueAmazingSelector
:options="options"
label="label"
value="value"
placeholder="请选择"
:disabled="false"
:width="160"
:height="36"
:num="6"
v-model="selectedValue"
@change="onChange" />
</template>
<script>
export default {
name: 'App',
data () {
return {
options: [
{
label: '北京市',
value: 1
},
{
label: '上海市上海市上海市上海市',
value: 2,
disabled: true
},
{
label: '郑州市',
value: 3
},
{
label: '纽约市纽约市纽约市纽约市',
value: 4
},
{
label: '旧金山',
value: 5
},
{
label: '悉尼市',
value: 6
},
{
label: '伦敦市',
value: 7
},
{
label: '巴黎市',
value: 8
}
],
selectedValue: 1
}
},
watch: {
selectedValue (to) {
console.log('selectedValue:', to)
}
},
mounted () {
setTimeout(() => { // 模拟接口调用
this.selectedValue = 3
}, 1000)
},
methods: {
onChange (value, label, index) {
console.log('item:', value, label, index)
}
}
}
</script>
⑪在 package.json 的 scripts 中添加一条编译命令
--target: 构建目标,默认为应用模式。这里修改为 lib 启用库模式。
--dest : 输出目录,默认 dist。这里我们改成 lib
[entry]: 最后一个参数为入口文件,默认为 src/App.vue。这里我们指定编译 packages/ 组件库目录。
"scripts": {
"lib": "vue-cli-service build --target lib --name selector --dest lib packages/index.js"
}
⑫执行编译命令
yarn lib(或num run lib)
执行结果如下图:
然后在项目根目录会生成如下图所示文件夹:
⑬在终端执行 npm init 初始化包配置文件package.json 可忽略这一步
package.json部分截图如下:
name: 包名,该名字是唯一的。可在 npm 官网搜索名字,不可重复。
version: 版本号,每次发布至 npm 需要修改版本号,不能和历史版本号相同。
main: 入口文件,需指向最终编译后的包文件。
author:作者
private:是否私有,需要修改为 false 才能发布到 npm
⑭在项目根目录创建 .npmignore 文件,设置忽略发布的文件,类似 .gitignore 文件
只有编译后的 lib 目录、package.json、README.md是需要被发布的
# 忽略目录
.DS_Store
node_modules
packages/
public/
src/
# 忽略指定文件
.eslintrc.cjs
.gitignore
.npmignore
.npmrc
babel.config.js
vue.config.js
yarn.lock
*.map
⑮编写README.md文件(使用markdown格式)
参考文档: http://markdown.p2hp.com/index.html
# vue-amazing-selector
## An Amazing Select Component For Vue2
## Install & Use
```sh
npm install vue-amazing-selector
#or
yarn add vue-amazing-selector
Import and register component
Global
import Vue from 'vue'
import VueAmazingSelector from 'vue-amazing-selector'
Vue.use(VueAmazingSelector)
Local
<template>
<VueAmazingSelector
:options="options"
label="label"
value="value"
placeholder="请选择"
:disabled="false"
:width="160"
:height="36"
:num="6"
v-model="selectedValue"
@change="onChange" />
</template>
<script>
export default {
name: 'App',
data () {
return {
options: [
{
label: '北京市',
value: 1
},
{
label: '上海市上海市上海市上海市',
value: 2,
disabled: true
},
{
label: '郑州市',
value: 3
},
{
label: '纽约市纽约市纽约市纽约市',
value: 4
},
{
label: '旧金山',
value: 5
},
{
label: '悉尼市',
value: 6
},
{
label: '伦敦市',
value: 7
},
{
label: '巴黎市',
value: 8
}
],
selectedValue: 1
}
},
watch: {
selectedValue (to) {
console.log('selectedValue:', to)
}
},
mounted () {
setTimeout(() => { // 模拟接口调用
this.selectedValue = 3
}, 1000)
},
methods: {
onChange (value, label, index) {
console.log('item:', value, label, index)
}
}
}
</script>
## Props
| 属性 | 说明 | 类型 | 默认值 |
| :--- | :--- | :--- | :--- |
options | 选项数据 | Array | []
label | 选择器字典项的文本字段名 | String | label
value | 选择器字典项的值字段名 | String | value
placeholder | 选择框默认文字 | String | 请选择
disabled | 是否禁用下拉 | Boolean | false
allowClear | 是否支持清除 | Boolean | false
width | 选择框宽度 | Number | 200
height | 选择框高度 | Number | 36
num | 下拉面板最多能展示的下拉项数,超过后滚动显示 | Number | 6
selectedValue | (v-model)当前选中的option条目 | /Number/String | null
## Events
事件名 | 说明 | 返回值
:--- | :--- | :---
change | 选择某项下拉后的回调函数 | value, label, index(value值,label文本值,index索引值)
⑯登录npm
如果没有npm账号,可以去npm官网( npm) 注册一个账号
注册成功后在本地查看npm镜像:
npm config get registry
输出:https://registry.npmjs.org 即可
如果不是则需要设置为npm镜像:
npm config set registry https://registry.npmjs.org
然后在终端执行:
npm login
依次输入用户名,密码,邮箱
输出Logged in as…即可
npm whoami // 查看当前用户是否已登录
⑰发布组件到npm
在终端执行:npm publish
发布成功后即可在npm官网搜索到该组件,如下图;并可以通过 npm install my-popup-selector(或yarn add my-popup-selector)进行安装
⑱在要使用的项目中安装并注册插件
yarn add my-popup-selector
// 在 main.js 文件中引入并注册:
import Vue from 'vue'
import VueAmazingSelector from 'my-popup-selector'
Vue.use(VueAmazingSelector)
在要使用组件的页面直接使用即可:
<template>
<VueAmazingSelector
:options="options"
label="label"
value="value"
placeholder="请选择"
:disabled="false"
:width="160"
:height="36"
:num="6"
v-model="selectedValue"
@change="onChange" />
</template>
<script>
export default {
name: 'App',
data () {
return {
options: [
{
label: '北京市',
value: 1
},
{
label: '上海市上海市上海市上海市',
value: 2,
disabled: true
},
{
label: '郑州市',
value: 3
},
{
label: '纽约市纽约市纽约市纽约市',
value: 4
},
{
label: '旧金山',
value: 5
},
{
label: '悉尼市',
value: 6
},
{
label: '伦敦市',
value: 7
},
{
label: '巴黎市',
value: 8
}
],
selectedValue: 1
}
},
watch: {
selectedValue (to) {
console.log('selectedValue:', to)
}
},
mounted () {
setTimeout(() => { // 模拟接口调用
this.selectedValue = 3
}, 1000)
},
methods: {
onChange (value, label, index) {
console.log('item:', value, label, index)
}
}
}
</script>
如果遇到less-loader版本过高问题:
yarn add less-loader安装版本过高,报错如图
解决:yarn add less-loader@6.0.0