feat 添加自定义指令
This commit is contained in:
parent
42b86e8c51
commit
8f1153e739
@ -38,6 +38,8 @@
|
|||||||
--info-color: #1890ff;
|
--info-color: #1890ff;
|
||||||
--info-color-deprecated-bg: #e6f7ff;
|
--info-color-deprecated-bg: #e6f7ff;
|
||||||
--info-color-deprecated-border: #91d5ff;
|
--info-color-deprecated-border: #91d5ff;
|
||||||
|
--placeholder-color: #ccc;
|
||||||
|
--border-color: #d9d9d9;
|
||||||
|
|
||||||
--primary-color-text: #333;
|
--primary-color-text: #333;
|
||||||
--font-size: 14px;
|
--font-size: 14px;
|
||||||
|
@ -21,12 +21,12 @@ defineOptions({
|
|||||||
name: 'PButton'
|
name: 'PButton'
|
||||||
})
|
})
|
||||||
type ButtonProps = {
|
type ButtonProps = {
|
||||||
type: 'primary' | 'default' | 'text' | 'link'
|
type?: 'primary' | 'default' | 'text' | 'link'
|
||||||
/**
|
/**
|
||||||
* 是否正在加载中
|
* 是否正在加载中
|
||||||
*/
|
*/
|
||||||
loading: boolean
|
loading?: boolean
|
||||||
disabled: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
const props = defineProps<ButtonProps>()
|
const props = defineProps<ButtonProps>()
|
||||||
const btnType = computed(() => props.type || 'primary')
|
const btnType = computed(() => props.type || 'primary')
|
||||||
|
@ -74,7 +74,7 @@ const value = computed({
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 4px 11px;
|
padding: 0px 11px;
|
||||||
color: #000000d9;
|
color: #000000d9;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5715;
|
line-height: 1.5715;
|
||||||
@ -136,7 +136,7 @@ const value = computed({
|
|||||||
position: relative;
|
position: relative;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 4px 6px;
|
padding: 0px 6px;
|
||||||
color: #000000d9;
|
color: #000000d9;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.5715;
|
line-height: 1.5715;
|
||||||
@ -157,6 +157,8 @@ const value = computed({
|
|||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
margin: 0 6px;
|
margin: 0 6px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -6,7 +6,12 @@
|
|||||||
import {defineComponent} from "vue";
|
import {defineComponent} from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "POption"
|
name: "POption",
|
||||||
|
props:{
|
||||||
|
value:{
|
||||||
|
type: [String,Number,Object],
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="select-wrapper">
|
<div class="select-wrapper" :class="{active:isActive}" v-click-outside="onClose">
|
||||||
<div class="select-box-wrapper">
|
<div class="select-box-wrapper" @click="isActive = !isActive">
|
||||||
<span class="select-value">{{ selectValue }}</span>
|
<div class="select-value">{{ selectValue }}<span class="placeholder">{{ placeholder }}</span></div>
|
||||||
<span class="icon-arrow">
|
<div class="icon-arrow">
|
||||||
<ArrowDown class="icon-select-arrow"/>
|
<ArrowDown class="icon-select-arrow"/>
|
||||||
</span>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="select-options-wrapper">
|
||||||
|
<ul>
|
||||||
|
<slot></slot>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
|
||||||
<slot></slot>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
|
default: '请选择'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
@ -40,13 +43,91 @@ export default {
|
|||||||
],
|
],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const selectValue = ref(props.modelValue)
|
const selectValue = ref(props.modelValue)
|
||||||
|
const isActive = ref(false)
|
||||||
|
function onClose(){
|
||||||
|
isActive.value = false
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
selectValue
|
selectValue, isActive,onClose
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="less">
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.select-options-wrapper {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.select-box-wrapper{
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 3px var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-box-wrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding: 3px 6px;
|
||||||
|
color: #000000d9;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #fff;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: var(--border-radius-middle);
|
||||||
|
transition: all .3s;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: var(--placeholder-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-value {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧图标
|
||||||
|
.icon-arrow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-options-wrapper {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
background-color: #fff;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 32px;
|
||||||
|
box-shadow: 0 0 7px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: var(--border-radius-middle);
|
||||||
|
|
||||||
|
ul, li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
line-height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
60
admin-fe/src/directive/click_outside.ts
Normal file
60
admin-fe/src/directive/click_outside.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import type {
|
||||||
|
ObjectDirective,
|
||||||
|
} from 'vue'
|
||||||
|
|
||||||
|
const nodeList = new Map<Element, Function>();
|
||||||
|
document.addEventListener('mousedown', (e: MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
// @ts-ignore
|
||||||
|
const paths = e.path as Element[];
|
||||||
|
// console.log(paths)
|
||||||
|
// 已经绑定过指令的元素
|
||||||
|
nodeList.forEach((fn, ele) => {
|
||||||
|
// 当前点击元素的数组
|
||||||
|
for (const c of paths) {
|
||||||
|
// 找到对应的元素 说明是点击了该元素或者他的子元素
|
||||||
|
if (c == ele) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 没有找到 说明在外面 可以出发事件
|
||||||
|
fn.call(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义指令
|
||||||
|
const clickOutside: ObjectDirective = {
|
||||||
|
// 在绑定元素的 attribute 前
|
||||||
|
// 或事件监听器应用前调用
|
||||||
|
created(el, binding, vnode, prevVnode) {
|
||||||
|
// 下面会介绍各个参数的细节
|
||||||
|
nodeList.set(el,binding.value)
|
||||||
|
// console.log(nodeList)
|
||||||
|
},
|
||||||
|
// 在元素被插入到 DOM 前调用
|
||||||
|
beforeMount(el, binding, vnode, prevVnode) {
|
||||||
|
},
|
||||||
|
// 在绑定元素的父组件
|
||||||
|
// 及他自己的所有子节点都挂载完成后调用
|
||||||
|
mounted(el, binding, vnode, prevVnode) {
|
||||||
|
},
|
||||||
|
// 绑定元素的父组件更新前调用
|
||||||
|
beforeUpdate(el, binding, vnode, prevVnode) {
|
||||||
|
},
|
||||||
|
// 在绑定元素的父组件
|
||||||
|
// 及他自己的所有子节点都更新后调用
|
||||||
|
updated(el, binding, vnode, prevVnode) {
|
||||||
|
},
|
||||||
|
// 绑定元素的父组件卸载前调用
|
||||||
|
beforeUnmount(el, binding, vnode, prevVnode) {
|
||||||
|
nodeList.delete(el)
|
||||||
|
// console.log('remove ', index, el)
|
||||||
|
// nodeList.splice(index, 1)// 移除要销毁的元素
|
||||||
|
// console.log(nodeList)
|
||||||
|
},
|
||||||
|
// 绑定元素的父组件卸载后调用
|
||||||
|
unmounted(el, binding, vnode, prevVnode) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default clickOutside
|
@ -6,6 +6,7 @@ import router from './router'
|
|||||||
import {httpConfig} from "./util/http";
|
import {httpConfig} from "./util/http";
|
||||||
import './assets/app.less'
|
import './assets/app.less'
|
||||||
import PInput from "./components/input";
|
import PInput from "./components/input";
|
||||||
|
import clickOutside from "./directive/click_outside";
|
||||||
|
|
||||||
httpConfig.baseURL = "http://localhost:8080"
|
httpConfig.baseURL = "http://localhost:8080"
|
||||||
|
|
||||||
@ -14,5 +15,6 @@ const app = createApp(App)
|
|||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.component('PInput',PInput)
|
app.component('PInput',PInput)
|
||||||
|
app.directive('click-outside',clickOutside)
|
||||||
// 将应用实例挂载到 模板中
|
// 将应用实例挂载到 模板中
|
||||||
app.mount('#vue-root-app')
|
app.mount('#vue-root-app')
|
@ -2,32 +2,52 @@ import {RouteRecordRaw} from "vue-router";
|
|||||||
import Home from '../views/admin/Home.vue'
|
import Home from '../views/admin/Home.vue'
|
||||||
import Login from '../views/Login.vue'
|
import Login from '../views/Login.vue'
|
||||||
import NotFound from '../views/NotFound.vue'
|
import NotFound from '../views/NotFound.vue'
|
||||||
|
import Test from '../views/Test.vue'
|
||||||
import AdminLayout from '../views/layout/AdminLayout.vue'
|
import AdminLayout from '../views/layout/AdminLayout.vue'
|
||||||
import UserIndex from '../views/user/index.vue'
|
import UserIndex from '../views/user/index.vue'
|
||||||
import GoodsIndex from '../views/goods/index.vue'
|
import GoodsIndex from '../views/goods/index.vue'
|
||||||
|
|
||||||
|
export const AdminRoutes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: 'home',
|
||||||
|
name: 'Home',
|
||||||
|
component: Home,
|
||||||
|
meta: {
|
||||||
|
title: 'Home'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'user',
|
||||||
|
name: 'UserIndex',
|
||||||
|
component: UserIndex,
|
||||||
|
meta: {
|
||||||
|
title: '用户管理'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'goods',
|
||||||
|
name: 'GoodsIndex',
|
||||||
|
component: GoodsIndex,
|
||||||
|
meta: {
|
||||||
|
title: '商品管理'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'test',
|
||||||
|
name: 'TestIndex',
|
||||||
|
component: Test,
|
||||||
|
meta: {
|
||||||
|
title: '测试页'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: AdminLayout,
|
component: AdminLayout,
|
||||||
redirect: '/home',
|
redirect: '/home',
|
||||||
children: [
|
children: AdminRoutes
|
||||||
{
|
|
||||||
path: 'home',
|
|
||||||
name: 'Home',
|
|
||||||
component: Home
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'user',
|
|
||||||
name: 'UserIndex',
|
|
||||||
component: UserIndex
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'goods',
|
|
||||||
name: 'GoodsIndex',
|
|
||||||
component: GoodsIndex
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
|
@ -1,43 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="demo-box">
|
<div class="demo-box">
|
||||||
<div class="form-item">
|
<div>
|
||||||
<PInput placeholder="测试21" v-model="data.a">
|
<div class="form-item">
|
||||||
<template #suffix>
|
<PInput placeholder="测试21" v-model="data.a">
|
||||||
<span>密码</span>
|
<template #suffix>
|
||||||
</template>
|
<span>密码</span>
|
||||||
</PInput>
|
</template>
|
||||||
</div>
|
</PInput>
|
||||||
<div class="form-item">
|
</div>
|
||||||
<PInput type="password" placeholder="测试12" v-model="data.b"/>
|
<div class="form-item">
|
||||||
</div>
|
<PInput type="password" placeholder="测试12" v-model="data.b"/>
|
||||||
<div class="form-item">
|
</div>
|
||||||
<PInput type="password" placeholder="测试12" v-model="data.b"/>
|
<div class="form-item">
|
||||||
</div>
|
<PInput type="password" placeholder="测试12" v-model="data.b"/>
|
||||||
<div>
|
</div>
|
||||||
{{ JSON.stringify(data) }}
|
<div>
|
||||||
</div>
|
{{ JSON.stringify(data) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
商品类型
|
||||||
|
<p-select>
|
||||||
|
<p-option value="1">普通商品</p-option>
|
||||||
|
<p-option class="selected" value="2">精选商品</p-option>
|
||||||
|
<p-option value="3">秒杀商品</p-option>
|
||||||
|
</p-select>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import PInput from "../components/input";
|
import PInput from "../components/input";
|
||||||
import {reactive} from "vue";
|
import {reactive} from "vue";
|
||||||
|
import PSelect from "../components/select/Select.vue";
|
||||||
|
import POption from "../components/select/Option.vue";
|
||||||
|
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
a: '1',
|
a: '1',
|
||||||
b: '2'
|
b: '2'
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.demo-box {
|
.demo-box {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
border: solid 1px #f00;
|
border: solid 1px #f00;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-item {
|
.form-item {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -13,9 +13,7 @@
|
|||||||
<div class="left-menu">
|
<div class="left-menu">
|
||||||
<div class="title">积分管理系统</div>
|
<div class="title">积分管理系统</div>
|
||||||
<div class="menu-list">
|
<div class="menu-list">
|
||||||
<router-link active-class="active-menu" to="/home">HOME</router-link>
|
<router-link v-for="r in AdminRoutes" active-class="active-menu" :to="'/' + r.path">{{ r.meta.title }}</router-link>
|
||||||
<router-link active-class="active-menu" to="/goods">商品管理</router-link>
|
|
||||||
<router-link active-class="active-menu" to="/user">用户管理</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
@ -30,6 +28,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {useUserStore} from "../../service/store";
|
import {useUserStore} from "../../service/store";
|
||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
|
import {AdminRoutes} from './../../router/routes'
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -15,6 +15,12 @@
|
|||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"types": [
|
"types": [
|
||||||
"unplugin-vue-define-options/macros-global"
|
"unplugin-vue-define-options/macros-global"
|
||||||
|
],
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"es5",
|
||||||
|
"scripthost",
|
||||||
|
"es2015.collection"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user