添加挪车码功能

This commit is contained in:
我若为王 2024-12-13 17:37:29 +08:00
parent 09707324e9
commit 4eba3afed8
5 changed files with 298 additions and 1 deletions

9
package-lock.json generated
View File

@ -13,6 +13,7 @@
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"crypto-es": "^2.1.0", "crypto-es": "^2.1.0",
"nuxt": "^3.14.1592", "nuxt": "^3.14.1592",
"qrcode.vue": "^3.6.0",
"vue": "latest", "vue": "latest",
"vue-router": "latest" "vue-router": "latest"
}, },
@ -7524,6 +7525,14 @@
"resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz",
"integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==" "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q=="
}, },
"node_modules/qrcode.vue": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.6.0.tgz",
"integrity": "sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==",
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",

View File

@ -17,6 +17,7 @@
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"crypto-es": "^2.1.0", "crypto-es": "^2.1.0",
"nuxt": "^3.14.1592", "nuxt": "^3.14.1592",
"qrcode.vue": "^3.6.0",
"vue": "latest", "vue": "latest",
"vue-router": "latest" "vue-router": "latest"
}, },

View File

@ -39,11 +39,18 @@ const tools = [
}, },
{ {
id: 2, id: 2,
title: '挪车码牌生成',
description: '生成挪车码牌,方便他人联系车主',
path: '/move-car-code',
available: true
},
{
id: 3,
title: '敬请期待', title: '敬请期待',
description: '更多工具正在开发中...', description: '更多工具正在开发中...',
path: '', path: '',
available: false available: false
} },
]; ];
</script> </script>

View File

@ -0,0 +1,85 @@
<template>
<head>
<title>挪车码牌生成</title>
</head>
<div class="p-4">
<div class="mx-auto w-full max-w-3xl bg-white shadow-lg rounded-lg p-8 mb-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold text-gray-800">挪车码牌生成</h1>
</div>
<a-form :model="form" :rules="rules" ref="formRef">
<a-form-item name="plateNumber" label="车牌号">
<a-input v-model:value="form.plateNumber" placeholder="请输入车牌号" />
</a-form-item>
<a-form-item name="phoneNumber" label="手机号">
<a-input v-model:value="form.phoneNumber" placeholder="请输入手机号" />
</a-form-item>
<a-form-item label="是否为新能源车">
<a-checkbox v-model:checked="form.newEnergy"></a-checkbox>
</a-form-item>
<a-form-item label="微信推送设置">
<a-input-group compact>
<a-input style="width: 50%" v-model:value="form.token" placeholder="请输入 Token" />
<a-input style="width: 50%" v-model:value="form.uid" placeholder="请输入 UID" />
</a-input-group>
<a-typography-link href="https://wxpusher.zjiecode.com/docs/#/" target="_blank">查看文档</a-typography-link>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleSubmit">生成挪车码牌</a-button>
</a-form-item>
</a-form>
</div>
<div v-if="generatedUrl" class="w-full max-w-3xl bg-white shadow-lg rounded-lg p-8 text-center">
<div class="prose prose-sm max-w-none">
<h2 class="text-xl font-bold mb-4">生成的挪车码牌链接</h2>
<a-typography-paragraph copyable>
<a :href="generatedUrl" target="_blank">{{ generatedUrl }}</a>
</a-typography-paragraph>
<div class="flex justify-center">
<qrcode-vue :value="generatedUrl" :size="200"></qrcode-vue>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { message } from 'ant-design-vue'
import QrcodeVue from 'qrcode.vue'
const form = ref({
plateNumber: '',
phoneNumber: '',
token: '',
uid: '',
newEnergy: false
})
const generatedUrl = ref('')
const formRef = ref(null)
const rules = {
plateNumber: [{ required: true, message: '请输入车牌号' }],
phoneNumber: [
{ required: true, message: '请输入手机号' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号' }
]
}
const handleSubmit = () => {
formRef.value.validate().then(() => {
const url = new URL(window.location.origin + '/move-car-display')
url.searchParams.append('plateNumber', form.value.plateNumber)
url.searchParams.append('phoneNumber', form.value.phoneNumber)
if (form.value.token) url.searchParams.append('token', form.value.token)
if (form.value.uid) url.searchParams.append('uid', form.value.uid)
if (form.value.newEnergy) url.searchParams.append('new', 'true')
generatedUrl.value = url.toString()
}).catch(error => {
message.warn('请填写完整信息')
})
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,195 @@
<template>
<head>
<title>快点挪车-信息</title>
</head>
<div v-if="loading" class="loading-overlay">
<div class="loading-container">
<div class="loading-wave">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div class="loading-text">加载中...</div>
</div>
</div>
<body>
<div class="container">
<div class="row">
<div class="col-12">
<div class="car" :class="{ 'new-energy': newEnergy }">
<span class="car-province">{{ plateNumber.charAt(0) }}</span><span class="car-letter">{{
plateNumber.charAt(1) }}</span><span class="car-dot"></span><span class="car-number">{{
plateNumber.slice(2) }}</span>
</div>
<div id="cardContainer">
<div class="card">
<div class="card-body">
<h1 class="card-title">挪车信息</h1>
<h2 class="card-text">临时停靠请多关照</h2>
<p class="card-text">车牌号: {{ plateNumber }}</p>
<p class="card-text">联系电话: {{ phoneNumber }}</p>
<button v-if="uid && token" type="button" class="btn btn-success"
@click="notifyOwner">通知车主</button>
<button type="button" class="btn btn-primary" @click="callNumber">一键呼叫</button>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</template>
<script setup>
import { message } from 'ant-design-vue'
definePageMeta({
layout: false,
})
import { ref, onMounted, onBeforeUnmount } from 'vue'
const plateNumber = ref('')
const phoneNumber = ref('')
const token = ref('')
const uid = ref('')
const newEnergy = ref(false)
const loading = ref(true)
const checkPageLoaded = () => {
if (document.readyState === 'complete') {
loading.value = false
}
}
onMounted(() => {
//
if (document.readyState === 'complete') {
loading.value = false
} else {
//
window.addEventListener('load', checkPageLoaded)
}
const urlParams = new URLSearchParams(window.location.search)
plateNumber.value = urlParams.get('plateNumber') || ''
phoneNumber.value = urlParams.get('phoneNumber') || ''
token.value = urlParams.get('token') || ''
uid.value = urlParams.get('uid') || ''
newEnergy.value = urlParams.get('new') === 'true'
})
onBeforeUnmount(() => {
//
window.removeEventListener('load', checkPageLoaded)
})
const notifyOwner = () => {
const currentTime = new Date().getTime()
const lastNotifyTime = localStorage.getItem('lastNotifyTime')
const timeDifference = (currentTime - lastNotifyTime) / 1000
if (lastNotifyTime && timeDifference < 60) {
message.warning('您已发送过通知请1分钟后再次尝试。')
return
}
fetch('https://wxpusher.zjiecode.com/api/send/message', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
appToken: token.value,
content: '您好,有人需要您挪车,请及时处理。',
contentType: 1,
uids: [uid.value],
}),
})
.then((response) => response.json())
.then((data) => {
if (data.code === 1000) {
message.success('通知已发送!')
localStorage.setItem('lastNotifyTime', currentTime)
} else {
message.error('通知发送失败,请稍后重试。')
}
})
.catch((error) => {
console.error('Error sending notification:', error)
message.error('通知发送出错,请检查网络连接。')
})
}
const callNumber = () => {
window.location.href = 'tel:' + phoneNumber.value
}
</script>
<style scoped>
.container {
width: 100%;
margin: 0 auto;
padding: 20px;
}
.row {
display: flex;
justify-content: center;
}
.card {
width: 100%;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card-body {
padding: 20px;
}
.card-title {
font-size: 24px;
margin-bottom: 10px;
}
.card-text {
font-size: 16px;
margin-bottom: 10px;
}
.btn {
display: inline-block;
padding: 10px 20px;
font-size: 16px;
border-radius: 4px;
text-align: center;
cursor: pointer;
margin-right: 10px;
}
.btn-success {
background-color: #38b000;
color: #fff;
}
.btn-primary {
background-color: #4b5cc4;
color: #fff;
}
.car {
color: #fff;
background-color: #4b5cc4;
border-radius: 6px;
border: 1px solid #fff;
text-align: center;
padding: 10px 0;
box-shadow: 0 0 2px 4px #4b5cc4;
width: 100%;
margin: 15px auto;
font-size: 40px;
letter-spacing: 5px;
text-transform: uppercase;
}
.car.new-energy {
background-color: #38b000;
box-shadow: 0 0 2px 4px #38b000;
}
</style>