添加组件

master
suixy 5 months ago
parent 66a269afaa
commit adedfb7a68

@ -6,7 +6,10 @@
<script>
export default {
name: 'App'
name: 'App',
mounted() {
document.title = "青岛海威物联科技有限公司";
}
}
</script>
@ -36,9 +39,6 @@ body {
//margin-top: 60px;
}
/deep/ .el-message {
z-index: 999;
}
</style>

@ -0,0 +1,398 @@
<template>
<div>
<div class="onlineConsultation" @click="consultation ">
<div class="btn">
<img :src="customerService" alt="" class="customerService">
<span class="text">在线咨询</span>
</div>
</div>
<div class="chat" v-if="isChat" ref="dragDiv">
<div class="topDrag" @mousedown="onMouseDown">
<i class="el-icon-close close" @click="isChat = false"></i>
</div>
<div class="content1" ref="chatContainer">
<template v-for="i in chatInfo">
<div v-if="i.type===0" class="message">
<div class="userInfo">{{ i.userName }} {{ formatTime(i.time) }}</div>
<div class="info">{{ i.content }}</div>
</div>
<div v-if="i.type===1" class="message1">
<div class="userInfo">{{ i.userName }} {{ formatTime(i.time) }}</div>
<div class="info">{{ i.content }}</div>
</div>
<div v-if="i.type===2" class="message">
<div class="userInfo">{{ i.userName }} {{ formatTime(i.time) }}</div>
<div class="info">
<div>
请选择问题类型
</div>
<el-button size="mini" @click="busy(1)"></el-button>
<el-button size="mini" @click="busy(2)"></el-button>
</div>
<div>
</div>
</div>
</template>
</div>
<div class="chatBox">
<el-input
type="textarea"
placeholder="请输入内容"
class="no-border-textarea"
@keydown.enter.native="sendChat"
v-model="textarea">
</el-input>
<el-button type="primary" size="mini" class="chatBtn" @click="sendChat">
</el-button>
</div>
</div>
</div>
</template>
<script>
import customerService from '@/assets/icon/customerService.png'
import {listHwWebDocument} from "@/api/hwWebDocument";
export default {
name: "Chat",
data() {
return {
customerService,
textarea: '',
isChat: false,
isDown: false,
offsetX: 0,
offsetY: 0,
chatInfo: [
{
type: 0,
content: '你好',
time: new Date().getTime(),
userName: '海威物联',
},
]
}
},
mounted() {
listHwWebDocument()
setTimeout(() => {
this.isChat = true;
this.$nextTick(() => {
this.scrollToBottom()
})
}, 5 * 1000)
},
methods: {
consultation() {
this.isChat = true;
this.$nextTick(() => {
this.scrollToBottom()
})
},
onMouseDown(e) {
this.isDown = true;
const rect = this.$refs.dragDiv.getBoundingClientRect();
this.offsetX = e.clientX - rect.left;
this.offsetY = e.clientY - rect.top;
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
},
onMouseMove(e) {
if (!this.isDown) return;
const dragDiv = this.$refs.dragDiv;
// left/top
let left = e.clientX - this.offsetX;
let top = e.clientY - this.offsetY;
// ()
if (left < 0) left = 0;
if (top < 0) top = 0;
if (left > window.innerWidth - dragDiv.offsetWidth) {
left = window.innerWidth - dragDiv.offsetWidth;
}
if (top > window.innerHeight - dragDiv.offsetHeight) {
top = window.innerHeight - dragDiv.offsetHeight;
}
// right/bottom
let right = window.innerWidth - left - dragDiv.offsetWidth;
let bottom = window.innerHeight - top - dragDiv.offsetHeight;
dragDiv.style.right = right + "px";
dragDiv.style.bottom = bottom + "px";
},
onMouseUp() {
this.isDown = false;
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
},
scrollToBottom() {
const container = this.$refs.chatContainer;
container.scrollTop = container.scrollHeight;
},
formatTime(date) {
const now = new Date();
const input = new Date(date);
const diffTime = now - input;
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const isToday = now.toDateString() === input.toDateString();
const yesterday = new Date();
yesterday.setDate(now.getDate() - 1);
const isYesterday = yesterday.toDateString() === input.toDateString();
const padZero = num => String(num).padStart(2, '0');
const timeStr = `${padZero(input.getHours())}:${padZero(input.getMinutes())}:${padZero(input.getSeconds())}`;
if (isToday) {
return `今天 ${timeStr}`;
} else if (isYesterday) {
return `昨天 ${timeStr}`;
} else {
return `${diffDays}天前`;
}
},
textMap() {
if (this.textarea) {
if (this.textarea.includes('转人工')) {
this.chatInfo.push({
type: 0,
content: '请稍后',
time: new Date().getTime(),
userName: '海威物联',
})
setTimeout(() => {
this.chatInfo.push({
type: 2,
time: new Date().getTime(),
userName: '海威物联',
})
}, 1000)
}
if (this.textarea.includes('工业') && this.textarea.toLowerCase().includes('rfid')) {
this.chatInfo.push({
type: 0,
content: '工业RFID信息',
time: new Date().getTime(),
userName: '海威物联',
})
}
if (this.textarea.includes('海威物联')) {
this.chatInfo.push({
type: 0,
content: '青岛海威物联科技有限公司是一家专注于工业物联网的高新技术企业主要聚焦于感知互联解决方案和轮胎全产业链RFID电子标签应用业务涵盖轮胎生产、仓储物流、销售跟踪、车队管理及翻新等环节的数字化追踪与管理。公司拥有多项国内外发明专利参与制定了多项轮胎用RFID电子标签的国际和国家标准具备较强的自主研发与技术创新能力。凭借在细分领域的领先地位和标准制定优势海威物联已获得“国家级专精特新小巨人企业”、国家高新技术企业、青岛市雏鹰企业等多项资质与荣誉。',
time: new Date().getTime(),
userName: '海威物联',
})
}
}
},
sendChat(e) {
if (e.isComposing) {
return;
}
if (this.textarea === '') {
return
}
this.chatInfo.push({
type: 1,
content: this.textarea,
time: new Date().getTime(),
userName: '我',
})
this.textMap()
this.textarea = ''
this.$nextTick(() => {
this.scrollToBottom()
})
},
busy(e) {
if (e === 1) {
this.chatInfo.push({
type: 0,
content: '请稍后',
time: new Date().getTime(),
userName: '海威物联',
})
setTimeout(() => {
this.chatInfo.push({
type: 0,
content: '目前咨询人数过多如有疑问请致电xxxx',
time: new Date().getTime(),
userName: '海威物联',
})
this.$nextTick(() => {
this.scrollToBottom()
})
}, 1000)
}
if (e === 2) {
this.chatInfo.push({
type: 0,
content: '请稍后',
time: new Date().getTime(),
userName: '海威物联',
})
setTimeout(() => {
this.chatInfo.push({
type: 0,
content: '目前咨询人数过多如有疑问请致电xxxx',
time: new Date().getTime(),
userName: '海威物联',
})
this.$nextTick(() => {
this.scrollToBottom()
})
}, 1000)
}
this.$nextTick(() => {
this.scrollToBottom()
})
}
}
}
</script>
<style scoped lang="less">
.onlineConsultation {
position: fixed;
right: 5vw;
bottom: 0;
width: 150px;
height: 50px;
background-color: #41B5EA;
border-radius: 5px;
cursor: pointer;
z-index: 99;
.customerService {
position: absolute;
bottom: 0;
left: 0;
width: 70px;
}
.text {
position: absolute;
bottom: 0;
left: 70px;
width: 80px;
line-height: 50px;
font-size: 16px;
color: #fff;
}
}
.chat {
z-index: 999;
position: fixed;
bottom: 5vw;
right: 5vw;
width: 450px;
height: 450px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: #fff;
overflow: hidden;
box-shadow: -4px 4px 8px rgba(0, 0, 0, 0.1),
4px 4px 8px rgba(0, 0, 0, 0.1),
0 4px 8px rgba(0, 0, 0, 0.1);
.topDrag {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 50px;
background-color: #41B5EA;
cursor: move;
.close {
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
color: #fff;
font-size: 20px;
cursor: pointer;
}
}
.content1 {
position: absolute;
top: 50px;
height: 300px;
width: 100%;
overflow: auto;
.message {
width: 80%;
margin: 10px 0 10px 15px;
text-align: left;
.userInfo {
font-size: 12px;
color: #0008
}
.info {
max-width: 80%;
display: inline-block;
background-color: #EDEFF5;
padding: 5px;
border-radius: 5px;
border-top-left-radius: 0;
}
}
.message1 {
width: calc(100% - 20px);
margin: 10px 0 10px 5px;
text-align: right;
overflow: hidden;
.userInfo {
font-size: 12px;
color: #0008
}
.info {
text-align: left;
max-width: 80%;
float: right;
background-color: #EDEFF5;
padding: 5px;
border-radius: 5px 0px 5px 5px;
}
}
}
.chatBox {
position: absolute;
top: 350px;
height: 88px;
width: 100%;
margin-top: 12px;
border-top: 2px solid #2222;
.no-border-textarea /deep/ .el-textarea__inner {
border: none;
box-shadow: none;
resize: none;
outline: none;
}
.chatBtn {
position: absolute;
bottom: 5px;
right: 10px;
}
}
}
</style>

@ -28,51 +28,21 @@
<div style=" color: #fff">top</div>
</div>
</div>
<div class="onlineConsultation" @click="consultation ">
<div class="btn">
<img :src="customerService" alt="" class="customerService">
<span class="text">在线咨询</span>
</div>
</div>
<div class="chat" v-if="isChat" ref="dragDiv">
<div class="topDrag" @mousedown="onMouseDown">
<i class="el-icon-close close" @click="isChat = false"></i>
</div>
<div class="content1" ref="chatContainer">
<template v-for="i in chatInfo">
<div v-if="i.type===0" class="message">
<div class="userInfo">{{ i.userName }} {{ formatTime(i.time) }}</div>
<div class="info">{{ i.content }}</div>
</div>
<div v-if="i.type===1" class="message1">
<div class="userInfo">{{ i.userName }} {{ formatTime(i.time) }}</div>
<div class="info">{{ i.content }}</div>
</div>
</template>
</div>
<div class="chatBox">
<el-input
type="textarea"
placeholder="请输入内容"
class="no-border-textarea"
v-model="textarea">
</el-input>
<el-button type="primary" size="mini" class="chatBtn" @click="sendChat"></el-button>
</div>
</div>
<Chat/>
</div>
</template>
<script>
import customerService from '@/assets/icon/customerService.png'
import qrcode from '@/assets/icon/QRCode.png'
import Menu from '@/components/menu/index'
import Chat from "@/components/chat/index.vue";
import logo from '@/assets/logo.png'
export default {
name: 'Index',
components: {
Menu
Menu,
Chat
},
methods: {
toIndex(){
@ -84,140 +54,13 @@ export default {
toService() {
this.$router.push('/serviceSupport')
},
consultation() {
this.isChat = true;
this.$nextTick(() => {
this.scrollToBottom()
})
},
onMouseDown(e) {
this.isDown = true;
const rect = this.$refs.dragDiv.getBoundingClientRect();
this.offsetX = e.clientX - rect.left;
this.offsetY = e.clientY - rect.top;
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
},
onMouseMove(e) {
if (!this.isDown) return;
const dragDiv = this.$refs.dragDiv;
// left/top
let left = e.clientX - this.offsetX;
let top = e.clientY - this.offsetY;
// ()
if (left < 0) left = 0;
if (top < 0) top = 0;
if (left > window.innerWidth - dragDiv.offsetWidth) {
left = window.innerWidth - dragDiv.offsetWidth;
}
if (top > window.innerHeight - dragDiv.offsetHeight) {
top = window.innerHeight - dragDiv.offsetHeight;
}
// right/bottom
let right = window.innerWidth - left - dragDiv.offsetWidth;
let bottom = window.innerHeight - top - dragDiv.offsetHeight;
dragDiv.style.right = right + "px";
dragDiv.style.bottom = bottom + "px";
},
onMouseUp() {
this.isDown = false;
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
},
formatTime(date) {
const now = new Date();
const input = new Date(date);
const diffTime = now - input;
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const isToday = now.toDateString() === input.toDateString();
const yesterday = new Date();
yesterday.setDate(now.getDate() - 1);
const isYesterday = yesterday.toDateString() === input.toDateString();
const padZero = num => String(num).padStart(2, '0');
const timeStr = `${padZero(input.getHours())}:${padZero(input.getMinutes())}:${padZero(input.getSeconds())}`;
if (isToday) {
return `今天 ${timeStr}`;
} else if (isYesterday) {
return `昨天 ${timeStr}`;
} else {
return `${diffDays}天前`;
}
},
scrollToBottom() {
const container = this.$refs.chatContainer;
container.scrollTop = container.scrollHeight;
},
sendChat() {
if (this.textarea === '') {
return
}
this.chatInfo.push({
type: 1,
content: this.textarea,
time: new Date().getTime(),
userName: '我',
})
this.textarea = ''
this.$nextTick(() => {
this.scrollToBottom()
})
}
},
mounted() {
setTimeout(() => {
this.isChat = true;
this.$nextTick(() => {
this.scrollToBottom()
})
}, 5 * 1000)
},
data() {
return {
textarea: '',
logo,
qrcode,
customerService,
isChat: false,
isDown: false,
offsetX: 0,
offsetY: 0,
chatInfo: [
{
type: 0,
content: '你好',
time: new Date().getTime(),
userName: '海威物联',
},
{
type: 0,
content: '你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好',
time: new Date().getTime(),
userName: '海威物联',
},
{
type: 1,
content: '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试',
time: new Date().getTime(),
userName: '我',
},
{
type: 0,
content: '你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好你好',
time: new Date().getTime(),
userName: '海威物联',
},
]
}
}
}
@ -346,136 +189,4 @@ export default {
}
}
.onlineConsultation {
position: fixed;
right: 5vw;
bottom: 0;
width: 150px;
height: 50px;
background-color: #41B5EA;
border-radius: 5px;
cursor: pointer;
z-index: 99;
.customerService {
position: absolute;
bottom: 0;
left: 0;
width: 70px;
}
.text {
position: absolute;
bottom: 0;
left: 70px;
width: 80px;
line-height: 50px;
font-size: 16px;
color: #fff;
}
}
.chat {
z-index: 999;
position: fixed;
bottom: 5vw;
right: 5vw;
width: 450px;
height: 450px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
background-color: #fff;
overflow: hidden;
box-shadow: -4px 4px 8px rgba(0, 0, 0, 0.1),
4px 4px 8px rgba(0, 0, 0, 0.1),
0 4px 8px rgba(0, 0, 0, 0.1);
.topDrag {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 50px;
background-color: #41B5EA;
cursor: move;
.close {
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
color: #fff;
font-size: 20px;
cursor: pointer;
}
}
.content1 {
position: absolute;
top: 50px;
height: 300px;
overflow: auto;
.message {
width: 80%;
margin: 10px 0 10px 15px;
text-align: left;
.userInfo {
font-size: 12px;
color: #0008
}
.info {
background-color: #EDEFF5;
padding: 5px;
border-radius: 5px;
border-top-left-radius: 0;
}
}
.message1 {
width: calc(100% - 20px);
margin: 10px 0 10px 5px;
text-align: right;
overflow: hidden;
.userInfo {
font-size: 12px;
color: #0008
}
.info {
text-align: left;
width: 80%;
float: right;
background-color: #EDEFF5;
padding: 5px;
border-radius: 5px 0px 5px 5px;
}
}
}
.chatBox {
position: absolute;
top: 350px;
height: 88px;
width: 100%;
margin-top: 12px;
.no-border-textarea /deep/ .el-textarea__inner {
border: none;
box-shadow: none;
resize: none;
outline: none;
}
.chatBtn {
position: absolute;
bottom: 5px;
right: 10px;
}
}
}
</style>

@ -5,7 +5,9 @@ import Layout from '@/layout/index.vue'
Vue.use(Router)
export default new Router({
routes: [
routes: [{
path: '/a', component: () => import('@/views/a/index.vue'),
},
{
path: '/editor', component: () => import('@/views/editPage/index.vue'),
},

@ -0,0 +1,31 @@
<template>
<div class="browser-warning">
<i class="fas fa-exclamation-triangle"></i>
<div>
<strong>HTTP协议限制</strong> 当前页面使用HTTP协议唤起微信可能受限建议使用HTTPS协议提高成功率
</div>
</div>
</template>
<script>
export default {
name: 'BrowserWarning'
}
</script>
<style scoped>
.browser-warning {
background: #ff6b6b;
color: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
align-items: center;
}
.browser-warning i {
margin-right: 10px;
font-size: 20px;
}
</style>

@ -0,0 +1,139 @@
<template>
<div class="card">
<div class="card-title">
<i class="fas fa-users"></i>
推荐好友
</div>
<div class="contact-list">
<div class="contact-item" v-for="contact in contacts" :key="contact.id">
<div class="avatar">{{ contact.avatar }}</div>
<div class="contact-info">
<div class="contact-name">{{ contact.name }}</div>
<div class="contact-id">微信号: {{ contact.wechatId }}</div>
</div>
<button class="add-btn" @click="$emit('add-contact', contact)" :disabled="contact.adding">
<span class="loading" v-if="contact.adding"></span>
{{ contact.adding ? '添加中...' : '添加' }}
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ContactList',
props: {
contacts: {
type: Array,
default: () => []
}
}
}
</script>
<style scoped>
.card {
background: #1f1f1f;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.card-title {
font-size: 18px;
margin-bottom: 15px;
color: #07C160;
display: flex;
align-items: center;
}
.card-title i {
margin-right: 10px;
}
.contact-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.contact-item {
display: flex;
align-items: center;
padding: 15px;
background: #333;
border-radius: 8px;
transition: transform 0.2s;
}
.contact-item:hover {
transform: translateY(-2px);
background: #3a3a3a;
}
.avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background: #07C160;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
color: white;
font-size: 20px;
font-weight: bold;
}
.contact-info {
flex: 1;
}
.contact-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 5px;
}
.contact-id {
font-size: 14px;
color: #aaa;
}
.add-btn {
background: #07C160;
color: white;
border: none;
padding: 8px 15px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: background 0.3s;
}
.add-btn:hover {
background: #06ae56;
}
.add-btn:disabled {
background: #555;
cursor: not-allowed;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
margin-right: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>

@ -0,0 +1,103 @@
<template>
<div class="card">
<div class="card-title">
<i class="fas fa-qrcode"></i>
我的二维码
</div>
<div class="qr-section">
<div class="qr-code">
<svg width="130" height="130" viewBox="0 0 130 130">
<rect width="130" height="130" fill="white"/>
<rect x="10" y="10" width="25" height="25" fill="black"/>
<rect x="95" y="10" width="25" height="25" fill="black"/>
<rect x="10" y="95" width="25" height="25" fill="black"/>
<rect x="45" y="45" width="10" height="10" fill="black"/>
<rect x="75" y="45" width="10" height="10" fill="black"/>
<rect x="45" y="75" width="10" height="10" fill="black"/>
<rect x="75" y="75" width="10" height="10" fill="black"/>
</svg>
</div>
<div class="qr-text">
<p>扫描二维码添加我为好友</p>
<p>打开手机微信点击右上角"+"选择"扫一扫"扫描上方二维码即可添加好友</p>
<p v-if="!isHttps" class="info">HTTP/HTTPS100%</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'QrCodeSection',
props: {
isHttps: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped>
.card {
background: #1f1f1f;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.card-title {
font-size: 18px;
margin-bottom: 15px;
color: #07C160;
display: flex;
align-items: center;
}
.card-title i {
margin-right: 10px;
}
.qr-section {
display: flex;
align-items: center;
gap: 20px;
margin-top: 20px;
}
.qr-code {
width: 150px;
height: 150px;
background: white;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
padding: 10px;
}
.qr-text {
flex: 1;
}
.qr-text p {
margin-bottom: 10px;
line-height: 1.5;
}
.info {
background: #cce7ff;
color: #004085;
padding: 8px;
border-radius: 4px;
}
@media (max-width: 768px) {
.qr-section {
flex-direction: column;
text-align: center;
}
}
</style>

@ -0,0 +1,116 @@
<template>
<div class="card">
<div class="card-title">
<i class="fas fa-search"></i>
搜索添加好友
</div>
<div class="search-box">
<input
type="text"
class="search-input"
:value="value"
@input="$emit('input', $event.target.value)"
placeholder="请输入微信号、手机号或昵称"
@keyup.enter="$emit('search')"
>
<button class="search-btn" @click="$emit('search')" :disabled="searching">
<span class="loading" v-if="searching"></span>
{{ searching ? '搜索中...' : '搜索' }}
</button>
</div>
<p>通过微信号手机号或昵称搜索添加好友</p>
</div>
</template>
<script>
export default {
name: 'SearchBox',
props: {
value: {
type: String,
default: ''
},
searching: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped>
.card {
background: #1f1f1f;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.card-title {
font-size: 18px;
margin-bottom: 15px;
color: #07C160;
display: flex;
align-items: center;
}
.card-title i {
margin-right: 10px;
}
.search-box {
display: flex;
margin-bottom: 20px;
}
.search-input {
flex: 1;
padding: 12px 15px;
background: #333;
border: 1px solid #444;
border-radius: 8px 0 0 8px;
color: #e0e0e0;
font-size: 16px;
outline: none;
}
.search-input:focus {
border-color: #07C160;
}
.search-btn {
background: #07C160;
color: white;
border: none;
padding: 0 20px;
border-radius: 0 8px 8px 0;
cursor: pointer;
font-weight: 600;
transition: background 0.3s;
}
.search-btn:hover {
background: #06ae56;
}
.search-btn:disabled {
background: #555;
cursor: not-allowed;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
margin-right: 10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>

@ -0,0 +1,46 @@
<template>
<div class="status-message" :class="type">
{{ message }}
</div>
</template>
<script>
export default {
name: 'StatusMessage',
props: {
message: {
type: String,
required: true
},
type: {
type: String,
default: 'info',
validator: (value) => ['success', 'error', 'info'].includes(value)
}
}
}
</script>
<style scoped>
.status-message {
padding: 10px;
border-radius: 5px;
margin-top: 10px;
text-align: center;
}
.success {
background: #d4edda;
color: #155724;
}
.error {
background: #f8d7da;
color: #721c24;
}
.info {
background: #cce7ff;
color: #004085;
}
</style>

@ -0,0 +1,289 @@
<template>
<div class="wechat-add-friend">
<div class="container">
<div class="header">
<h1><i class="fab fa-weixin"></i> Vue 2 微信添加好友</h1>
<p>使用Vue 2组件实现的微信H5添加好友页面</p>
<div class="protocol-info">
<span>当前协议:</span>
<span class="protocol-tag" :class="protocolClass">{{ currentProtocol }}</span>
<span>唤起成功率: {{ successRate }}</span>
</div>
</div>
<div class="content">
<browser-warning v-if="!isHttps"/>
<search-box
v-model="searchText"
:searching="searching"
@search="handleSearch"
/>
<qr-code-section :is-https="isHttps"/>
<contact-list
:contacts="contacts"
@add-contact="addContact"
/>
<status-message
v-if="statusMessage"
:message="statusMessage"
:type="statusType"
/>
<div class="action-buttons">
<button class="action-btn primary-btn" @click="openWechat">
<i class="fab fa-weixin"></i>
打开微信添加好友
</button>
<button class="action-btn secondary-btn" @click="showTips">
<i class="fas fa-info-circle"></i>
使用提示
</button>
</div>
<div class="footer">
<p>© 2023 微信添加好友 | 基于Vue 2组件开发</p>
<p>当前环境: {{ userAgent }}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import BrowserWarning from './BrowserWarning.vue'
import SearchBox from './SearchBox.vue'
import QrCodeSection from './QrCodeSection.vue'
import ContactList from './ContactList.vue'
import StatusMessage from './StatusMessage.vue'
export default {
name: 'WechatAddFriend',
components: {
BrowserWarning,
SearchBox,
QrCodeSection,
ContactList,
StatusMessage
},
data() {
return {
searchText: '',
searching: false,
statusMessage: '',
statusType: '',
userAgent: navigator.userAgent,
isHttps: window.location.protocol === 'https:',
contacts: [
{id: 1, name: '张三', wechatId: 'zhangsan2023', avatar: '张', adding: false},
{id: 2, name: '李四', wechatId: 'lisi_tech', avatar: '李', adding: false},
{id: 3, name: '王五', wechatId: 'wangwu888', avatar: '王', adding: false}
]
}
},
computed: {
currentProtocol() {
return this.isHttps ? 'HTTPS' : 'HTTP'
},
protocolClass() {
return this.isHttps ? 'protocol-https' : 'protocol-http'
},
successRate() {
return this.isHttps ? '较高' : '较低'
}
},
methods: {
showStatus(message, type = 'info') {
this.statusMessage = message
this.statusType = type
setTimeout(() => {
this.statusMessage = ''
this.statusType = ''
}, 5000)
},
handleSearch() {
if (!this.searchText.trim()) {
this.showStatus('请输入搜索内容', 'error')
return
}
this.searching = true
this.showStatus(`正在搜索: ${this.searchText}`, 'info')
setTimeout(() => {
this.searching = false
this.openWechatDirect()
this.showStatus(`搜索完成,正在尝试唤起微信...`, 'success')
}, 1500)
},
addContact(contact) {
contact.adding = true
this.showStatus(`正在向 ${contact.name} 发送好友请求...`, 'info')
setTimeout(() => {
contact.adding = false
this.openWechatDirect()
this.showStatus(`已发送好友请求给 ${contact.name},请在新打开的微信中确认`, 'success')
}, 2000)
},
openWechatDirect() {
const wechatScheme = 'weixin://'
window.location.href = wechatScheme
setTimeout(() => {
if (!document.hidden) {
if (this.isHttps) {
this.showStatus('唤起失败:请确保已安装微信桌面版', 'error')
} else {
this.showStatus('唤起失败HTTP环境下限制较多建议使用二维码方案', 'error')
}
}
}, 1000)
},
openWechat() {
this.showStatus('正在尝试唤起微信...', 'info')
this.openWechatDirect()
setTimeout(() => {
if (!document.hidden) {
this.showStatus('自动唤起失败,请尝试二维码添加或手动打开微信', 'error')
}
}, 1500)
},
showTips() {
const tips = this.isHttps ?
"HTTPS环境提示\n\n• 唤起成功率较高\n• 浏览器限制较少\n• 用户体验良好" :
"HTTP环境提示\n\n• 唤起成功率较低\n• 浏览器可能阻止自动唤起\n• 建议使用二维码方案"
alert(tips)
}
},
mounted() {
console.log('Vue 2 微信添加好友组件已加载')
}
}
</script>
<style scoped>
.wechat-add-friend {
width: 100%;
max-width: 900px;
margin: 0 auto;
}
.container {
background: #2d2d2d;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.header {
background: #07C160;
color: white;
padding: 20px;
text-align: center;
}
.header h1 {
font-size: 24px;
margin-bottom: 10px;
}
.header p {
font-size: 16px;
opacity: 0.9;
}
.content {
padding: 30px;
}
.action-buttons {
display: flex;
gap: 15px;
margin-top: 20px;
}
.action-btn {
flex: 1;
padding: 12px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s;
}
.primary-btn {
background: #07C160;
color: white;
}
.primary-btn:hover {
background: #06ae56;
transform: translateY(-2px);
}
.secondary-btn {
background: #444;
color: #e0e0e0;
}
.secondary-btn:hover {
background: #555;
transform: translateY(-2px);
}
.footer {
margin-top: 30px;
text-align: center;
font-size: 14px;
color: #888;
padding-top: 20px;
border-top: 1px solid #444;
}
.protocol-info {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-top: 15px;
font-size: 14px;
}
.protocol-tag {
padding: 4px 8px;
border-radius: 4px;
font-weight: bold;
}
.protocol-https {
background: #07C160;
color: white;
}
.protocol-http {
background: #ff6b6b;
color: white;
}
@media (max-width: 768px) {
.content {
padding: 20px;
}
.action-buttons {
flex-direction: column;
}
}
</style>

@ -52,31 +52,35 @@ export default {
}
},
mounted() {
if (this.$route.query.id) {
this.tabsActive = parseFloat(this.$route.query.id)
}
this.getData()
},
watch: {
'$route'() {
this.tabsActive = parseFloat(this.$route.query.id)
// this.getData()
let id = this.$route.query.id
if (id === '11') {
this.tabsActive = 0
}
if (id === '12') {
this.tabsActive = 1
}
if (id === '13') {
this.tabsActive = 2
}
if (id === '14') {
this.tabsActive = 3
}
if (id === '15') {
this.tabsActive = 4
}
if (id === '16') {
this.tabsActive = 5
}
this.itemId = id
// let id = this.$route.query.id
// if (id === '11') {
// this.tabsActive = 0
// }
// if (id === '12') {
// this.tabsActive = 1
// }
// if (id === '13') {
// this.tabsActive = 2
// }
// if (id === '14') {
// this.tabsActive = 3
// }
// if (id === '15') {
// this.tabsActive = 4
// }
// if (id === '16') {
// this.tabsActive = 5
// }
// this.itemId = id
}
},
methods: {
@ -87,7 +91,6 @@ export default {
selectMenuTree().then(e => {
let tabsData = e.data.find(v => v.webMenuId === 7).children
this.tabsData = tabsData
console.log(tabsData)
this.tabsActive = tabsData[0].webMenuId
})
},

@ -3,7 +3,7 @@ module.exports = defineConfig({
transpileDependencies: true, runtimeCompiler: true,
devServer: {
host: "0.0.0.0", port: 8899, open: true, proxy: {
host: "0.0.0.0", port: 8899, open: true, https: true, proxy: {
// detail: https://cli.vuejs.org/config/#devserver-proxy
'/dev-api': {
// target: `http://175.27.215.92:8899/prod-api`,

Loading…
Cancel
Save