Browse Source

Merge pull request '增加定位提示' (#104) from zq-perSliderMenu into master

Reviewed-on: #104
pull/105/head
zhangqi 3 weeks ago
parent
commit
2db95b48c7
  1. 222
      src/components/Tooltip.vue
  2. 5
      src/main.js
  3. 33
      src/utils/tooltip.js
  4. 101
      src/views/agent/siteTemplate.vue
  5. 29
      src/views/elementGroups.vue

222
src/components/Tooltip.vue

@ -0,0 +1,222 @@
<template>
<transition name="fade">
<div v-if="visible" :class="['position-message', type, position]" id="qq" :style="positionStyle">
<i :class="iconClass"></i>
<span>{{ message }}</span>
</div>
</transition>
</template>
<script>
export default {
props: {
type: {
type: String,
default: 'info',
validator: val => ['info', 'success', 'error'].includes(val)
},
position: {
type: String,
default: 'top',
validator: val => ['top', 'bottom'].includes(val)
},
message: String,
target: null, // /DOM
offset: {
type: Number,
default: 10
}
},
data() {
return {
visible: false,
positionStyle: {}
}
},
computed: {
iconClass() {
return {
info: 'el-icon-info',
success: 'el-icon-success',
error: 'el-icon-error'
}[this.type]
}
},
methods: {
show() {
this.visible = true
this.$nextTick(() => {
this.calculatePosition(); // DOM
});
console.log('zoulma???????', this.visible);
return this //
},
hide() {
this.visible = false
},
calculatePosition() {
const targetEl = typeof this.target === 'string'
? document.querySelector(this.target)
: this.target?.$el || this.target;
if (!targetEl?.getBoundingClientRect) {
console.error('Invalid target:', this.target);
return;
}
const targetRect = targetEl.getBoundingClientRect();
const messageWidth = this.$el.offsetWidth;
const viewportWidth = window.innerWidth;
//
let left = targetRect.left + targetRect.width / 2 - messageWidth / 2;
//
if (left < 0) {
left = targetRect.left; //
} else if (left + messageWidth > viewportWidth) {
left = viewportWidth - messageWidth - (viewportWidth -targetRect.right); //
}
this.positionStyle = {
top: `${targetRect.bottom + window.scrollY + this.offset}px`,
left: `${left}px`,
right: 'auto', // right
};
},
},
}
</script>
<style scoped>
.position-message {
position: absolute;
min-width: 100px;
padding: 8px 12px;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
z-index: 9999;
display: flex;
align-items: center;
background: red;
}
.position-message i {
margin-right: 6px;
font-size: 16px;
}
.position-message.info {
background-color: #f4f4f5;
color: #909399;
border: 1px solid #e9e9eb;
}
.position-message.success {
background-color: #f0f9eb;
color: #67c23a;
border: 1px solid #e1f3d8;
}
.position-message.error {
background-color: #fef0f0;
color: #f56c6c;
border: 1px solid #fde2e2;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
<!-- <template>
<transition name="fade">
<div
v-if="visible"
:class="['position-message', type]"
:style="positionStyle"
>
<i :class="iconClass"></i>
<span>{{ message }}</span>
</div>
</transition>
</template>
<script>
export default {
computed: {
iconClass() {
return {
info: 'el-icon-info',
success: 'el-icon-success',
error: 'el-icon-error'
}[this.type]
}
},
props: {
type: {
type: String,
default: 'info',
validator: val => ['info', 'success', 'error'].includes(val)
},
position: {
type: String,
default: 'top',
validator: val => ['top', 'bottom'].includes(val)
},
message: String,
target: null, // /DOM
offset: {
type: Number,
default: 5
}
},
data() {
return {
visible: false,
positionStyle: {}
}
},
methods: {
hide() {
this.visible = false
},
calculatePosition() {
const targetEl = typeof this.target === 'string'
? document.querySelector(this.target)
: this.target?.$el || this.target;
if (!targetEl?.getBoundingClientRect) {
console.error('Invalid target:', this.target);
return;
}
const targetRect = targetEl.getBoundingClientRect();
const messageWidth = this.$el.offsetWidth;
if (this.position === 'bottom') {
this.positionStyle = {
top: `${targetRect.bottom + window.scrollY + this.offset}px`,
left: `${targetRect.left + targetRect.width / 2 - messageWidth / 2}px`,
};
} else {
//
this.positionStyle = {
top: `${targetRect.top + window.scrollY - this.offset - this.$el.offsetHeight}px`,
left: `${targetRect.left + targetRect.width / 2 - messageWidth / 2}px`,
};
}
},
show() {
this.visible = true;
this.$nextTick(this.calculatePosition);
return this;
},
},
};
</script> -->

5
src/main.js

@ -19,6 +19,7 @@ import clipboard from '@/utils/dirClipBoard';
import { modernCopyToClipboard } from '@/utils/clipboard';
//登陆
import { autoLoginByToken } from '@/utils/login'
import PositionMessage from './utils/tooltip'
function filterByPermission(data, targetPermission) {
return data.filter(item => {
@ -55,6 +56,10 @@ Vue.config.productionTip = false;
Vue.prototype.$http = request;
Vue.prototype.$token = ''
Vue.use(ElementUI);
Vue.use(PositionMessage)
Vue.prototype.$Message = GuipMessage
Vue.use(GuipMessage)
Vue.use(clipboard);

33
src/utils/tooltip.js

@ -0,0 +1,33 @@
import Vue from 'vue'
import Tooltip from '@/components/Tooltip.vue'
const PositionMessageConstructor = Vue.extend(Tooltip)
// 自动隐藏功能扩展
function showMessage(options) {
const instance = new PositionMessageConstructor({
propsData: options
}).$mount()
document.body.appendChild(instance.$el)
const showInstance = instance.show()
// 添加自动隐藏
if (options.duration !== 0) {
setTimeout(() => {
showInstance.hide()
setTimeout(() => {
document.body.removeChild(instance.$el)
}, 300) // 等待动画结束
}, options.duration || 2000)
}
return showInstance
}
export default {
install(Vue) {
Vue.prototype.$positionMessage = showMessage
}
}

101
src/views/agent/siteTemplate.vue

@ -5,9 +5,10 @@
<div class=" flex-common" id="">
<h3>模板选择</h3>
<div class="templeteImgs flex" v-if="templeteList.length">
<div class="templeteImgs-item column" v-for="(item, index) in templeteList" :key="item.tpl_id"
@click="chooseTemplate(item)">
<div class="outImg_tem" :class="tpl_id === item.id?'outImg_tem_active':''">
<div class="templeteImgs-item column" v-for="(item, index) in templeteList"
:key="item.name + index" :ref="el => { if (el) itemRefs[index] = el }"
@click="chooseTemplate(item, index)">
<div class="outImg_tem" :class="tpl_id === item.id ? 'outImg_tem_active' : ''">
<img :src="item.picture" alt="" preview class="tem_img">
</div>
<span class="look" @click.stop="previewImg(item, index)">预览</span>
@ -15,10 +16,11 @@
<p>{{ item.name }}</p>
</div>
<div class="templeteImgs-item column" v-if="tpl_customize" @click="choosePrivTemplate()">
<div class="outImg_tem" :class="is_public_tpl === 0?'outImg_tem_active':''">
<div class="outImg_tem" :class="is_public_tpl === 0 ? 'outImg_tem_active' : ''">
<img src="@/assets/site/tpl_customize.svg" alt="" class="tem_img">
</div>
<img src="@/assets/site/tem-active.svg" v-if="is_public_tpl === 0" class="tem-active" alt="">
<img src="@/assets/site/tem-active.svg" v-if="is_public_tpl === 0" class="tem-active"
alt="">
<p>自定义模板</p>
</div>
</div>
@ -37,13 +39,13 @@
</GuipButton>
<div slot="tip" class="el-upload__tip">浏览器标题前面的图片必须是ico格式尺寸32PX*32PX</div>
</el-upload>
<!-- <input type="file" accept=".ico" @change="handleAvatarChange" ref="fileInput"-->
<!-- style="display: none">-->
<!-- <GuipButton class="upload-button mt12" slot="trigger" type="ignore"-->
<!-- :btnstyle="{ width: '118px' }" @click="$refs.fileInput.click()">-->
<!-- <i class="bgImg"></i>选择文件-->
<!-- </GuipButton>-->
<!-- <p class="desc mt12">浏览器标题前面的图片必须是ico格式尺寸32PX</p>-->
<!-- <input type="file" accept=".ico" @change="handleAvatarChange" ref="fileInput"-->
<!-- style="display: none">-->
<!-- <GuipButton class="upload-button mt12" slot="trigger" type="ignore"-->
<!-- :btnstyle="{ width: '118px' }" @click="$refs.fileInput.click()">-->
<!-- <i class="bgImg"></i>选择文件-->
<!-- </GuipButton>-->
<!-- <p class="desc mt12">浏览器标题前面的图片必须是ico格式尺寸32PX</p>-->
</div>
<div class="uploadwrap mt24">
<label class="flex upload-title gap8">
@ -86,10 +88,13 @@
<el-upload class="upload-demo" :on-change="handleAvatarChange2" action="#" :multiple="false"
:limit="Number(1)" ref="avatorUpload2" :auto-upload="false" accept=".zip">
<div class="flex gap12">
<GuipButton class="upload-button" slot="trigger" type="ignore" :btnstyle="{ width: '118px' }">
<GuipButton class="upload-button" slot="trigger" type="ignore"
:btnstyle="{ width: '118px' }">
<i class="bgImg"></i><span v-if="!priv_down_url">选择文件</span><span v-else>重新上传</span>
</GuipButton>
<a v-if="priv_down_url" :href="priv_down_url" target="_blank" class="link flex gap10"><img src="@/assets/site/form_linkActive.svg" alt="">导出网页模板</a>
<a v-if="priv_down_url" :href="priv_down_url" target="_blank"
class="link flex gap10"><img src="@/assets/site/form_linkActive.svg"
alt="">导出网页模板</a>
</div>
<div slot="tip" class="el-upload__tip">根据开发文档的提示将内容压缩成zip包上传</div>
</el-upload>
@ -148,18 +153,19 @@ export default {
templeteList: [],
formData: new FormData(),
previewVisible: false,
currentImage: ''
currentImage: '',
itemRefs: {}
}
},
mounted() {
if(!this.$route.query.uid && !this.$route.query.site_type){
if (!this.$route.query.uid && !this.$route.query.site_type) {
this.$message.error('未知错误');
this.$router.push('/')
return false;
}
//
const siteTplInfo = JSON.parse(localStorage.getItem('site_tpl_info'))
if(!this.$route.query.uid && siteTplInfo) {
if (!this.$route.query.uid && siteTplInfo) {
this.tpl_id = siteTplInfo.site_tpl
this.pictureUrl = siteTplInfo.picture
this.previewUrl = siteTplInfo.preview
@ -173,13 +179,13 @@ export default {
//
get_site_tpl_list() {
let obj = {}
if(this.$route.query.site_type) obj.site_type = this.$route.query.site_type;
if(this.$route.query.uid) obj.uid = this.$route.query.uid;
if (this.$route.query.site_type) obj.site_type = this.$route.query.site_type;
if (this.$route.query.uid) obj.uid = this.$route.query.uid;
this.$http('POST', '/agentnew/ajax_get_site_tpl_list', obj).then(response => {
if(response.status) {
if (response.status) {
this.$nextTick(() => {
if(!this.tpl_id) {
if (!this.tpl_id) {
this.tpl_id = response.data.tpl_data.tpl_id
this.pictureUrl = response.data.tpl_data.picture
this.previewUrl = response.data.tpl_data.thumbnail
@ -197,17 +203,17 @@ export default {
console.error(error, 'error')
})
},
jumpCancle(){
jumpCancle() {
this.$router.go(-1)
},
jumpDoc(){
jumpDoc() {
window.open(this.$router.resolve({
path: '/customizeDoc',
}).href, '_blank')
},
update_site_tpl() {
//id
if(!this.$route.query.uid){
if (!this.$route.query.uid) {
let site_tpl_info = {
site_tpl: this.tpl_id,
site_tpl_name: this.tpl_name,
@ -225,7 +231,7 @@ export default {
this.formData.set('tpl_id', this.tpl_id)
this.formData.set('is_public_tpl', this.is_public_tpl)
this.$http('POST', '/agentnew/ajax_update_site_tpl', this.formData).then(response => {
if(response.status){
if (response.status) {
this.$router.go(-1)
return true
}
@ -235,15 +241,26 @@ export default {
})
return true;
},
chooseTemplate(item) {
chooseTemplate(item, index) {
// ref DOM
const targetEl = this.itemRefs[index]; // $refs
this.is_public_tpl = 1
this.tpl_id = item.id;
this.tpl_name = item.name;
this.pictureUrl = item.picture;
this.previewUrl = item.thumbnail;
this.$Message.info(item.introduce)
this.openDialog3(targetEl, 'top', item.introduce)
},
openDialog3(el, pos, introduce) {
// type
this.$positionMessage({
type: 'info',
message: introduce,
target: el, // DOM
position: pos || 'bottom' // 'bottom'
}) // 2
},
choosePrivTemplate(){
choosePrivTemplate() {
this.is_public_tpl = 0
this.tpl_id = ''
this.tpl_name = '';
@ -285,36 +302,42 @@ export default {
}
</script>
<style scoped lang="scss">
.pb100{
.pb100 {
padding-bottom: 100px;
}
.mb10{
.mb10 {
margin-bottom: 10px;
}
.link{
.link {
text-decoration: none;
color: #006AFF;
}
.add-info{
.add-info {
padding: 20px 14px;
letter-spacing: 0.08em;
::v-deep .prompt-desc{
::v-deep .prompt-desc {
color: #1E2226;
font-weight: bold;
}
}
.siteTem-wrap{
.siteTem-wrap {
padding: 12px 12px 0;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
.content{
.content {
flex: 1;
}
}
.register-btns {
position: fixed;
left: 0;
@ -390,9 +413,10 @@ export default {
text-align: left;
}
.preview-img{
.preview-img {
width: 100%;
}
.img-preview {
flex: 1;
overflow: hidden;
@ -430,7 +454,7 @@ export default {
border: 2px solid #E8E9EA;
}
.outImg_tem_active{
.outImg_tem_active {
border: 2px solid #006AFF;
}
@ -540,7 +564,8 @@ export default {
display: block;
transition: all .3s;
}
.outImg_tem{
.outImg_tem {
border: 2px solid #006AFF;
}
}

29
src/views/elementGroups.vue

@ -95,8 +95,8 @@
<GuipButton type="primary" @click="toggleAllSelection">全选按钮</GuipButton>
<GuipTable :tableData="tableData2" ref="multipleTable" @selectChange="handleSelectionChange"
:multiple="true" autoColumn="true" :loading="tableLoading" :border="true">
<GuipTable :tableData="[{payment:0}]" ref="multipleTable" @selectChange="handleSelectionChange"
:multiple="true" autoColumn="true" :loading="tableLoading">
<!-- <template slot="header"> -->
<el-table-column width="180" fixed="left" label="名称(固定左)"></el-table-column>
<el-table-column prop="created_at" label="时间" width="200">
@ -525,6 +525,10 @@
</div>
<div class="flex ele-item">
<label for="">一些功能集合</label>
<GuipButton type="primary" ref="button1" :btnstyle="{ width: '300px' }" @click="openDialog3('button1','bottom')">下方弹出消息提示
</GuipButton>
<GuipButton type="primary" ref="button2" :btnstyle="{ width: '300px' }" @click="openDialog3('button2','top')">上方弹出消息提示
</GuipButton>
<GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog">打开弹框标题巨左按钮居右
</GuipButton>
<GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog1">打开弹框标题巨中按钮居中
@ -532,6 +536,7 @@
<GuipButton type="primary" :btnstyle="{ width: '300px' }" @click="openDialog2">打开弹框-放弃原按钮自定义
</GuipButton>
<GuipButton type="primary" @click="openLoading" size="page">展示加载动画 2s</GuipButton>
</div>
<div class="flex ele-item">
@ -717,10 +722,10 @@ export default {
input3: '',
},
languageOptions1: [
{ label: 'JavaScript', value: 'js', selectedLabel: '333提拉米苏33', id: '1', name: '麻辣烫' },
{ label: 'Python', value: 'py', selectedLabel: '柠檬茶', id: '10', name: '易烊千玺' },
{ label: 'Java', value: 'java', disabled: true, selectedLabel: '榴莲大福', id: '11', name: '王源' }, //
{ label: 'Go', value: 'go', selectedLabel: '444222麻辣烫', id: '12', name: '王俊凯' },
{ label: 'JavaScript', value: 'js', selectedLabel: '333提拉米苏33', id: 0, name: 0 },
{ label: 'Python', value: 'py', selectedLabel: '柠檬茶', id: 1, name: '易烊千玺' },
{ label: 'Java', value: 'java', disabled: true, selectedLabel: '榴莲大福', id: 2, name: '王源' }, //
{ label: 'Go', value: 'go', selectedLabel: '444222麻辣烫', id: 3, name: '王俊凯' },
],
languageOptions: {
'20': '查重站',
@ -1115,6 +1120,7 @@ export default {
that.total = response.data.total
})
}).catch(error => {
this.tableLoading = false
console.error(error, 'error')
})
},
@ -1148,15 +1154,24 @@ export default {
},
// ---start
openDialog() {
console.log(this.switchValue1,'switchValue1');
this.dialogVisible = true;
},
openDialog1() {
this.dialogVisible1 = true;
},
openDialog2() {
this.dialogVisible2 = true;
},
openDialog3(el,pos){
// type
this.$positionMessage({
type: 'success',
message: '操作成功',
target: this.$refs[el], // DOM
position: pos // 'bottom'
}) // 3
},
//
handleConfirm() {
this.$message.success('点击了确认按钮');

Loading…
Cancel
Save