first commit
This commit is contained in:
508
src/pages/index.vue
Normal file
508
src/pages/index.vue
Normal file
@@ -0,0 +1,508 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { getInquireCategoryConfig, getInquiryItemIconUrl } from '@/config/inquireCategories'
|
||||
import { toolboxCategories, getCategoryHotTools } from '@/config/toolboxRegistry'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '全能查',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
interface CaseItem {
|
||||
id: string
|
||||
tag: string
|
||||
vin: string
|
||||
model: string
|
||||
}
|
||||
|
||||
interface ReviewItem {
|
||||
id: string
|
||||
name: string
|
||||
content: string
|
||||
}
|
||||
|
||||
const caseList = ref<CaseItem[]>([
|
||||
{
|
||||
id: '1',
|
||||
tag: '新能源电池查询',
|
||||
vin: 'LBV8********0981',
|
||||
model: '某品牌新能源车型',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
tag: '里程异常检测',
|
||||
vin: 'LSGN********3389',
|
||||
model: '凯迪拉克 XT5',
|
||||
},
|
||||
])
|
||||
|
||||
const vehicleItems = computed(() => getInquireCategoryConfig('vehicle')?.items ?? [])
|
||||
|
||||
const reviewList = ref<ReviewItem[]>([
|
||||
{
|
||||
id: '1',
|
||||
name: '陈先生',
|
||||
content: '查完车况再成交,心里更踏实,避免了重大事故车。',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '周女士',
|
||||
content: '报告内容很详细,维保、出险一目了然,值得推荐。',
|
||||
},
|
||||
])
|
||||
|
||||
function reviewInitial(name: string) {
|
||||
return name.slice(0, 1) || '?'
|
||||
}
|
||||
|
||||
function goInquireFeature(feature: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/inquire/index?feature=${encodeURIComponent(feature)}`,
|
||||
})
|
||||
}
|
||||
|
||||
/** 左侧大图:车辆出险详版查询 */
|
||||
function goVinClaim() {
|
||||
goInquireFeature('toc_VehicleClaimDetail')
|
||||
}
|
||||
|
||||
/** 右上小图:车辆维保详细版查询 */
|
||||
function goVinMaintain() {
|
||||
goInquireFeature('toc_VehicleMaintenanceDetail')
|
||||
}
|
||||
|
||||
function goEvHealth() {
|
||||
uni.showToast({ title: '功能开发中', icon: 'none' })
|
||||
}
|
||||
|
||||
function goToolboxItem(key: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/toolbox/query?key=${encodeURIComponent(key)}`,
|
||||
})
|
||||
}
|
||||
|
||||
function goCategory(categoryKey: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/toolbox/category?category=${encodeURIComponent(categoryKey)}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<scroll-view scroll-y class="scrollarea">
|
||||
<view class="page">
|
||||
<view class="banner">
|
||||
<image
|
||||
class="banner-img"
|
||||
src="/static/home/images/Banner.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="main-query">
|
||||
<view class="main-left card card-img-wrap" @tap="goVinClaim">
|
||||
<image
|
||||
class="main-left-img"
|
||||
src="/static/home/images/VIN.png"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</view>
|
||||
<view class="main-right">
|
||||
<view class="card card-img-wrap small-card-img" @tap="goVinMaintain">
|
||||
<image
|
||||
class="main-right-img"
|
||||
src="/static/home/images/VIN2.png"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="card card-img-wrap small-card-img" @tap="goEvHealth">
|
||||
<image
|
||||
class="main-right-img"
|
||||
src="/static/home/images/VIN3.png"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-for="cat in toolboxCategories"
|
||||
:key="cat.key"
|
||||
class="card"
|
||||
>
|
||||
<view class="card-header">
|
||||
<view class="card-header-left">
|
||||
<view class="cat-badge" :style="{ background: `${cat.color}15` }">
|
||||
<view :class="['cat-badge-icon', cat.icon]" :style="{ color: cat.color }" />
|
||||
</view>
|
||||
<text class="card-title">{{ cat.name }}</text>
|
||||
</view>
|
||||
<text class="card-more-link" @tap="goCategory(cat.key)">
|
||||
查看更多
|
||||
</text>
|
||||
</view>
|
||||
<view class="inq-grid">
|
||||
<view
|
||||
v-for="item in getCategoryHotTools(cat.key)"
|
||||
:key="item.key"
|
||||
class="inq-cell"
|
||||
@tap="goToolboxItem(item.key)"
|
||||
>
|
||||
<view class="inq-icon-custom" :style="{ color: cat.color }">
|
||||
<view :class="item.icon" />
|
||||
</view>
|
||||
<text class="inq-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">
|
||||
车辆查询
|
||||
</text>
|
||||
<text class="card-sub">
|
||||
请选择查询类型
|
||||
</text>
|
||||
</view>
|
||||
<view class="inq-grid">
|
||||
<view
|
||||
v-for="item in vehicleItems"
|
||||
:key="item.feature"
|
||||
class="inq-cell"
|
||||
@tap="goInquireFeature(item.feature)"
|
||||
>
|
||||
<image
|
||||
class="inq-icon"
|
||||
:src="getInquiryItemIconUrl(item)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="inq-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">
|
||||
查询案例
|
||||
</text>
|
||||
<text class="card-sub">
|
||||
已服务 290000+ 车主
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="item in caseList"
|
||||
:key="item.id"
|
||||
class="case-item"
|
||||
>
|
||||
<view class="case-line">
|
||||
<text class="case-tag">
|
||||
{{ item.tag }}
|
||||
</text>
|
||||
<text class="case-vin">
|
||||
{{ item.vin }}
|
||||
</text>
|
||||
<text class="case-model">
|
||||
{{ item.model }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">
|
||||
客户评价
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
v-for="item in reviewList"
|
||||
:key="item.id"
|
||||
class="review-item"
|
||||
>
|
||||
<view class="review-avatar">
|
||||
<text class="review-avatar-text">
|
||||
{{ reviewInitial(item.name) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="review-content">
|
||||
<view class="review-name">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<view class="review-text">
|
||||
{{ item.content }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #f8faff 0%, #f3f5fb 100%);
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24rpx 24rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.banner {
|
||||
margin-bottom: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 18rpx 40rpx rgba(15, 35, 52, 0.05);
|
||||
}
|
||||
|
||||
.banner-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
box-shadow:
|
||||
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.card-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.cat-badge {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 14rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cat-badge-icon {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.card-more-link {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
padding: 8rpx 16rpx;
|
||||
background: #f2f3f5;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.card-more-link:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.card-sub {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.grid-4-second {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
.icon-svg {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.grid-text {
|
||||
font-size: 24rpx;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.inq-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -8rpx;
|
||||
}
|
||||
|
||||
.inq-cell {
|
||||
width: 33.333%;
|
||||
padding: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.inq-icon {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
display: block;
|
||||
margin: 0 auto 10rpx;
|
||||
}
|
||||
|
||||
.inq-icon-custom {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
display: block;
|
||||
margin: 0 auto 10rpx;
|
||||
font-size: 48rpx;
|
||||
color: #1768ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.inq-name {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #1d2129;
|
||||
text-align: center;
|
||||
line-height: 1.35;
|
||||
padding: 0 4rpx;
|
||||
min-height: 60rpx;
|
||||
}
|
||||
|
||||
.main-query {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.main-left {
|
||||
flex: 1.2;
|
||||
margin-right: 16rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.card-img-wrap {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.main-left-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.small-card-img {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.main-right-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.case-item + .case-item {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.case-line {
|
||||
font-size: 22rpx;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.case-tag {
|
||||
color: #1768ff;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.case-vin {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.review-item {
|
||||
display: flex;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.review-avatar {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 16rpx;
|
||||
background: linear-gradient(145deg, #e8f0ff 0%, #d4e4ff 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.review-avatar-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.review-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.review-name {
|
||||
font-size: 24rpx;
|
||||
color: #1d2129;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.review-text {
|
||||
font-size: 22rpx;
|
||||
color: #4e5969;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
103
src/pages/inquire/example.vue
Normal file
103
src/pages/inquire/example.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<script setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { getQueryExample } from '@/api'
|
||||
import VehicleReportShell from '@/components/report/VehicleReportShell.vue'
|
||||
import { normalizeVehicleQueryData } from '@/utils/vehicleReportNormalize'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '示例报告',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
const feature = ref('')
|
||||
const loading = ref(true)
|
||||
const errText = ref('')
|
||||
const productName = ref('')
|
||||
const queryParams = ref({})
|
||||
const rows = ref(normalizeVehicleQueryData([]))
|
||||
|
||||
onLoad((options) => {
|
||||
feature.value = options?.feature || ''
|
||||
void load()
|
||||
})
|
||||
|
||||
async function load() {
|
||||
if (!feature.value) {
|
||||
loading.value = false
|
||||
errText.value = '缺少产品参数'
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
errText.value = ''
|
||||
try {
|
||||
const res = await getQueryExample(feature.value)
|
||||
if (res?.code === 200 && res.data) {
|
||||
productName.value = res.data.product_name || feature.value
|
||||
queryParams.value = res.data.query_params || {}
|
||||
rows.value = normalizeVehicleQueryData(res.data.query_data || [])
|
||||
if (!rows.value.length)
|
||||
errText.value = '该产品暂无示例模块数据'
|
||||
}
|
||||
else if (res?.code === 200003) {
|
||||
productName.value = feature.value
|
||||
errText.value = '暂无示例报告'
|
||||
}
|
||||
else {
|
||||
productName.value = feature.value
|
||||
errText.value = res?.msg || '加载失败'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
productName.value = feature.value
|
||||
errText.value = '网络异常'
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<view v-if="loading" class="state">
|
||||
加载中…
|
||||
</view>
|
||||
<view v-else-if="errText && !rows.length" class="state">
|
||||
{{ errText }}
|
||||
</view>
|
||||
<view v-else class="content">
|
||||
<VehicleReportShell
|
||||
mode="example"
|
||||
:product-name="productName"
|
||||
:query-params="queryParams"
|
||||
:rows="rows"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
min-height: 100vh;
|
||||
background: #d2dffa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.state {
|
||||
padding: 100rpx 32rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
/* 微信小程序:scroll-view + flex:1 + height:0 常导致可视高度为 0;改由页面整体滚动 */
|
||||
.content {
|
||||
padding: 24rpx 24rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
1128
src/pages/inquire/index.vue
Normal file
1128
src/pages/inquire/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
131
src/pages/legal/authorization.vue
Normal file
131
src/pages/legal/authorization.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '授权书',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
function formatSignDate(d: Date) {
|
||||
const y = d.getFullYear()
|
||||
const m = d.getMonth() + 1
|
||||
const day = d.getDate()
|
||||
return `${y}年${m}月${day}日`
|
||||
}
|
||||
|
||||
const signTime = ref(formatSignDate(new Date()))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="legal-root">
|
||||
<scroll-view scroll-y class="legal-scroll" :show-scrollbar="true">
|
||||
<view class="legal-inner">
|
||||
<view class="legal-page-title">
|
||||
个人信息查询授权书
|
||||
</view>
|
||||
<view class="legal-p">
|
||||
广西福铭网络科技有限公司:
|
||||
</view>
|
||||
<view class="legal-p legal-indent-text">
|
||||
本人________拟向贵司申请大数据分析报告查询业务,贵司需要了解本人相关状况,用于查询大数据分析报告,因此本人同意向贵司提供本人的姓名和手机号等个人信息,并同意贵司向第三方传送上述信息。第三方将使用上述信息核实信息真实情况,查询信用记录,并生成报告。
|
||||
</view>
|
||||
<view class="legal-h4">
|
||||
授权内容如下:
|
||||
</view>
|
||||
<view class="legal-ol">
|
||||
<view class="legal-li">
|
||||
1. 贵司向依法成立的第三方服务商根据本人提交的信息进行核实,并有权通过前述第三方服务机构查询、使用本人的身份信息、设备信息、运营商信息等,查询本人信息(包括但不限于学历、婚姻、资产状况及对信息主体产生负面影响的不良信息),出具相关报告。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
2. 依法成立的第三方服务商查询或核实、搜集、保存、处理、共享、使用(含合法业务应用)本人相关数据,且不再另行告知本人,但法律、法规、监管政策禁止的除外。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
3. 本人授权有效期为自授权之日起 1 个月。本授权为不可撤销授权,但法律法规另有规定的除外。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-h4">
|
||||
用户声明与承诺:
|
||||
</view>
|
||||
<view class="legal-ol">
|
||||
<view class="legal-li">
|
||||
1. 本人在授权签署前,已通过实名认证及动态验证码验证(或其他身份验证手段),确认本授权行为为本人真实意思表示,平台已履行身份验证义务。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
2. 本人在此声明已充分理解上述授权条款含义,知晓并自愿承担因授权数据使用可能带来的后果,包括但不限于影响个人信用评分、生活行为等。本人确认授权范围内的相关信息由本人提供并真实有效。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
3. 若用户冒名签署或提供虚假信息,由用户自行承担全部法律责任,平台不承担任何后果。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-h4">
|
||||
特别提示:
|
||||
</view>
|
||||
<view class="legal-ol">
|
||||
<view class="legal-li">
|
||||
1. 本产品所有数据均来自第三方。可能部分数据未公开、数据更新延迟或信息受到限制,贵司不对数据的准确性、真实性、完整性做任何承诺。用户需根据实际情况,结合报告内容自行判断与决策。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
2. 本产品仅供用户本人查询或被授权查询。除非用户取得合法授权,用户不得利用本产品查询他人信息。用户因未获得合法授权而擅自查询他人信息所产生的任何后果,由用户自行承担责任。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
3. 本授权书涉及对本人敏感信息(包括但不限于婚姻状态、资产状况等)的查询与使用。本人已充分知晓相关信息的敏感性,并明确同意贵司及其合作方依据授权范围使用相关信息。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
4. 平台声明:本授权书涉及的信息核实及查询结果由第三方服务商提供,平台不对数据的准确性、完整性、实时性承担责任;用户根据报告所作决策的风险由用户自行承担,平台对此不承担法律责任。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
5. 本授权书中涉及的数据查询和报告生成由依法成立的第三方服务商提供。若因第三方行为导致数据错误或损失,用户应向第三方主张权利,平台不承担相关责任。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-h4">
|
||||
附加说明:
|
||||
</view>
|
||||
<view class="legal-ol">
|
||||
<view class="legal-li">
|
||||
1. 本人在授权的相关数据将依据法律法规及贵司内部数据管理规范妥善存储,存储期限为法律要求的最短必要时间。超过存储期限或在数据使用目的达成后,贵司将对相关数据进行销毁或匿名化处理。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
2. 本人有权随时撤回本授权书中的授权,但撤回前的授权行为及其法律后果仍具有法律效力。若需撤回授权,本人可通过贵司官方渠道提交书面申请,贵司将在收到申请后依法停止对本人数据的使用。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
3. 你通过「全能查」/「天远查」,自愿支付相应费用,用于购买广西福铭网络科技有限公司的大数据报告产品。如若对产品内容存在异议,可通过邮箱 admin@iieeii.com 或小程序内「联系客服」进行反馈,贵司将在收到异议之日起 20 日内进行核查和处理,并将结果答复。
|
||||
</view>
|
||||
<view class="legal-li">
|
||||
4. 你向广西福铭网络科技有限公司的支付方式为:广西福铭网络科技有限公司及其经官方授权的相关企业的支付宝账户。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-h4">
|
||||
争议解决机制:
|
||||
</view>
|
||||
<view class="legal-ul">
|
||||
<view class="legal-li">
|
||||
若因本授权书引发争议,双方应友好协商解决;协商不成的,双方同意将争议提交至授权书签署地(广西壮族自治区)有管辖权的人民法院解决。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-h4">
|
||||
签署方式的法律效力声明:
|
||||
</view>
|
||||
<view class="legal-ul">
|
||||
<view class="legal-li">
|
||||
本授权书通过用户在线勾选、电子签名或其他网络签署方式完成,与手写签名具有同等法律效力。平台已通过技术手段保存签署过程的完整记录,作为用户真实意思表示的证据。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-p legal-mt">
|
||||
本授权书于 {{ signTime }} 起生效。
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './legal.scss';
|
||||
|
||||
.legal-indent-text {
|
||||
text-indent: 2em;
|
||||
}
|
||||
</style>
|
||||
125
src/pages/legal/legal.scss
Normal file
125
src/pages/legal/legal.scss
Normal file
@@ -0,0 +1,125 @@
|
||||
.legal-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.legal-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.legal-inner {
|
||||
padding: 24rpx 28rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.legal-page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 28rpx;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.legal-indent {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.legal-section {
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.legal-block,
|
||||
.legal-para {
|
||||
font-size: 26rpx;
|
||||
line-height: 1.75;
|
||||
color: #4e5969;
|
||||
margin-bottom: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.legal-para view,
|
||||
.legal-block view {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.legal-h3 {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 12rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.legal-h4 {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-top: 16rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.legal-subhead {
|
||||
font-size: 26rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin: 16rpx 0 8rpx;
|
||||
}
|
||||
|
||||
.legal-strong {
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.legal-link {
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.legal-date {
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.legal-date-right {
|
||||
display: block;
|
||||
text-align: right;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.legal-mt {
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
|
||||
.legal-mt-lg {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.legal-my {
|
||||
margin: 12rpx 0;
|
||||
}
|
||||
|
||||
.legal-p {
|
||||
font-size: 26rpx;
|
||||
line-height: 1.75;
|
||||
color: #4e5969;
|
||||
margin: 12rpx 0;
|
||||
}
|
||||
|
||||
.legal-ol,
|
||||
.legal-ul {
|
||||
padding-left: 32rpx;
|
||||
margin: 12rpx 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.legal-li {
|
||||
font-size: 26rpx;
|
||||
line-height: 1.75;
|
||||
color: #4e5969;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
499
src/pages/legal/privacy-policy.vue
Normal file
499
src/pages/legal/privacy-policy.vue
Normal file
@@ -0,0 +1,499 @@
|
||||
<script setup lang="ts">
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '隐私政策',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="legal-root">
|
||||
<scroll-view scroll-y class="legal-scroll" :show-scrollbar="true">
|
||||
<view class="legal-inner">
|
||||
<view>
|
||||
<!-- 页面标题 -->
|
||||
<view class="legal-page-title">
|
||||
隐私政策
|
||||
</view>
|
||||
|
||||
<!-- 内容主体 -->
|
||||
<view class="legal-indent">
|
||||
<view class="legal-section">
|
||||
<!-- 开篇说明 -->
|
||||
<view class="legal-h3">
|
||||
您的信任对我们非常重要
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们深知个人信息对您的重要性,我们将按法律法规要求,采取相应安全保护措施,尽力保护您的个人信息安全可控。
|
||||
有鉴于此,广西福铭网络科技有限公司(以下简称“我们”或“天远查”)作为天远查产品及服务的提供者制定本《隐私政策》(下称“本政策”)并提醒您:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
本政策适用于全部天远查产品及服务,如我们关联公司的产品或服务中使用了天远查提供的产品或服务但未设独立的隐私政策的,
|
||||
该部分天远查提供的产品或服务同样适用于本政策。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
需要特别说明的是,本政策不适用于其他第三方通过网页或天远查客户端直接向您提供的服务(统称“第三方服务”),
|
||||
您向该第三方服务提供者提供的信息不适用于本政策,您在选择使用第三方服务前应充分了解第三方服务的产品功能及隐私保护政策,再选择是否开通功能。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
在使用天远查产品或服务前,请您务必仔细阅读并透彻理解本政策,在确认充分理解使用相关产品或服务。
|
||||
一旦您开始使用天远查产品或服务,即表示您已充分理解并同意本政策。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-h3">
|
||||
第一部分 定义
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第一部分 -->
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
1、天远查服务提供者:是指研发并提供天远查产品和服务法律主体,广西福铭网络科技有限公司(下称“我们”或“天远查”)
|
||||
</view>
|
||||
<view>
|
||||
2、天远查用户:是指注册天远查账户的用户,以下称“您”。
|
||||
</view>
|
||||
<view>
|
||||
3、个人信息:指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。
|
||||
</view>
|
||||
<view>
|
||||
4、个人信息删除:指在实现日常业务功能所涉及的系统中去除个人信息的行为,使其保持不可被检索、访问的状态,具体指产品内的账号注销功能。
|
||||
</view>
|
||||
<view>
|
||||
5、个人信息匿名化:通过对个人信息的加密技术处理,使得个人信息主体无法被识别,且处理后的信息不能被复原的过程。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-h3">
|
||||
第二部分 隐私政策
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第一部分 -->
|
||||
<view class="legal-h3">
|
||||
一、我们如何收集您的个人信息
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
为了向您及天远查企业用户提供天远查服务,维护天远查服务的正常运行,改进及优化我们的服务体验并保障您的账号安全,
|
||||
我们会出于本政策下述目的及方式收集您在注册、使用天远查服务时主动提供、授权提供或基于您使用天远查服务时产生的信息:
|
||||
</view>
|
||||
|
||||
<!-- 注册天远查用户信息 -->
|
||||
<view class="legal-section">
|
||||
<view class="legal-h3">
|
||||
(一)注册天远查用户信息
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
为注册成为天远查用户,以便我们为您提供天远查服务,诸如数据查询、视频查看等功能,
|
||||
您需要提供您的手机号码及短信验证码以注册并创建天远查账号,否则您将不能使用天远查服务。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
如果您仅需使用浏览、搜索天远查网页展示的产品、功能及服务介绍,您不需要注册成为天远查用户并提供上述信息。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
如您的账号是注册在企业下的关联账号,当您所在企业用户注销天远查账户时,我们将会匿名化处理或删除您在该组织的相关个人信息,
|
||||
但您作为天远查个人用户的个人信息仍将保留,除非您主动注销天远查账户。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
在经过用户授权同意的情况下,我司需要获取用户的手机号码以便开展相应业务。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 使用天远查服务过程中收集信息 -->
|
||||
<view class="legal-section">
|
||||
<view class="legal-h3">
|
||||
(二)使用天远查服务过程中收集信息
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
当您在使用天远查服务过程中,为向您提供您需求的天远查软件服务、交互展示、搜索结果、识别账号异常状态,维护天远查服务的正常运行,改进及优化您对天远查服务的体验并保障您的账号安全,包括您使用天远查服务以及使用方式的信息,并将这些信息进行关联:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>1、日志信息:</view>
|
||||
<view>
|
||||
当您使用我们的网站或客户端提供的产品或服务时,我们会自动收集您对我们服务的详细使用情况,作为有关网络日志保存。例如您的搜索查询内容、IP地址、使用的语言、访问日期和时间、您访问的网页记录、日志信息。
|
||||
</view>
|
||||
<view>
|
||||
请注意,单独的设备信息、日志信息是无法识别特定自然人身份的信息。如果我们将这类非个人信息与其他信息结合用于识别特定自然人身份,或者将其与个人信息结合使用,则在结合使用期间,这类非个人信息将有可能被视为个人信息,除取得您授权或法律法规另有规定外,我们会将该类个人信息做匿名化、去标识化处理。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>2、您向我们提供的信息:</view>
|
||||
<view>
|
||||
在服务使用过程中,特别是在申请提现、实名认证或佣金结算时,您需要提供包括但不限于姓名、身份证号、银行卡号、手机号、税务身份信息等个人资料。
|
||||
您同意我们为履行合同义务、税务申报、身份核验、财务结算等必要目的,收集、使用、存储并在必要范围内共享该等信息。
|
||||
在进行税务代扣代缴、结算服务时,我们有权将必要信息提供给依法合作的第三方税务服务商、结算服务商,前提是该第三方承担同等信息保护义务。
|
||||
</view>
|
||||
<view>
|
||||
您可以对全能查产品及服务的体验问题反馈,帮助我们更好地了解您使用我们产品或服务的体验和需求,改善我们产品或服务,为此我们会记录您的联系信息、反馈的问题或建议,以便我们进一步联系您反馈您我们的处理意见。
|
||||
为向您提供更好的服务,例如在不同的服务端或设备上提供体验一致的服务和您需求的客服接待,了解产品适配性,识别账号异常状态。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>3、为您提供安全保障收集信息:</view>
|
||||
<view>
|
||||
为预防、发现、调查欺诈、侵权、危害安全、非法或违反与我们或与我们关联公司的协议、政策或规则的行为,我们可能收集或整合您的用户个人信息、服务使用信息、设备信息、日志信息以及我们关联公司、合作伙伴取得您授权或依据法律共享的信息。
|
||||
您理解并同意,我们向您提供的功能和服务场景是不断迭代升级的,如我们未在上述场景中明示您需要收集的个人信息,我们将会通过页面提示、交互设计等方式另行向您明示信息收集的内容、范围和目的并征得您同意。
|
||||
</view>
|
||||
<view>
|
||||
如我们停止运营天远查产品或服务,我们将及时停止继续收集您个人信息的活动,将停止运营的通知以公告或短信的形式通知您,并依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>4、手机号码收集及其用途:</view>
|
||||
<view>
|
||||
在您使用天远查服务的过程中,我们可能会要求您提供手机号码。我们收集您的手机号码,主要是为了向您发送重要的通知、服务更新、账户安全信息、促销活动、服务相关的短信等。为了确保您能及时获得关于您账号安全、产品更新和优化、系统维护等信息,我们可能会向您发送有关服务变更、功能更新、版本升级等通知,确保您能够持续享受我们的产品和服务。
|
||||
</view>
|
||||
<view>
|
||||
此外,您的手机号码还可能用于为您提供个性化的短信推广内容,帮助您了解我们新推出的服务、产品或活动优惠。我们承诺,不会在未经您明确同意的情况下,将您的手机号码用于任何与服务相关以外的用途,且不会将您的信息出售或租赁给第三方。为了保障您的权益,您可以随时通过设置页面或联系客户服务停止接收短信通知或推广信息。如果您选择取消订阅短信通知或推广,您仍将继续收到与账户安全、系统通知等相关的重要信息。
|
||||
</view>
|
||||
<view>
|
||||
我们会采取严格的措施保护您的手机号码不被滥用,包括采用加密存储、定期审查访问权限等技术和管理手段,以确保您的个人信息安全。同时,我们也会根据适用的法律法规,在您停止使用我们的服务或终止您的账户时,删除或匿名化处理您的手机号码及其他相关信息。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第二部分 -->
|
||||
<view class="legal-h3">
|
||||
二、我们如何使用信息
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
收集您的信息是为了向您提供服务及提升服务质量,为了实现这一目的,我们会把您的信息用于下列用途:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(1)向您提供您使用的天远查产品或服务,并维护、改进、优化这些服务及服务体验;
|
||||
</view>
|
||||
<view>
|
||||
(2)为预防、发现、调查欺诈、侵权、危害安全、非法或违反与我们或与我们关联公司的协议、政策或规则的行为,保护您、其他用户或公众以及我们或我们关联公司的合法权益,我们会使用或整合您的个人信息、服务使用信息、设备信息、日志信息以及我们关联公司、合作伙伴取得您授权或依据法律共享的信息,来综合判断您的操作风险、检测及防范安全事件,并依法采取必要的记录、审计、分析、处置措施;
|
||||
</view>
|
||||
<view>(3)经您许可的其他用途。</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第三部分 -->
|
||||
<view class="legal-h3">
|
||||
三、我们如何使用Cookie 和同类技术
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
为使您获得更轻松的访问体验,您使用天远查产品或服务时,我们可能会通过采用各种技术收集和存储您访问天远查服务的相关数据,
|
||||
在您访问或再次访问天远查服务时,我们能识别您的身份,并通过分析数据为您提供更好更多的服务。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
包括使用小型数据文件识别您的身份,这么做是为了解您的使用习惯,帮您省去重复输入账户信息的步骤,或者帮助判断您的账户安全。
|
||||
这些数据文件可能是Cookie、Flash
|
||||
Cookie,或您的浏览器或关联应用程序提供的其他本地存储(统称“Cookie”)。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
请您理解,我们的某些服务只能通过使用Cookie才可得到实现。如果您的浏览器或浏览器附加服务允许,
|
||||
您可以修改对Cookie的接受程度或者拒绝天远查的Cookie,但拒绝天远查的Cookie在某些情况下您可能无法使用依赖于cookies的天远查服务的部分功能。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第四部分 -->
|
||||
<view class="legal-h3">
|
||||
四、我们如何共享、转让、公开披露您的信息
|
||||
</view>
|
||||
|
||||
<!-- 共享 -->
|
||||
<view class="legal-subhead">
|
||||
(一) 共享
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们不会和其他公司、组织和个人共享您的个人信息,但以下情况除外:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(1)在获取您同意的情况下共享:获得您的明确同意后,我们会与其他方共享您的个人信息。
|
||||
</view>
|
||||
<view>
|
||||
(2)在法定情形下的共享:我们可能会根据法律法规规定、诉讼争议解决需要,或按行政、司法机关依法提出的要求,对外共享您的个人信息。
|
||||
</view>
|
||||
<view>
|
||||
(3)只有透露您的资料,才能提供您所要求的第三方产品和服务,在您通过天远查客户端购买查询服务的,您同意天远查向实际产品提供者提供您的身份信息,包括真实姓名和身份证号等。为了提升实人认证的准确性,您同意第三方公司仅限于个人信息进行验证相关服务,将您提供的个人信息与法律法规允许的机构或政府机关授权的机构的数据进行校验。
|
||||
</view>
|
||||
<view>
|
||||
(4)在您被他人投诉侵犯知识产权或其他合法权利时,需要向投诉人披露您的必要资料,以便进行投诉处理的;
|
||||
</view>
|
||||
<view>
|
||||
(5)天远查服务可能含有其他网站的链接。除法律另有规定外,天远查对其他网站的隐私保护措施不负相应法律责任。我们可能在需要的时候增加商业伙伴,但是提供给他们的将仅是综合信息,我们将不会公开您的个人信息。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 转让 -->
|
||||
<view class="legal-subhead">
|
||||
(二) 转让
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(1)在获取明确同意的情况下转让:获得您的明确同意后,我们会向其他方转让您的个人信息。
|
||||
</view>
|
||||
<view>
|
||||
(2)在天远查发生合并、收购或破产清算情形,或其他涉及合并、收购或破产清算情形时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本政策的约束,否则我们将要求该公司、组织和个人重新向您征求授权同意。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 公开披露 -->
|
||||
<view class="legal-subhead">
|
||||
(三) 公开披露
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们仅会在以下情况下,公开披露您的个人信息:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(1)获得您明确同意或基于您的主动选择,我们可能会公开披露您的个人信息;
|
||||
</view>
|
||||
<view>
|
||||
(2)如果我们确定您出现违反法律法规或严重违反天远查相关协议规则的情况,或为保护天远查及其关联公司用户或公众的人身财产安全免遭侵害,我们可能依据法律法规或天远查相关协议规则征得您同意的情况下披露关于您的个人信息,包括相关违规行为以及天远查已对您采取的措施。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 特殊情况 -->
|
||||
<view class="legal-subhead">
|
||||
(四)
|
||||
共享、转让、公开披露个人信息时事先征得授权同意的例外
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
以下情形中,共享、转让、公开披露您的个人信息无需事先征得您的授权同意:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>(1)与国家安全、国防安全有关的;</view>
|
||||
<view>
|
||||
(2)与公共安全、公共卫生、重大公共利益有关的;
|
||||
</view>
|
||||
<view>
|
||||
(3)与犯罪侦查、起诉、审判和判决执行等有关的;
|
||||
</view>
|
||||
<view>
|
||||
(4)出于维护您或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
|
||||
</view>
|
||||
<view>(5)您自行向社会公众公开的个人信息;</view>
|
||||
<view>
|
||||
(6)从合法公开披露的信息中收集个人信息的,如合法的新闻报道、政府信息公开等渠道。
|
||||
请您注意,根据法律规定,共享、转让经匿名化处理的个人信息,且确保数据接收方无法复原并重新识别个人信息主体的,不属于个人信息的对外共享、转让及公开披露行为,对此类数据的保存及处理将无需另行向您通知并征得您的同意。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第五部分 -->
|
||||
<view class="legal-h3">
|
||||
五、我们如何保护您的信息
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们会采取各种预防措施来保护您的个人信息,以保障您的个人信息免遭丢失、盗用和误用,以及被擅自取阅、披露、更改或销毁。
|
||||
为确保您个人信息的安全,我们有严格的信息安全规定和流程并严格执行上述措施。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
天远查建立了全方位、多维度的数据安全管理体系,保证整个天远查各个平台的安全性。
|
||||
我们会采取合理可行的措施,尽力避免收集无关的个人信息,
|
||||
并在限于达成本政策所述目的所需的期限以及所适用法律法规所要求的期限内对您的个人信息进行脱敏处理。
|
||||
在您使用查询过程中所涉及的用户姓名、身份证号、手机号/账号密码信息均采用的是AES加密方式,
|
||||
所有二次输出信息均经过脱敏处理,数据库文件不存储用户明文数据。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
在不幸发生个人信息安全事件后,我们将按照法律法规的要求(最迟不迟于30个自然日内)向您告知:
|
||||
安全事件的基本情况和可能的影响、我们已采取或将要采取的处置措施、您可自主防范和降低风险的建议、对您的补救措施等。
|
||||
事件相关情况我们将以邮件、信函、电话通知等方式告知您,
|
||||
难以逐一告知个人信息主体时,我们会采取合理、有效的方式发布公告。
|
||||
同时,我们还将按照监管部门要求,上报个人信息安全事件的处置情况。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
互联网环境并非百分之百安全,尽管我们有这些安全措施,但仍然无法完全避免互联网中存在的各种风险,我们将尽力确保您的信息的安全性。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第六部分 -->
|
||||
<view class="legal-h3">
|
||||
六、未成年人保护
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们重视未成年人的信息保护,如您为未成年人的,建议您请您的父母或监护人仔细阅读本隐私权政策,
|
||||
并在征得您的父母或监护人同意的前提下使用我们的服务或向我们提供信息。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
对于经父母或监护人同意使用我们的产品或服务而收集未成年人个人信息的情况,
|
||||
我们只会在法律法规允许,父母或监护人明确同意或者保护未成年人所必要的情况下使用、共享、转让或披露此信息。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们将根据国家相关法律法规及本政策的规定保护未成年人的个人信息。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第七部分 -->
|
||||
<view class="legal-h3">
|
||||
七、您的个人信息存储
|
||||
</view>
|
||||
<!-- 存储地区 -->
|
||||
<view class="legal-subhead">
|
||||
(一) 存储地区
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们将在中华人民共和国境内运营天远查服务中收集和产生的个人信息存储在中华人民共和国境内。
|
||||
目前,我们不会将上述信息传输至境外,如果我们向境外传输,我们将会遵循相关国家规定或者征求您的同意。
|
||||
</view>
|
||||
<!-- 存储期限 -->
|
||||
<view class="legal-subhead">
|
||||
(二) 存储期限
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
您在使用本平台期间,我们将保存您的个人脱敏加密信息,保存期限将以不超过为您提供服务所必须的期间为原则。
|
||||
在您终止使用本平台后,除法律法规对于特定信息保留期限另有规定外,我们会对您的信息进行删除或做匿名化处理。
|
||||
如我们停止运营本平台服务,我们将在合理期限内依照所适用的法律对所持有的您的个人信息进行删除或匿名化处理。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第八部分 -->
|
||||
<view class="legal-h3">
|
||||
八、您享有的权利及权利行使路径
|
||||
</view>
|
||||
<!-- 访问查询权 -->
|
||||
<view class="legal-subhead">
|
||||
(一) 访问查询权
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
您对您的天远查账号内的信息(含个人信息)依法享有访问查询权,包括:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
<text class="legal-strong">账户信息:</text>
|
||||
您可以登录手机客户端,通过【我的-点击名字或头像】可以访问您的头像信息、姓名、绑定手机号。
|
||||
</view>
|
||||
<view>
|
||||
<text class="legal-strong">使用信息:</text>
|
||||
您可以在天远查手机客户端相关页面访问、查询您的使用信息,包括订单信息,
|
||||
可以通过【报告列表-查看详情】进行访问、查看。
|
||||
</view>
|
||||
<view>
|
||||
<text class="legal-strong">其他信息:</text>
|
||||
如您在此前述过程中遇到操作问题的或如需获取其他前述无法获知的个人信息内容,
|
||||
您可通过在线客服或邮箱联系我们,我们将在核实您的身份后在合理期限内向您提供,
|
||||
但法律法规另有规定的或本政策另有约定的除外。
|
||||
</view>
|
||||
</view>
|
||||
<!-- 同意的撤回与变更 -->
|
||||
<view class="legal-subhead">
|
||||
(二) 同意的撤回与变更
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
若您需要更改相关权限的授权(例如:相机、相册、麦克风),您可以通过您的硬件设备进行修改。
|
||||
您也可以通过注销天远查账户的方式永久撤回我们继续收集您个人信息的全部授权。
|
||||
如您在此过程中遇到操作问题的,可以通过本政策“帮助中心”方式联系我们。
|
||||
</view>
|
||||
<!-- 帮助反馈权 -->
|
||||
<view class="legal-subhead">
|
||||
(三) 帮助反馈权
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们为您提供了多种反馈渠道,具体请见设置—帮助中心。
|
||||
</view>
|
||||
<!-- 提前获知产品与/或服务停止运营权 -->
|
||||
<view class="legal-subhead">
|
||||
(四) 提前获知产品与/或服务停止运营权
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们将持续为您提供优质服务,若因特殊原因导致我们的部分或全部产品与/或服务被迫停止运营,
|
||||
我们将提前在显著位置或通知您,并将停止对您个人信息的收集,
|
||||
同时在超出法律法规规定的必需且最短期限后,我们将会对所持有的您的个人信息进行删除或匿名化处理。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第九部分 -->
|
||||
<view class="legal-h3">
|
||||
九、本政策如何更新
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
我们的隐私政策可能变更。
|
||||
未经您明确同意我们不会限制您按照本隐私政策所应享有的权利。
|
||||
我们会在天远查各个平台,包括客户端、相关网页上以首页弹窗形式发布对本隐私政策所做的任何变更,并以交互设计提醒您阅读并完整理解。
|
||||
对于重大变更,我们还会提供更为显著的通知(可能包括公告通知甚至向您提供弹窗提示)。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
本政策所指的重大变更包括但不限于:
|
||||
<view>
|
||||
(1)我们的服务模式发生重大变化。如处理用户信息的目的、用户信息的使用方式等;
|
||||
</view>
|
||||
<view>
|
||||
(2)我们在控制权、组织架构等方面发生重大变化。如业务调整、破产并购等引起的所有者变更等;
|
||||
</view>
|
||||
<view>
|
||||
(3)用户信息共享、转让或公开披露的主要对象发生变化;
|
||||
</view>
|
||||
<view>
|
||||
(4)我们负责处理用户信息安全的责任部门、联络方式及投诉渠道发生变化时;
|
||||
</view>
|
||||
<view>
|
||||
(5)用户信息安全影响评估报告表明存在高风险时。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第十部分 -->
|
||||
<view class="legal-h3">
|
||||
十、如何联系我们
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
如果您对本政策或数据处理有任何疑问、意见或建议,可以通过天远查产品内的“联系客服”或邮箱
|
||||
<text class="legal-link"> admin@iieeii.com </text>
|
||||
与我们联系。我们将在收到您发送的响应请求或相关信息之日起十五(15)天内回复您。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
您理解并同意,当涉及以下任一情形时,我们无法响应您的请求:
|
||||
<view>(1)与国家安全、国防安全有关的;</view>
|
||||
<view>
|
||||
(2)与公共安全、公共卫生、重大公共利益有关的;
|
||||
</view>
|
||||
<view>(3)与犯罪侦查、起诉和审判等有关的;</view>
|
||||
<view>
|
||||
(4)有充分证据表明您存在主观恶意或滥用权利的;
|
||||
</view>
|
||||
<view>
|
||||
(5)响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的;
|
||||
</view>
|
||||
<view>(6)涉及天远查或任何第三方主体商业秘密的;</view>
|
||||
<view>(7)法律法规规定的其他情形。</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
如果您对我们的回复不满意,特别是您认为我们的个人信息处理行为损害了您的合法权益,
|
||||
您还可以通过向有管辖权的法院提起诉讼来寻求解决方案。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第十一部分 -->
|
||||
<view class="legal-h3">
|
||||
十一、其他
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
(一)本《隐私政策》的解释及争议解决均应适用中华人民共和国大陆地区法律。
|
||||
与本《隐私政策》相关的任何纠纷,双方应协商友好解决;若不能协商解决,
|
||||
应将争议提交至广西福铭网络科技有限公司注册地有管辖权的人民法院解决。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
(二)本《隐私政策》的标题仅为方便及阅读而设,并不影响正文其中任何规定的含义或解释。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-date legal-date-right">
|
||||
<text>2024年11月19日</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './legal.scss';
|
||||
</style>
|
||||
311
src/pages/legal/user-agreement.vue
Normal file
311
src/pages/legal/user-agreement.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<script setup lang="ts">
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '用户协议',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="legal-root">
|
||||
<scroll-view scroll-y class="legal-scroll" :show-scrollbar="true">
|
||||
<view class="legal-inner">
|
||||
<view>
|
||||
<!-- 页面标题 -->
|
||||
<view class="legal-page-title">
|
||||
用户协议
|
||||
</view>
|
||||
|
||||
<!-- 内容主体 -->
|
||||
<view class="legal-indent">
|
||||
<view class="legal-block">
|
||||
本协议是您(以下又称“用户”)在使用本服务时,约定您和广西福铭网络科技有限公司之间权利义务关系的有效协议。
|
||||
</view>
|
||||
|
||||
<view class="legal-block">
|
||||
在您使用本服务前,请您务必仔细阅读本协议,特别是隐私权保护及授权条款、免除或者限制广西福铭网络科技有限公司责任的条款、争议解决和法律适用条款。一旦您有对本服务的任何部分或全部的注册、查看、定制、使用等任何使用行为,即视为您已充分阅读、理解并接受本协议的全部内容,并与广西福铭网络科技有限公司达成本协议。如您对本协议有任何疑问,应向广西福铭网络科技有限公司客服咨询。如果您不同意本协议的部分或全部约定,您应立即停止使用本服务。
|
||||
</view>
|
||||
<view class="legal-block">
|
||||
您与广西福铭网络科技有限公司达成本协议后,您承诺接受并遵守本协议的约定,并不得以未阅读本协议的内容或者未获得广西福铭网络科技有限公司对您问询的解答等理由,主张本协议无效,或要求撤销本协议。在本协议履行过程中,广西福铭网络科技有限公司可以依其单独判断暂时停止提供、限制或改变本服务,并有权根据自身业务需要修订本协议。一旦本协议的内容发生变动,广西福铭网络科技有限公司将通过平台公布最新的服务协议,不再向您作个别通知。如果您不同意广西福铭网络科技有限公司对本服务协议所做的修改,您应立即停止使用本服务或通过广西福铭网络科技有限公司客服与广西福铭网络科技有限公司联系。如果您继续使用本服务,则视为您接受广西福铭网络科技有限公司对本协议所做的修改,并应遵照修改后的协议执行。
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<view class="legal-h3">
|
||||
一、服务内容
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
本服务向您提供多项个人信息整理服务。您知悉并认可,如您需使用该类服务,必须满足如下所述条件;且您承诺,您向广西福铭网络科技有限公司提请服务申请时,已经满足如下所述条件。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>A.您已注册成为本服务的会员;</view>
|
||||
<view>
|
||||
B.您已在服务页面对应框中填写被查询主体的姓名、身份证号、手机号、银行卡号和被查询主体的手机号收到的动态验证码(以下称“被查询主体信息”);
|
||||
</view>
|
||||
<view>
|
||||
C.您确保被查询主体信息是您本人的信息或者被查询主体已授权您本人使用被查询主体信息进行查询(授权内容应包括本条D项所述内容),并且被查询主体已知悉该授权的风险。
|
||||
</view>
|
||||
<view>
|
||||
D.被查询主体不可撤销地授权广西福铭网络科技有限公司为查询、评估被查询主体的信息状况:a.可以委托合法存续的第三方机构收集、查询、验证、使用并提供您或被查询主体的个人信息;b.可以向数据源机构采集您或被查询主体的个人信息;c.可以整理、保存、加工、使用您或被查询主体的个人信息,并向您提供数据报告;d.可以向为您提供服务的第三方商户提供脱敏后的个人信息或数据报告。本条所述的个人信息包括但不限于身份信息、联系方式、职业和居住地址等个人基本信息,个人社保、公积金、收入及在商业活动中形成的各类交易记录,个人公共费用缴纳、违法违规信息、财产状况等;
|
||||
</view>
|
||||
<view>
|
||||
E.被查询主体已被明确告知提供被查询主体信息并作出D项授权可能给被查询主体带来的各类损失以及其他可能的不利后果,包括采集上述个人信息对被查询主体信用方面可能产生不良影响以及上述信息被信息使用者依法提供给第三方后被他人不当利用的风险。
|
||||
</view>
|
||||
<view>F.您已全额支付相应的查询服务费用;</view>
|
||||
<view>
|
||||
G.验证码请不要轻易提供给他人,一旦填入手机号对应验证码,视为手机号机主本人操作。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<view class="legal-h3">
|
||||
二、服务中断或故障
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
您同意,因下列原因导致广西福铭网络科技有限公司无法正常提供本服务的,广西福铭网络科技有限公司不承担责任:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>(1)承载本服务的系统停机维护期间;</view>
|
||||
<view>
|
||||
(2)您的电脑、手机软硬件和通信线路、供电线路出现故障的;
|
||||
</view>
|
||||
<view>
|
||||
(3)您操作不当或通过非广西福铭网络科技有限公司授权或认可的方式使用本服务的;
|
||||
</view>
|
||||
<view>
|
||||
(4)因病毒、木马、恶意程序攻击、网络拥堵、系统不稳定、系统或设备故障、通讯故障、电力故障或政府行为等原因;
|
||||
</view>
|
||||
<view>
|
||||
(5)由于黑客攻击、网络供应商技术调整或故障、网站升级、手机运营商系统方面的问题等原因而造成的本服务中断或延迟;
|
||||
</view>
|
||||
<view>
|
||||
(6)因台风、地震、海啸、洪水、停电、战争、恐怖袭击等不可抗力之因素,造成本服务系统障碍不能执行业务的。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
广西福铭网络科技有限公司不对因使用本服务而对用户造成的间接的、附带的、特殊的、后果性的损失承担任何法律责任;尽管有前款约定广西福铭网络科技有限公司将采取合理行动积极促使本服务恢复正常。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<view class="legal-h3">
|
||||
三、信息的使用和保护
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
广西福铭网络科技有限公司深知您注重个人信息安全和保护,并理解保护被查询主体个人信息的重要性。
|
||||
广西福铭网络科技有限公司会严格遵守中国关于收集、使用、保存用户个人信息的相关法律法规,
|
||||
尽最大努力采用相应安全技术和管理手段保护您或被查询主体的个人信息,
|
||||
防止您或被查询主体个人信息遭受未经授权的访问、适用或泄露、毁损、篡改或者丢失。
|
||||
未经您或被查询主体的授权不会向任何第三方提供。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
您使用本服务,即表示您已授权广西福铭网络科技有限公司将您相关信息披露给广西福铭网络科技有限公司关联公司
|
||||
(关联公司是指直接或间接控制于本协议一方的任何法律实体,或者与本协议一方共同于另一法律实体的任何法律实体)使用,
|
||||
且广西福铭网络科技有限公司关联公司仅为了向您提供服务而使用您的相关信息。
|
||||
如广西福铭网络科技有限公司关联公司使用您的相关信息,则受本协议约束且会按照与广西福铭网络科技有限公司同等谨慎程度保护您的相关信息。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
在您使用本服务过程中,特别是在申请提现、实名认证或佣金结算时,您需要提供包括但不限于姓名、身份证号、银行卡号、手机号、税务身份信息等个人资料。
|
||||
您同意我们为履行合同义务、税务申报、身份核验、财务结算等必要目的,收集、使用、存储并在必要范围内共享该等信息。
|
||||
在进行税务代扣代缴、结算服务时,我们有权将必要信息提供给依法合作的第三方税务服务商、结算服务商,前提是该第三方承担同等信息保护义务。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
您有权查询、更正您的个人信息,也可以根据平台流程申请注销账户或停止使用相关服务,我们将根据法律要求妥善处理相关信息。
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
广西福铭网络科技有限公司就下列原因导致的您或被查询主体个人信息的泄露,不承担任何法律责任:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(1)由于您个人原因将本服务的会员账号和密码告知他人或与他人共享广西福铭网络科技有限公司服务账户,由此导致的与您相关的信息的泄露。
|
||||
</view>
|
||||
<view>
|
||||
(2)您使用第三方提供的服务(包括您向第三方提供的任何个人信息),须受第三方自己的服务条款及个人信息保护协议(而非本协议)约束,您需要仔细阅读其条款。本协议仅适用于广西福铭网络科技有限公司所提供的服务,并不适用于任何第三方提供的服务或第三方的信息使用规则,广西福铭网络科技有限公司对任何第三方使用由您提供的信息不承担任何责任。
|
||||
</view>
|
||||
<view>
|
||||
(3)根据相关的法律法规、相关政府主管部门或相关证券交易所的要求提供、公布与您相关的信息。
|
||||
</view>
|
||||
<view>
|
||||
(4)或其他非因广西福铭网络科技有限公司原因导致的与您相关的信息的泄露。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第四部分 -->
|
||||
<view class="legal-h3">
|
||||
四、用户声明与保证
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(1)您使用本服务的前提是您依照适用的法律,是具有完全民事权利和民事行为能力,能够独立承担民事责任的自然人。
|
||||
</view>
|
||||
<view>
|
||||
(2)您如违反本协议第一条款中的承诺,您可能会对他人造成侵权。如由此给广西福铭网络科技有限公司或他人造成损失的,您需依照法律法规规定承担相应的法律责任。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第五部分 -->
|
||||
<view class="legal-h3">
|
||||
五、知识产权保护
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
本服务涉及的文档资料、软件、商标、图案、排版设计等(以下简称“广西福铭网络科技有限公司产品”)的著作权、商标以及其他知识产权或权益均为广西福铭网络科技有限公司享有或广西福铭网络科技有限公司获得授权使用。
|
||||
用户不得出租、出借、拷贝、仿冒、复制或修改广西福铭网络科技有限公司产品任何部分或用于其他任何商业目的,
|
||||
也不得将广西福铭网络科技有限公司产品做反向工程、反编译或反汇编,或以其他方式或工具取得广西福铭网络科技有限公司产品之目标程序或源代码。
|
||||
如果用户违反此约定,造成广西福铭网络科技有限公司及其他任何第三方任何损失的,甲方应予以全额赔偿。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第六部分 -->
|
||||
<view class="legal-h3">
|
||||
六、隐私保护
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
天远查保证不对外公开或向第三方提供单个用户的注册资料及存储在天远查的非公开内容,但下列情况下除外:
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>1. 事先获得用户的明确授权;</view>
|
||||
<view>2. 根据有关的法律法规要求;</view>
|
||||
<view>3. 按照有关政府部门的要求;</view>
|
||||
<view>4. 为维护社会公众的利益;</view>
|
||||
<view>5. 为维护天远查的合法利益。</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
在不透露单个用户隐私资料的前提下,天远查有权利对整个用户数据库进行分析并对用户数据库进行商业上的利用。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-section">
|
||||
<!-- 第七部分 -->
|
||||
<view class="legal-h3">
|
||||
七、免责条款
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(一)不管基于任何直接的、间接的、特殊的、惩罚性的、惩戒性的、附带的、或结果性的损害、损失或费用,我们均不对其承担责任。即使有人告知我们或我们的员工存在出现这些损害、损失或费用的可能性。这些损害、损失或费用由以下这些情况引起或与这些情况有关:
|
||||
</view>
|
||||
<view>1. 使用我们网站上或其他链接网站上的信息;</view>
|
||||
<view>2. 无法使用这些信息;</view>
|
||||
<view>
|
||||
3.
|
||||
任何在操作或传输中出现的操作失败、错误、遗漏、中断、缺陷、延迟,计算机病毒,断线或系统运行失败。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(二)我们可以在不事先通知的情况下更改信息,并且不承担更新这些信息的义务。不经任何种类的授权,不做任何专门或暗指或法定的不侵犯第三方权利、名称、可出售性、出于某种特殊目的适当措施或不携带计算机病毒的保证。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(三)我们不对您查询信息内容的正确性、适当性、完整性、准确性、可靠性或适时性做出任何证明、声明和保证。我们不对任何因个人平台产生的错误、遗漏及失准承担任何责任。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(四)对于由于您违反本协议导致任何第三方针对我们及或我们的员工提出的任何申诉、起诉、要求或者诉讼或者其他法律程序,您同意自费作出赔偿并令其免受上述损害。
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第八部分 -->
|
||||
<view class="legal-h3">
|
||||
八、违约
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
用户不得利用本服务进行任何损害广西福铭网络科技有限公司及其他第三方权益的行为,否则广西福铭网络科技有限公司有权立即终止为该用户提供本服务,并要求用户赔偿损失。由此产生的任何后果由用户自行承担,与广西福铭网络科技有限公司无关。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第九部分 -->
|
||||
<view class="legal-h3">
|
||||
九、数据来源及准确性说明
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
本产品数据来源于第三方,可能因数据未公开、更新延迟或信息受到限制,因此不一定能完全返回。不同数据格式及记录详细程度会有所差异,这是行业正常现象。本报告仅供参考,请结合实际情况做出决策。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第十部分 -->
|
||||
<view class="legal-h3">
|
||||
十、退款协议
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
除非由于本程序的技术性问题导致用户无法正常使用本产品,否则我们不提供任何退款服务。
|
||||
用户在购买前应仔细阅读本用户协议及相关使用条款,确保对本产品有充分了解。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第十一部分 -->
|
||||
<view class="legal-h3">
|
||||
十一、协议的变更和终止
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
鉴于网络服务的特殊性,我们变更本协议及其附件的若干条款时,将提前通过我们平台公告有关变更事项。
|
||||
修订后的条款或将来可能发布或更新的各类规则-经在我们平台公布后,立即自动生效。
|
||||
如您不同意相关修订,应当立即停止使用该项服务。
|
||||
如您在发布上述协议变更的有关公告后继续使用互联网查询的,视为您已接受协议的有关变更,并受其约束。
|
||||
本协议中的相关条款根据该变更而自动做相应修改,双方无须另行签订书面协议。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第十二部分 -->
|
||||
<view class="legal-h3">
|
||||
十二、适用法律
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
本协议条款的解释、效力及纠纷的解决,适用中华人民共和国大陆地区法律法规。
|
||||
如用户和广西福铭网络科技有限公司之间发生任何争议,首先应友好协商解决,协商不成的,应将争议提交至广西福铭网络科技有限公司注册地有管辖权的人民法院解决。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第十三部分 -->
|
||||
<view class="legal-h3">
|
||||
十三、问题咨询
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
如您对本协议及本服务有任何问题,请通过邮箱
|
||||
<text class="legal-link"> admin@iieeii.com </text> 或
|
||||
通过“联系客服”联系广西福铭网络科技有限公司进行咨询。
|
||||
广西福铭网络科技有限公司会尽最大努力解决您的问题。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="legal-section">
|
||||
<!-- 第十四部分 -->
|
||||
<view class="legal-h3">
|
||||
十四、附则
|
||||
</view>
|
||||
<view class="legal-para">
|
||||
<view>
|
||||
(一)本协议的某一条款被确认无效,均不影响本协议其他条款的效力。
|
||||
</view>
|
||||
<view>
|
||||
(二)本协议未尽事宜,根据我国相关法律、法规及我们相关业务规定办理。如需制定补充协议,其法律效力同本协议。
|
||||
</view>
|
||||
</view>
|
||||
<view class="legal-block legal-mt">
|
||||
本协议通过点击同意/勾选的方式签署,自签署之日生效。
|
||||
</view>
|
||||
<view class="legal-date legal-date-right">
|
||||
<text>本协议于 2024 年 11 月 17 日生效。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './legal.scss';
|
||||
</style>
|
||||
528
src/pages/login.vue
Normal file
528
src/pages/login.vue
Normal file
@@ -0,0 +1,528 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onUnmounted, ref } from 'vue'
|
||||
import { postAuthSendSms, postUserMobileCodeLogin } from '@/api'
|
||||
import { saveAuthSession } from '@/utils/session'
|
||||
import { tryWxMiniProgramAuth } from '@/utils/wxMiniAuth'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '登录',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
const phoneMode = ref(false)
|
||||
const mobile = ref('')
|
||||
const code = ref('')
|
||||
const agreed = ref(false)
|
||||
const sending = ref(false)
|
||||
const submitting = ref(false)
|
||||
const wxLoading = ref(false)
|
||||
const isCountingDown = ref(false)
|
||||
const countdown = ref(60)
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const isPhoneValid = computed(() => /^1[3-9]\d{9}$/.test(mobile.value))
|
||||
const canSendSms = computed(() => isPhoneValid.value && !isCountingDown.value && !sending.value)
|
||||
const canSubmit = computed(
|
||||
() => isPhoneValid.value && code.value.length >= 6 && agreed.value && !submitting.value,
|
||||
)
|
||||
const canWxMiniLogin = computed(() => agreed.value && !wxLoading.value)
|
||||
|
||||
function clearTimer() {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
clearTimer()
|
||||
isCountingDown.value = true
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--
|
||||
}
|
||||
else {
|
||||
clearTimer()
|
||||
isCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function openPhoneForm() {
|
||||
phoneMode.value = true
|
||||
}
|
||||
|
||||
function closePhoneForm() {
|
||||
phoneMode.value = false
|
||||
}
|
||||
|
||||
async function handleSendSms() {
|
||||
if (!canSendSms.value)
|
||||
return
|
||||
if (!isPhoneValid.value) {
|
||||
uni.showToast({ title: '手机号有误', icon: 'none' })
|
||||
return
|
||||
}
|
||||
sending.value = true
|
||||
try {
|
||||
const res = await postAuthSendSms({ mobile: mobile.value }) as { code?: number }
|
||||
if (res && res.code === 200) {
|
||||
uni.showToast({ title: '已发送', icon: 'none' })
|
||||
startCountdown()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!isPhoneValid.value) {
|
||||
uni.showToast({ title: '手机号有误', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (code.value.length < 6) {
|
||||
uni.showToast({ title: '请输入验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!agreed.value) {
|
||||
uni.showToast({ title: '请先勾选协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
const res = await postUserMobileCodeLogin({
|
||||
mobile: mobile.value,
|
||||
code: code.value,
|
||||
}) as { code?: number, data?: { accessToken: string, refreshAfter: number | string, accessExpire: number | string } }
|
||||
if (res && res.code === 200 && res.data) {
|
||||
saveAuthSession(res.data)
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}, 400)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAgree() {
|
||||
agreed.value = !agreed.value
|
||||
}
|
||||
|
||||
function goLegalUserAgreement() {
|
||||
uni.navigateTo({ url: '/pages/legal/user-agreement' })
|
||||
}
|
||||
|
||||
function goLegalPrivacyPolicy() {
|
||||
uni.navigateTo({ url: '/pages/legal/privacy-policy' })
|
||||
}
|
||||
|
||||
function goLegalAuthorization() {
|
||||
uni.navigateTo({ url: '/pages/legal/authorization' })
|
||||
}
|
||||
|
||||
function onMobileInput(e: { detail?: { value?: string } }) {
|
||||
const raw = e.detail?.value ?? ''
|
||||
mobile.value = String(raw).replace(/\D/g, '').slice(0, 11)
|
||||
}
|
||||
|
||||
function onCodeInput(e: { detail?: { value?: string } }) {
|
||||
const raw = e.detail?.value ?? ''
|
||||
code.value = String(raw).replace(/\D/g, '').slice(0, 6)
|
||||
}
|
||||
|
||||
async function handleWxMiniLogin() {
|
||||
if (!agreed.value) {
|
||||
uni.showToast({ title: '请先勾选协议', icon: 'none' })
|
||||
return
|
||||
}
|
||||
wxLoading.value = true
|
||||
try {
|
||||
const ok = await tryWxMiniProgramAuth({ silent: false })
|
||||
if (!ok)
|
||||
return
|
||||
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}, 400)
|
||||
}
|
||||
catch {
|
||||
uni.showToast({ title: '登录失败', icon: 'none' })
|
||||
}
|
||||
finally {
|
||||
wxLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
clearTimer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<view class="bg-blob" />
|
||||
<view class="page">
|
||||
<!-- 入口:仅按钮 + 一行协议 -->
|
||||
<view v-show="!phoneMode" class="gate">
|
||||
<view class="brand-mark" />
|
||||
<view class="brand-title">
|
||||
全能查
|
||||
</view>
|
||||
<view class="brand-sub">
|
||||
安全查车况 · 更放心
|
||||
</view>
|
||||
|
||||
<view class="btn-stack">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view
|
||||
class="btn btn-wx"
|
||||
:class="{ disabled: !canWxMiniLogin }"
|
||||
@tap="handleWxMiniLogin"
|
||||
>
|
||||
{{ wxLoading ? '…' : '微信登录' }}
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view class="btn btn-phone" @tap="openPhoneForm">
|
||||
手机号登录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="agree-row" @tap.stop>
|
||||
<view class="agree-tap" @tap="toggleAgree">
|
||||
<view class="check" :class="{ on: agreed }" />
|
||||
<text class="agree-txt">同意</text>
|
||||
</view>
|
||||
<text class="link" @tap="goLegalUserAgreement">《用户协议》</text>
|
||||
<text class="agree-gap">与</text>
|
||||
<text class="link" @tap="goLegalPrivacyPolicy">《隐私政策》</text>
|
||||
<text class="agree-gap">及</text>
|
||||
<text class="link" @tap="goLegalAuthorization">《授权书》</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手机号表单 -->
|
||||
<view v-show="phoneMode" class="form-sheet">
|
||||
<view class="form-head">
|
||||
<view class="back" @tap="closePhoneForm">
|
||||
‹
|
||||
</view>
|
||||
<text class="form-title">手机号登录</text>
|
||||
<view class="back-spacer" />
|
||||
</view>
|
||||
|
||||
<view class="form-card">
|
||||
<view class="inp-wrap">
|
||||
<text class="inp-label">手机号</text>
|
||||
<input
|
||||
class="inp"
|
||||
type="digit"
|
||||
:value="mobile"
|
||||
:maxlength="11"
|
||||
placeholder="11 位手机号"
|
||||
placeholder-class="inp-ph"
|
||||
confirm-type="done"
|
||||
@input="onMobileInput"
|
||||
>
|
||||
</view>
|
||||
<view class="inp-wrap inp-row">
|
||||
<view class="inp-flex">
|
||||
<text class="inp-label">验证码</text>
|
||||
<input
|
||||
class="inp"
|
||||
type="digit"
|
||||
:value="code"
|
||||
:maxlength="6"
|
||||
placeholder="6 位验证码"
|
||||
placeholder-class="inp-ph"
|
||||
confirm-type="done"
|
||||
@input="onCodeInput"
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="sms"
|
||||
:class="{ disabled: !canSendSms }"
|
||||
@tap="handleSendSms"
|
||||
>
|
||||
{{ isCountingDown ? `${countdown}s` : '获取' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="agree-row agree-form" @tap.stop>
|
||||
<view class="agree-tap" @tap="toggleAgree">
|
||||
<view class="check" :class="{ on: agreed }" />
|
||||
<text class="agree-txt">同意</text>
|
||||
</view>
|
||||
<text class="link" @tap="goLegalUserAgreement">《用户协议》</text>
|
||||
<text class="agree-gap">与</text>
|
||||
<text class="link" @tap="goLegalPrivacyPolicy">《隐私政策》</text>
|
||||
<text class="agree-gap">及</text>
|
||||
<text class="link" @tap="goLegalAuthorization">《授权书》</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="btn btn-submit"
|
||||
:class="{ disabled: !canSubmit }"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
登录
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(165deg, #eef4ff 0%, #f5f7fb 42%, #fafbff 100%);
|
||||
}
|
||||
|
||||
.bg-blob {
|
||||
position: absolute;
|
||||
width: 520rpx;
|
||||
height: 520rpx;
|
||||
right: -120rpx;
|
||||
top: -80rpx;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle at 30% 30%, rgba(23, 104, 255, 0.18), transparent 55%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 56rpx 40rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gate {
|
||||
padding-top: 48rpx;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 24rpx;
|
||||
margin: 0 auto 28rpx;
|
||||
background: linear-gradient(135deg, #1768ff 0%, #4d94ff 100%);
|
||||
box-shadow: 0 16rpx 40rpx rgba(23, 104, 255, 0.28);
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
text-align: center;
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #1d2129;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.brand-sub {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #86909c;
|
||||
margin-top: 12rpx;
|
||||
margin-bottom: 72rpx;
|
||||
}
|
||||
|
||||
.btn-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn-wx {
|
||||
background: #07c160;
|
||||
color: #fff;
|
||||
box-shadow: 0 12rpx 32rpx rgba(7, 193, 96, 0.28);
|
||||
}
|
||||
|
||||
.btn-wx.disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn-phone {
|
||||
background: #fff;
|
||||
color: #1768ff;
|
||||
border: 2rpx solid rgba(23, 104, 255, 0.35);
|
||||
box-shadow: 0 8rpx 24rpx rgba(15, 35, 52, 0.06);
|
||||
}
|
||||
|
||||
.agree-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.agree-tap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.check {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
border: 2rpx solid #c9cdd4;
|
||||
margin-right: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.check.on {
|
||||
border-color: #1768ff;
|
||||
background: #1768ff;
|
||||
}
|
||||
|
||||
.agree-txt {
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.agree-gap {
|
||||
margin: 0 4rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.agree-form {
|
||||
margin-top: 0;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.form-sheet {
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
.form-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.back {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
text-align: center;
|
||||
font-size: 48rpx;
|
||||
color: #1d2129;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.back-spacer {
|
||||
width: 72rpx;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: #fff;
|
||||
border-radius: 28rpx;
|
||||
padding: 8rpx 28rpx 8rpx;
|
||||
border: 1rpx solid rgba(23, 104, 255, 0.08);
|
||||
box-shadow: 0 20rpx 48rpx rgba(15, 35, 52, 0.06);
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.inp-wrap {
|
||||
padding: 22rpx 0;
|
||||
border-bottom: 1rpx solid #f0f1f5;
|
||||
}
|
||||
|
||||
.inp-wrap:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.inp-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.inp-flex {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.inp-label {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.inp {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
font-size: 32rpx;
|
||||
color: #1d2129;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.inp-ph {
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.sms {
|
||||
flex-shrink: 0;
|
||||
height: 68rpx;
|
||||
line-height: 68rpx;
|
||||
padding: 0 28rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #1768ff;
|
||||
background: #f0f5ff;
|
||||
border-radius: 34rpx;
|
||||
}
|
||||
|
||||
.sms.disabled {
|
||||
color: #c9cdd4;
|
||||
background: #f7f8fa;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
background: linear-gradient(90deg, #1768ff 0%, #4d94ff 100%);
|
||||
color: #fff;
|
||||
box-shadow: 0 16rpx 40rpx rgba(23, 104, 255, 0.28);
|
||||
}
|
||||
|
||||
.btn-submit.disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
982
src/pages/mine.vue
Normal file
982
src/pages/mine.vue
Normal file
@@ -0,0 +1,982 @@
|
||||
<script setup lang="ts">
|
||||
import { onShareAppMessage, onShareTimeline, onShow } from '@dcloudio/uni-app'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
import { clearAuthStorage, getUserDetail, postAuthSendSmsBindMobile, postUserBindMobile } from '@/api'
|
||||
import { hasToken, saveAuthSession } from '@/utils/session'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '我的',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
// 微信小程序:允许使用 open-type="share" 与右上角转发
|
||||
enableShareAppMessage: true,
|
||||
enableShareTimeline: true,
|
||||
},
|
||||
})
|
||||
|
||||
/** 商务合作弹窗中的二维码图:将图片放到 `src/static/` 后改为 `/static/xxx.png` 或填网络地址 */
|
||||
const BUSINESS_COOP_QR_SRC = ''
|
||||
|
||||
const SHARE_TITLE = '全能查 — 买车先查车况,更安心'
|
||||
const SHARE_PATH = '/pages/index'
|
||||
|
||||
const WX_NICK_KEY = 'wx_display_name'
|
||||
|
||||
const isLogin = ref(false)
|
||||
const nickname = ref('')
|
||||
const userDesc = ref('')
|
||||
const hasBoundMobile = ref(false)
|
||||
const wxNickStorage = ref('')
|
||||
|
||||
const bindModalOpen = ref(false)
|
||||
const bindPhone = ref('')
|
||||
const bindCode = ref('')
|
||||
const bindSending = ref(false)
|
||||
const bindSubmitting = ref(false)
|
||||
const bindCountingDown = ref(false)
|
||||
const bindCountdown = ref(60)
|
||||
let bindSmsTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const coopModalOpen = ref(false)
|
||||
|
||||
onShareAppMessage(() => ({
|
||||
title: SHARE_TITLE,
|
||||
path: SHARE_PATH,
|
||||
}))
|
||||
|
||||
onShareTimeline(() => ({
|
||||
title: SHARE_TITLE,
|
||||
query: '',
|
||||
}))
|
||||
|
||||
function maskMobile(plain: string) {
|
||||
const s = (plain || '').trim()
|
||||
if (s.length >= 11)
|
||||
return `${s.slice(0, 3)}****${s.slice(-4)}`
|
||||
return s || ''
|
||||
}
|
||||
|
||||
function loadWxNickFromStorage() {
|
||||
try {
|
||||
const n = uni.getStorageSync(WX_NICK_KEY)
|
||||
wxNickStorage.value = typeof n === 'string' && n ? n : ''
|
||||
}
|
||||
catch {
|
||||
wxNickStorage.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function clearBindSmsTimer() {
|
||||
if (bindSmsTimer) {
|
||||
clearInterval(bindSmsTimer)
|
||||
bindSmsTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function startBindCountdown() {
|
||||
clearBindSmsTimer()
|
||||
bindCountingDown.value = true
|
||||
bindCountdown.value = 60
|
||||
bindSmsTimer = setInterval(() => {
|
||||
if (bindCountdown.value > 0) {
|
||||
bindCountdown.value--
|
||||
}
|
||||
else {
|
||||
clearBindSmsTimer()
|
||||
bindCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const isBindPhoneValid = () => /^1[3-9]\d{9}$/.test(bindPhone.value)
|
||||
|
||||
async function refreshUserCard() {
|
||||
isLogin.value = hasToken()
|
||||
if (!isLogin.value) {
|
||||
nickname.value = ''
|
||||
userDesc.value = ''
|
||||
hasBoundMobile.value = false
|
||||
return
|
||||
}
|
||||
loadWxNickFromStorage()
|
||||
try {
|
||||
const res = await getUserDetail() as {
|
||||
code?: number
|
||||
data?: { userInfo?: { mobile?: string, nickName?: string } }
|
||||
}
|
||||
if (res && res.code === 200 && res.data?.userInfo) {
|
||||
const u = res.data.userInfo
|
||||
const mobile = (u.mobile || '').trim()
|
||||
const apiNick = (u.nickName || '').trim()
|
||||
hasBoundMobile.value = /^1[3-9]\d{9}$/.test(mobile)
|
||||
if (hasBoundMobile.value) {
|
||||
nickname.value = maskMobile(mobile)
|
||||
userDesc.value = '已绑定手机号,可同步历史报告与收藏'
|
||||
}
|
||||
else {
|
||||
nickname.value = wxNickStorage.value || apiNick || '微信用户'
|
||||
userDesc.value = '绑定手机号后,可同步历史报告与收藏'
|
||||
}
|
||||
}
|
||||
else {
|
||||
nickname.value = wxNickStorage.value || '已登录'
|
||||
userDesc.value = '获取资料失败,请稍后下拉刷新'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
nickname.value = wxNickStorage.value || '已登录'
|
||||
userDesc.value = '网络异常,请稍后重试'
|
||||
}
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
void refreshUserCard()
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
uni.showShareMenu({
|
||||
withShareTicket: true,
|
||||
menus: ['shareAppMessage', 'shareTimeline'],
|
||||
})
|
||||
}
|
||||
catch {
|
||||
/* ignore */
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
function handleUserTap() {
|
||||
if (isLogin.value) {
|
||||
uni.showActionSheet({
|
||||
itemList: ['退出登录'],
|
||||
success(res) {
|
||||
if (res.tapIndex === 0) {
|
||||
clearAuthStorage()
|
||||
isLogin.value = false
|
||||
nickname.value = ''
|
||||
userDesc.value = ''
|
||||
hasBoundMobile.value = false
|
||||
uni.showToast({ title: '已退出', icon: 'none' })
|
||||
}
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: '/pages/login' })
|
||||
}
|
||||
|
||||
/** 微信小程序:用户主动触发才可调 getUserProfile */
|
||||
async function syncWxNickname() {
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
const res = await new Promise<{ userInfo?: { nickName?: string } }>((resolve, reject) => {
|
||||
uni.getUserProfile({
|
||||
desc: '用于在本页展示昵称',
|
||||
lang: 'zh_CN',
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
const name = res.userInfo?.nickName?.trim()
|
||||
if (name) {
|
||||
uni.setStorageSync(WX_NICK_KEY, name)
|
||||
wxNickStorage.value = name
|
||||
if (!hasBoundMobile.value)
|
||||
nickname.value = name
|
||||
uni.showToast({ title: '昵称已更新', icon: 'none' })
|
||||
}
|
||||
}
|
||||
catch {
|
||||
uni.showToast({ title: '需要您确认授权', icon: 'none' })
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
function openBindModal() {
|
||||
bindPhone.value = ''
|
||||
bindCode.value = ''
|
||||
bindModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeBindModal() {
|
||||
bindModalOpen.value = false
|
||||
clearBindSmsTimer()
|
||||
bindCountingDown.value = false
|
||||
}
|
||||
|
||||
function onBindPhoneInput(e: { detail?: { value?: string } }) {
|
||||
const raw = e.detail?.value ?? ''
|
||||
bindPhone.value = String(raw).replace(/\D/g, '').slice(0, 11)
|
||||
}
|
||||
|
||||
function onBindCodeInput(e: { detail?: { value?: string } }) {
|
||||
const raw = e.detail?.value ?? ''
|
||||
bindCode.value = String(raw).replace(/\D/g, '').slice(0, 6)
|
||||
}
|
||||
|
||||
async function sendBindSms() {
|
||||
if (bindSending.value || bindCountingDown.value)
|
||||
return
|
||||
if (!isBindPhoneValid()) {
|
||||
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
bindSending.value = true
|
||||
try {
|
||||
const res = await postAuthSendSmsBindMobile({ mobile: bindPhone.value }) as { code?: number }
|
||||
if (res && res.code === 200) {
|
||||
uni.showToast({ title: '验证码已发送', icon: 'none' })
|
||||
startBindCountdown()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
bindSending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitBindMobile() {
|
||||
if (!isBindPhoneValid()) {
|
||||
uni.showToast({ title: '请输入正确手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (bindCode.value.length < 6) {
|
||||
uni.showToast({ title: '请输入 6 位验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
bindSubmitting.value = true
|
||||
try {
|
||||
const res = await postUserBindMobile({
|
||||
mobile: bindPhone.value,
|
||||
code: bindCode.value,
|
||||
}) as { code?: number, data?: { accessToken: string, refreshAfter: number | string, accessExpire: number | string } }
|
||||
if (res && res.code === 200 && res.data) {
|
||||
saveAuthSession(res.data)
|
||||
uni.showToast({ title: '绑定成功', icon: 'success' })
|
||||
closeBindModal()
|
||||
await refreshUserCard()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
bindSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goHistoryReport() {
|
||||
uni.switchTab({ url: '/pages/report' })
|
||||
}
|
||||
|
||||
function goFreeValuation() {
|
||||
uni.showToast({ title: '敬请期待', icon: 'none' })
|
||||
}
|
||||
|
||||
/** 非微信小程序:无 open-type=share,仅提示 */
|
||||
function goShareFallback() {
|
||||
uni.showToast({ title: '请在微信小程序内使用分享', icon: 'none' })
|
||||
}
|
||||
|
||||
function openCoopModal() {
|
||||
coopModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeCoopModal() {
|
||||
coopModalOpen.value = false
|
||||
}
|
||||
|
||||
function goLegalUserAgreement() {
|
||||
uni.navigateTo({ url: '/pages/legal/user-agreement' })
|
||||
}
|
||||
|
||||
function goLegalPrivacyPolicy() {
|
||||
uni.navigateTo({ url: '/pages/legal/privacy-policy' })
|
||||
}
|
||||
|
||||
function goLegalAuthorization() {
|
||||
uni.navigateTo({ url: '/pages/legal/authorization' })
|
||||
}
|
||||
|
||||
function goIllegalCode() {
|
||||
uni.showToast({ title: '敬请期待', icon: 'none' })
|
||||
}
|
||||
|
||||
function goOilPrice() {
|
||||
uni.showToast({ title: '敬请期待', icon: 'none' })
|
||||
}
|
||||
|
||||
function goHelp() {
|
||||
uni.showToast({ title: '敬请期待', icon: 'none' })
|
||||
}
|
||||
|
||||
function goAbout() {
|
||||
uni.showToast({ title: '敬请期待', icon: 'none' })
|
||||
}
|
||||
|
||||
function goSettings() {
|
||||
uni.showToast({ title: '敬请期待', icon: 'none' })
|
||||
}
|
||||
|
||||
/** 非微信小程序:无 open-type=contact */
|
||||
function goServiceFallback() {
|
||||
uni.showToast({ title: '请在微信小程序内使用在线客服', icon: 'none' })
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
clearBindSmsTimer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<scroll-view scroll-y class="scrollarea">
|
||||
<view class="page">
|
||||
<view class="banner mine-banner">
|
||||
<view class="banner-text">
|
||||
<view class="banner-title">
|
||||
买车先查车况,更安心
|
||||
</view>
|
||||
<view class="banner-sub">
|
||||
减少隐蔽事故车、泡水车、调表车风险
|
||||
</view>
|
||||
</view>
|
||||
<view class="banner-illustration">
|
||||
<view class="car-illus" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card mine-user">
|
||||
<view class="mine-user-main" @tap="handleUserTap">
|
||||
<view class="mine-avatar-placeholder" />
|
||||
<view class="mine-user-text">
|
||||
<view class="mine-user-name">
|
||||
{{ isLogin ? nickname : '您还没有登录,立即登录' }}
|
||||
</view>
|
||||
<view class="mine-user-desc">
|
||||
{{ isLogin ? userDesc : '登录后可同步历史报告与收藏' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view v-if="isLogin && !hasBoundMobile" class="mine-user-extra">
|
||||
<text class="extra-link" @tap.stop="syncWxNickname">同步微信昵称</text>
|
||||
<text class="extra-dot">·</text>
|
||||
<text class="extra-hint">绑定手机后优先显示手机号</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view
|
||||
v-if="isLogin && !hasBoundMobile"
|
||||
class="mine-bind-row"
|
||||
@tap.stop="openBindModal"
|
||||
>
|
||||
<text class="mine-bind-text">绑定手机号</text>
|
||||
<text class="mine-bind-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="grid-4">
|
||||
<view class="grid-item" @tap="goHistoryReport">
|
||||
<view class="icon-tool i-carbon-document" />
|
||||
<view class="grid-text">
|
||||
历史报告
|
||||
</view>
|
||||
</view>
|
||||
<view class="grid-item" @tap="goFreeValuation">
|
||||
<view class="icon-tool i-carbon-chart-line" />
|
||||
<view class="grid-text">
|
||||
免费估值
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="grid-item grid-item-btn" open-type="share" hover-class="grid-item-hover">
|
||||
<view class="icon-tool i-carbon-share" />
|
||||
<view class="grid-text">
|
||||
分享好友
|
||||
</view>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view class="grid-item" @tap="goShareFallback">
|
||||
<view class="icon-tool i-carbon-share" />
|
||||
<view class="grid-text">
|
||||
分享好友
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view class="grid-item" @tap="openCoopModal">
|
||||
<view class="icon-tool i-carbon-enterprise" />
|
||||
<view class="grid-text">
|
||||
商务合作
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card list-card">
|
||||
<view class="list-item" @tap="goLegalUserAgreement">
|
||||
<view class="list-icon i-carbon-document-blank" />
|
||||
<text class="list-text">用户协议</text>
|
||||
</view>
|
||||
<view class="list-item" @tap="goLegalPrivacyPolicy">
|
||||
<view class="list-icon i-carbon-security" />
|
||||
<text class="list-text">隐私政策</text>
|
||||
</view>
|
||||
<view class="list-item" @tap="goLegalAuthorization">
|
||||
<view class="list-icon i-carbon-certificate" />
|
||||
<text class="list-text">授权书</text>
|
||||
</view>
|
||||
<view class="list-item" @tap="goHelp">
|
||||
<view class="list-icon i-carbon-help" />
|
||||
<text class="list-text">帮助中心</text>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<button
|
||||
class="list-item list-item-contact no-border"
|
||||
open-type="contact"
|
||||
hover-class="list-item-contact-hover"
|
||||
>
|
||||
<view class="list-icon i-carbon-chat" />
|
||||
<view class="service-row">
|
||||
<text class="list-text">在线客服</text>
|
||||
<text class="service-time">人工客服 周一至周日 9:00-20:00</text>
|
||||
</view>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view v-if="coopModalOpen" class="coop-mask" @tap.self="closeCoopModal">
|
||||
<view class="coop-dialog" @tap.stop>
|
||||
<view class="coop-title">
|
||||
商务合作
|
||||
</view>
|
||||
<view class="coop-hint">
|
||||
扫码添加商务微信(图片路径可在代码中配置)
|
||||
</view>
|
||||
<image
|
||||
v-if="BUSINESS_COOP_QR_SRC"
|
||||
class="coop-qr"
|
||||
:src="BUSINESS_COOP_QR_SRC"
|
||||
mode="aspectFit"
|
||||
show-menu-by-longpress
|
||||
/>
|
||||
<view v-else class="coop-qr-placeholder">
|
||||
<text class="coop-qr-placeholder-text">二维码图片路径待定</text>
|
||||
<text class="coop-qr-placeholder-text coop-qr-placeholder-sub">请将图片放入 static 目录,并在 mine.vue 中为 BUSINESS_COOP_QR_SRC 赋值</text>
|
||||
</view>
|
||||
<view class="coop-close" @tap="closeCoopModal">
|
||||
知道了
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="bindModalOpen" class="bind-mask" @tap.self="closeBindModal">
|
||||
<view class="bind-sheet" @tap.stop>
|
||||
<view class="bind-sheet-title">
|
||||
绑定手机号
|
||||
</view>
|
||||
<view class="bind-field">
|
||||
<text class="bind-label">手机号</text>
|
||||
<input
|
||||
class="bind-input"
|
||||
type="digit"
|
||||
:value="bindPhone"
|
||||
:maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
placeholder-class="bind-ph"
|
||||
confirm-type="done"
|
||||
@input="onBindPhoneInput"
|
||||
>
|
||||
</view>
|
||||
<view class="bind-field bind-field-row">
|
||||
<view class="bind-field-grow">
|
||||
<text class="bind-label">验证码</text>
|
||||
<input
|
||||
class="bind-input"
|
||||
type="digit"
|
||||
:value="bindCode"
|
||||
:maxlength="6"
|
||||
placeholder="6 位短信码"
|
||||
placeholder-class="bind-ph"
|
||||
confirm-type="done"
|
||||
@input="onBindCodeInput"
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="bind-sms"
|
||||
:class="{ disabled: bindSending || bindCountingDown || !isBindPhoneValid() }"
|
||||
@tap="sendBindSms"
|
||||
>
|
||||
{{ bindCountingDown ? `${bindCountdown}s` : '获取验证码' }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="bind-submit"
|
||||
:class="{ disabled: bindSubmitting || !isBindPhoneValid() || bindCode.length < 6 }"
|
||||
@tap="submitBindMobile"
|
||||
>
|
||||
确认绑定
|
||||
</view>
|
||||
<view class="bind-cancel" @tap="closeBindModal">
|
||||
取消
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #f8faff 0%, #f3f5fb 100%);
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24rpx 24rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: flex;
|
||||
padding: 32rpx 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
background: linear-gradient(135deg, #fff7f0 0%, #ffffff 100%);
|
||||
border-radius: 24rpx;
|
||||
box-shadow:
|
||||
0 18rpx 40rpx rgba(15, 35, 52, 0.05),
|
||||
0 0 0 1rpx rgba(226, 229, 239, 0.9);
|
||||
}
|
||||
|
||||
.banner-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.banner-sub {
|
||||
font-size: 24rpx;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.banner-illustration {
|
||||
width: 180rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.car-illus {
|
||||
width: 160rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 16rpx;
|
||||
background: linear-gradient(145deg, #ffe8dc 0%, #ffc9a8 100%);
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
box-shadow:
|
||||
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
|
||||
.mine-user {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.mine-user-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mine-avatar-placeholder {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f2f3f5;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mine-user-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mine-user-name {
|
||||
font-size: 28rpx;
|
||||
color: #1d2129;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.mine-user-desc {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.mine-user-extra {
|
||||
margin-top: 16rpx;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.extra-link {
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.extra-dot {
|
||||
margin: 0 8rpx;
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.extra-hint {
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.mine-bind-row {
|
||||
margin-top: 12rpx;
|
||||
padding: 16rpx 0 4rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mine-bind-text {
|
||||
font-size: 26rpx;
|
||||
color: #1768ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mine-bind-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #1768ff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.bind-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bind-sheet {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
padding: 28rpx 28rpx calc(28rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bind-sheet-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 24rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bind-field {
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 12rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.bind-field-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.bind-field-grow {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bind-label {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.bind-input {
|
||||
width: 100%;
|
||||
height: 72rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1d2129;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bind-ph {
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.bind-sms {
|
||||
flex-shrink: 0;
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #1768ff;
|
||||
background: #f0f5ff;
|
||||
border-radius: 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bind-sms.disabled {
|
||||
color: #c9cdd4;
|
||||
background: #f7f8fa;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bind-submit {
|
||||
margin-top: 12rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
background: linear-gradient(90deg, #1768ff 0%, #4d94ff 100%);
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 44rpx;
|
||||
}
|
||||
|
||||
.bind-submit.disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bind-cancel {
|
||||
margin-top: 20rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #86909c;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.grid-4 {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
.grid-item-btn {
|
||||
margin: 0;
|
||||
padding: 8rpx 0 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
line-height: normal;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.grid-item-btn::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.grid-item-hover {
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.coop-mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1001;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coop-dialog {
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 36rpx 32rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coop-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
text-align: center;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.coop-hint {
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
text-align: center;
|
||||
margin-bottom: 28rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.coop-qr {
|
||||
display: block;
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
margin: 0 auto 28rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.coop-qr-placeholder {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
margin: 0 auto 28rpx;
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx dashed #dcdfe6;
|
||||
background: #fafbfc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.coop-qr-placeholder-text {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.coop-qr-placeholder-sub {
|
||||
font-size: 20rpx;
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.coop-close {
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background: linear-gradient(90deg, #1768ff 0%, #4d94ff 100%);
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
.icon-tool {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
margin-bottom: 8rpx;
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.grid-text {
|
||||
font-size: 24rpx;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
height: 96rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.list-item-contact {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
line-height: inherit;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.list-item-contact::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-item-contact-hover {
|
||||
background: rgba(23, 104, 255, 0.06);
|
||||
}
|
||||
|
||||
.list-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.list-text {
|
||||
font-size: 26rpx;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.service-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.service-time {
|
||||
font-size: 22rpx;
|
||||
color: #ffb020;
|
||||
}
|
||||
</style>
|
||||
325
src/pages/report.vue
Normal file
325
src/pages/report.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<script setup lang="ts">
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { getQueryList } from '@/api'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '查询报告',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
interface ReportItem {
|
||||
id: string
|
||||
orderId: number
|
||||
typeText: string
|
||||
status: string
|
||||
statusText: string
|
||||
vin: string
|
||||
model: string
|
||||
time: string
|
||||
}
|
||||
|
||||
const totalCount = ref(0)
|
||||
const reportList = ref<ReportItem[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
function readQueryParams(row: Record<string, unknown>): Record<string, unknown> {
|
||||
const raw = row.query_params
|
||||
if (raw && typeof raw === 'object' && !Array.isArray(raw))
|
||||
return raw as Record<string, unknown>
|
||||
return {}
|
||||
}
|
||||
|
||||
function pickVin(qp: Record<string, unknown>): string {
|
||||
const keys = ['vin_code', 'vin', 'frame_no', 'VIN', '车架号']
|
||||
for (const k of keys) {
|
||||
const v = qp[k]
|
||||
if (typeof v === 'string' && v.trim())
|
||||
return v.trim()
|
||||
if (v != null && typeof v !== 'object' && typeof v !== 'undefined')
|
||||
return String(v).trim()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function pickModel(qp: Record<string, unknown>): string {
|
||||
const keys = ['model', 'vehicle_model', 'car_model', '车型', 'name', 'car_name']
|
||||
for (const k of keys) {
|
||||
const v = qp[k]
|
||||
if (typeof v === 'string' && v.trim())
|
||||
return v.trim()
|
||||
if (v != null && typeof v !== 'object' && typeof v !== 'undefined')
|
||||
return String(v).trim()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function queryStateToUi(state: string): { status: string, statusText: string } {
|
||||
switch (state) {
|
||||
case 'success':
|
||||
return { status: 'success', statusText: '已完成' }
|
||||
case 'failed':
|
||||
return { status: 'failed', statusText: '失败' }
|
||||
case 'pending':
|
||||
return { status: 'pending', statusText: '待处理' }
|
||||
case 'processing':
|
||||
return { status: 'processing', statusText: '查询中' }
|
||||
case 'cleaned':
|
||||
return { status: 'cleaned', statusText: '已清理' }
|
||||
case 'refunded':
|
||||
return { status: 'refunded', statusText: '已退款' }
|
||||
default:
|
||||
return { status: '', statusText: state || '未知' }
|
||||
}
|
||||
}
|
||||
|
||||
function mapRow(row: Record<string, unknown>): ReportItem {
|
||||
const id = row.id != null ? String(row.id) : ''
|
||||
const orderId = Number(row.order_id)
|
||||
const qp = readQueryParams(row)
|
||||
const vin = pickVin(qp) || '—'
|
||||
let model = pickModel(qp)
|
||||
if (!model && typeof row.product === 'string' && row.product.trim())
|
||||
model = row.product.trim()
|
||||
if (!model)
|
||||
model = '—'
|
||||
|
||||
const typeText = typeof row.product_name === 'string' && row.product_name.trim()
|
||||
? row.product_name.trim()
|
||||
: '查询报告'
|
||||
const { status, statusText } = queryStateToUi(
|
||||
typeof row.query_state === 'string' ? row.query_state : '',
|
||||
)
|
||||
const time = typeof row.create_time === 'string' ? row.create_time : ''
|
||||
|
||||
return { id, orderId, typeText, status, statusText, vin, model, time }
|
||||
}
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const raw = await getQueryList({ page: 1, pageSize: 50 }, { skipLoading: true })
|
||||
const res = raw as { code?: number, data?: { total?: number, list?: Record<string, unknown>[] } }
|
||||
if (res?.code === 200 && res.data) {
|
||||
const listRaw = Array.isArray(res.data.list) ? res.data.list : []
|
||||
totalCount.value = typeof res.data.total === 'number' ? res.data.total : listRaw.length
|
||||
reportList.value = listRaw.map(r => mapRow(r))
|
||||
}
|
||||
else {
|
||||
totalCount.value = 0
|
||||
reportList.value = []
|
||||
}
|
||||
}
|
||||
catch {
|
||||
totalCount.value = 0
|
||||
reportList.value = []
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
void loadList()
|
||||
})
|
||||
|
||||
function handleReportTap(item: ReportItem) {
|
||||
if (!item.orderId) {
|
||||
uni.showToast({ title: '无法打开报告', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/report/detail?orderId=${encodeURIComponent(String(item.orderId))}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<scroll-view scroll-y class="scrollarea">
|
||||
<view class="page">
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">历史报告</text>
|
||||
<text class="card-sub">共 {{ totalCount }} 条</text>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="list-loading">
|
||||
加载中…
|
||||
</view>
|
||||
|
||||
<template v-else-if="reportList.length">
|
||||
<view
|
||||
v-for="item in reportList"
|
||||
:key="item.id"
|
||||
class="report-item"
|
||||
@tap="handleReportTap(item)"
|
||||
>
|
||||
<view class="report-top">
|
||||
<text class="report-type">{{ item.typeText }}</text>
|
||||
<text class="report-status" :class="item.status">{{ item.statusText }}</text>
|
||||
</view>
|
||||
<view class="report-middle">
|
||||
<text class="report-vin">{{ item.vin }}</text>
|
||||
<text class="report-model">{{ item.model }}</text>
|
||||
</view>
|
||||
<view class="report-bottom">
|
||||
<text class="report-time">{{ item.time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view v-else class="empty">
|
||||
<view class="empty-title">
|
||||
暂无报告
|
||||
</view>
|
||||
<view class="empty-desc">
|
||||
先去首页发起一次查询,报告会自动出现在这里
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #f8faff 0%, #f3f5fb 100%);
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24rpx 24rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
box-shadow:
|
||||
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.card-sub {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.list-loading {
|
||||
padding: 32rpx 0;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.report-item {
|
||||
padding: 18rpx 0;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.report-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.report-type {
|
||||
font-size: 24rpx;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.report-status {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 999rpx;
|
||||
background-color: #f2f3f5;
|
||||
color: #4e5969;
|
||||
}
|
||||
|
||||
.report-status.success {
|
||||
background-color: #e8fffb;
|
||||
color: #15bb8a;
|
||||
}
|
||||
|
||||
.report-status.failed {
|
||||
background-color: #fff1f0;
|
||||
color: #f53f3f;
|
||||
}
|
||||
|
||||
.report-status.pending,
|
||||
.report-status.processing {
|
||||
background-color: #e8f3ff;
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.report-status.cleaned,
|
||||
.report-status.refunded {
|
||||
background-color: #f2f3f5;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.report-middle {
|
||||
font-size: 22rpx;
|
||||
color: #4e5969;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.report-vin {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.report-model {
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.report-bottom {
|
||||
font-size: 20rpx;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 40rpx 10rpx 10rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 26rpx;
|
||||
color: #1d2129;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
</style>
|
||||
100
src/pages/report/detail.vue
Normal file
100
src/pages/report/detail.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<script setup>
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { ref } from 'vue'
|
||||
import { getQueryDetailByOrderId, getQueryDetailByOrderNo } from '@/api'
|
||||
import VehicleReportShell from '@/components/report/VehicleReportShell.vue'
|
||||
import { normalizeVehicleQueryData } from '@/utils/vehicleReportNormalize'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '报告详情',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
const orderNo = ref('')
|
||||
const orderId = ref('')
|
||||
const loading = ref(true)
|
||||
const errText = ref('')
|
||||
const productName = ref('')
|
||||
const queryParams = ref({})
|
||||
const rows = ref(normalizeVehicleQueryData([]))
|
||||
|
||||
onLoad((options) => {
|
||||
orderNo.value = options?.orderNo || ''
|
||||
orderId.value = options?.orderId || ''
|
||||
void load()
|
||||
})
|
||||
|
||||
async function load() {
|
||||
if (!orderNo.value && !orderId.value) {
|
||||
loading.value = false
|
||||
errText.value = '缺少订单信息'
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
errText.value = ''
|
||||
try {
|
||||
const res = orderId.value
|
||||
? await getQueryDetailByOrderId(orderId.value)
|
||||
: await getQueryDetailByOrderNo(orderNo.value)
|
||||
if (res?.code === 200 && res.data) {
|
||||
productName.value = res.data.product_name || '查询报告'
|
||||
queryParams.value = res.data.query_params || {}
|
||||
rows.value = normalizeVehicleQueryData(res.data.query_data || [])
|
||||
if (!rows.value.length)
|
||||
errText.value = '暂无报告模块数据'
|
||||
}
|
||||
else {
|
||||
errText.value = res?.msg || '加载失败'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
errText.value = '网络异常或未登录'
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<view v-if="loading" class="state">
|
||||
加载中…
|
||||
</view>
|
||||
<view v-else-if="errText && !rows.length" class="state">
|
||||
{{ errText }}
|
||||
</view>
|
||||
<view v-else class="content">
|
||||
<VehicleReportShell
|
||||
mode="detail"
|
||||
:product-name="productName"
|
||||
:query-params="queryParams"
|
||||
:rows="rows"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
min-height: 100vh;
|
||||
background: #d2dffa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.state {
|
||||
padding: 100rpx 32rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24rpx 24rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
205
src/pages/toolbox/category.vue
Normal file
205
src/pages/toolbox/category.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { toolboxCategories, getCategoryAllTools } from '@/config/toolboxRegistry'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '分类工具',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
const categoryKey = ref('')
|
||||
const category = ref<any>(null)
|
||||
const tools = ref<any[]>([])
|
||||
|
||||
onLoad((query) => {
|
||||
const key = (query?.category as string) || ''
|
||||
categoryKey.value = key
|
||||
|
||||
const cat = toolboxCategories.find(c => c.key === key)
|
||||
if (cat) {
|
||||
category.value = cat
|
||||
tools.value = getCategoryAllTools(key)
|
||||
uni.setNavigationBarTitle({ title: cat.name })
|
||||
}
|
||||
})
|
||||
|
||||
function goTool(key: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/toolbox/query?key=${encodeURIComponent(key)}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<scroll-view scroll-y class="scrollarea">
|
||||
<view class="page">
|
||||
<view v-if="category" class="cat-header">
|
||||
<view class="cat-icon-large" :style="{ background: `${category.color}15` }">
|
||||
<view :class="['icon', category.icon]" :style="{ color: category.color }" />
|
||||
</view>
|
||||
<view class="cat-info">
|
||||
<text class="cat-name">{{ category.name }}</text>
|
||||
<text class="cat-count">共 {{ tools.length }} 个工具</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tool-list">
|
||||
<view
|
||||
v-for="item in tools"
|
||||
:key="item.key"
|
||||
class="tool-item"
|
||||
@tap="goTool(item.key)"
|
||||
>
|
||||
<view class="item-icon-wrap" :style="{ background: category ? `${category.color}12` : '#e8f0fe' }">
|
||||
<view :class="['item-icon', item.icon]" :style="{ color: category?.color || '#1768ff' }" />
|
||||
</view>
|
||||
<view class="item-content">
|
||||
<text class="item-name">{{ item.name }}</text>
|
||||
<text class="item-desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
<!-- <text class="item-arrow"></text> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #f8faff 0%, #f3f5fb 100%);
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24rpx 24rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow:
|
||||
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
|
||||
.cat-icon-large {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 44rpx;
|
||||
}
|
||||
|
||||
.cat-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cat-name {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.cat-count {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.tool-list {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
|
||||
border-radius: 24rpx;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
|
||||
.tool-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 24rpx 28rpx;
|
||||
border-bottom: 1rpx solid #f2f3f5;
|
||||
}
|
||||
|
||||
.tool-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.tool-item:active {
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.item-icon-wrap {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 18rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #c9cdd4;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
199
src/pages/toolbox/index.vue
Normal file
199
src/pages/toolbox/index.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<script setup lang="ts">
|
||||
import { toolboxCategories, getCategoryHotTools } from '@/config/toolboxRegistry'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '实用工具',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
},
|
||||
})
|
||||
|
||||
function goTool(key: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/toolbox/query?key=${encodeURIComponent(key)}`,
|
||||
})
|
||||
}
|
||||
|
||||
function goCategory(categoryKey: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/toolbox/category?category=${encodeURIComponent(categoryKey)}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<scroll-view scroll-y class="scrollarea">
|
||||
<view class="page">
|
||||
<view
|
||||
v-for="cat in toolboxCategories"
|
||||
:key="cat.key"
|
||||
class="card"
|
||||
>
|
||||
<!-- 分类标题 -->
|
||||
<view class="card-header" @tap="goCategory(cat.key)">
|
||||
<view class="card-header-left">
|
||||
<view class="cat-icon-wrap" :style="{ background: `${cat.color}15` }">
|
||||
<view :class="['cat-icon', cat.icon]" :style="{ color: cat.color }" />
|
||||
</view>
|
||||
<text class="card-title">{{ cat.name }}</text>
|
||||
</view>
|
||||
<view class="card-more">
|
||||
<text class="more-text">查看更多</text>
|
||||
<!-- <text class="more-arrow">></text> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 热门工具网格 -->
|
||||
<view class="tool-grid">
|
||||
<view
|
||||
v-for="item in getCategoryHotTools(cat.key)"
|
||||
:key="item.key"
|
||||
class="tool-cell"
|
||||
@tap="goTool(item.key)"
|
||||
>
|
||||
<view class="tool-icon-wrap" :style="{ background: `${cat.color}12` }">
|
||||
<view :class="['tool-icon', item.icon]" :style="{ color: cat.color }" />
|
||||
</view>
|
||||
<text class="tool-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #f8faff 0%, #f3f5fb 100%);
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24rpx 24rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 24rpx 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
box-shadow:
|
||||
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.card-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.cat-icon-wrap {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cat-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.card-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #f2f3f5;
|
||||
}
|
||||
|
||||
.more-text {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.more-arrow {
|
||||
font-size: 22rpx;
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.card-more:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.tool-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -6rpx;
|
||||
}
|
||||
|
||||
.tool-cell {
|
||||
width: 33.333%;
|
||||
padding: 6rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 12rpx;
|
||||
padding-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.tool-cell:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.tool-icon-wrap {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.tool-icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
610
src/pages/toolbox/query.vue
Normal file
610
src/pages/toolbox/query.vue
Normal file
@@ -0,0 +1,610 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getToolboxItem } from '@/config/toolboxRegistry'
|
||||
import { postToolboxQuery } from '@/api/toolbox'
|
||||
|
||||
definePage({
|
||||
style: {
|
||||
navigationBarTitleText: '工具查询',
|
||||
navigationStyle: 'default',
|
||||
navigationBarBackgroundColor: '#ffffff',
|
||||
navigationBarTextStyle: 'black',
|
||||
enablePullDownRefresh: false,
|
||||
},
|
||||
})
|
||||
|
||||
const toolKey = ref('')
|
||||
const tool = ref<ReturnType<typeof getToolboxItem>>(null)
|
||||
|
||||
const form = ref<Record<string, string>>({})
|
||||
const loading = ref(false)
|
||||
const result = ref<Record<string, any> | null>(null)
|
||||
const error = ref('')
|
||||
|
||||
// 选择框相关状态
|
||||
const popup = ref<any>(null)
|
||||
const currentField = ref<any>(null)
|
||||
const pickerValue = ref([0])
|
||||
const indicatorStyle = `'height: 50px'`
|
||||
|
||||
onLoad((query) => {
|
||||
const key = (query?.key as string) || ''
|
||||
toolKey.value = key
|
||||
tool.value = getToolboxItem(key)
|
||||
if (!tool.value) {
|
||||
error.value = '未找到该工具'
|
||||
}
|
||||
else {
|
||||
uni.setNavigationBarTitle({ title: tool.value.name })
|
||||
|
||||
// 初始化表单默认值
|
||||
if (tool.value.fields) {
|
||||
tool.value.fields.forEach(field => {
|
||||
if (field.default !== undefined && !form.value[field.key]) {
|
||||
form.value[field.key] = field.default
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 如果工具标记了自动查询或不需要输入参数,打开即查
|
||||
if (tool.value.autoQuery || tool.value.fields.length === 0) {
|
||||
handleQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 获取选择框当前选中项的索引
|
||||
function getSelectIndex(field, value) {
|
||||
if (!field.options || !Array.isArray(field.options)) {
|
||||
return 0
|
||||
}
|
||||
const defaultValue = field.options[0]?.value || ''
|
||||
const targetValue = value || defaultValue
|
||||
return field.options.findIndex(opt => opt.value === targetValue)
|
||||
}
|
||||
|
||||
// 获取选择框当前选中项的标签
|
||||
function getSelectedLabel(field, value) {
|
||||
if (!field.options || !Array.isArray(field.options) || !value) {
|
||||
return null
|
||||
}
|
||||
const option = field.options.find(opt => opt.value === value)
|
||||
return option ? option.label : null
|
||||
}
|
||||
|
||||
// 显示自定义选择器
|
||||
function showPicker(field) {
|
||||
currentField.value = field
|
||||
const currentValue = form.value[field.key] || field.options[0]?.value || ''
|
||||
const index = field.options.findIndex(opt => opt.value === currentValue)
|
||||
pickerValue.value = [index]
|
||||
popup.value.open()
|
||||
}
|
||||
|
||||
// 关闭选择器
|
||||
function closePicker() {
|
||||
popup.value.close()
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
function confirmPicker() {
|
||||
if (currentField.value) {
|
||||
const index = pickerValue.value[0]
|
||||
form.value[currentField.value.key] = currentField.value.options[index].value
|
||||
closePicker()
|
||||
}
|
||||
}
|
||||
|
||||
// 选择器变化处理
|
||||
function onPickerChange(e) {
|
||||
pickerValue.value = e.detail.value
|
||||
}
|
||||
|
||||
async function handleQuery() {
|
||||
if (!tool.value)
|
||||
return
|
||||
|
||||
error.value = ''
|
||||
result.value = null
|
||||
revealedKeys.value = new Set()
|
||||
|
||||
if (!tool.value.validate(form.value)) {
|
||||
error.value = tool.value.validateMsg
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await postToolboxQuery(toolKey.value, form.value)
|
||||
if (res.code === 200 && res.data?.result) {
|
||||
result.value = res.data.result
|
||||
}
|
||||
else {
|
||||
error.value = res.msg || '查询失败'
|
||||
}
|
||||
}
|
||||
catch {
|
||||
error.value = '网络错误,请稍后重试'
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const resultEntries = computed(() => {
|
||||
if (!result.value || !tool.value?.resultLabels)
|
||||
return []
|
||||
return Object.entries(tool.value.resultLabels)
|
||||
.filter(([key]) => result.value![key] !== undefined)
|
||||
.map(([key, labelOrObj]) => {
|
||||
const val = result.value![key]
|
||||
const display = val === '' || val === null ? '无' : val
|
||||
if (typeof labelOrObj === 'object' && labelOrObj !== null) {
|
||||
return { key, label: labelOrObj.label, hidden: !!labelOrObj.hidden, value: display }
|
||||
}
|
||||
return { key, label: labelOrObj, hidden: false, value: display }
|
||||
})
|
||||
})
|
||||
|
||||
const revealedKeys = ref<Set<string>>(new Set())
|
||||
|
||||
function toggleReveal(key: string) {
|
||||
if (revealedKeys.value.has(key)) {
|
||||
revealedKeys.value.delete(key)
|
||||
}
|
||||
else {
|
||||
revealedKeys.value.add(key)
|
||||
}
|
||||
}
|
||||
|
||||
const resultList = computed(() => {
|
||||
if (!result.value || tool.value?.resultType !== 'list')
|
||||
return []
|
||||
const list = result.value.list
|
||||
if (!Array.isArray(list))
|
||||
return []
|
||||
return list
|
||||
})
|
||||
|
||||
const listLabelEntries = computed(() => {
|
||||
if (!tool.value?.resultLabels)
|
||||
return []
|
||||
return Object.entries(tool.value.resultLabels)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="page-root">
|
||||
<scroll-view scroll-y class="scrollarea">
|
||||
<view class="page">
|
||||
<view v-if="tool" class="card">
|
||||
<view class="card-desc">
|
||||
<text class="desc-text">{{ tool.desc }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 动态表单 -->
|
||||
<view class="form-area">
|
||||
<view v-for="field in tool.fields" :key="field.key" class="field">
|
||||
<text class="field-label">{{ field.label }}</text>
|
||||
|
||||
<!-- 选择框 -->
|
||||
<picker
|
||||
v-if="field.type === 'select'"
|
||||
mode="selector"
|
||||
:range="field.options.map(opt => opt.label)"
|
||||
:value="getSelectIndex(field, form[field.key])"
|
||||
@change="(e: any) => {
|
||||
const index = e.detail.value
|
||||
form[field.key] = field.options[index].value
|
||||
}"
|
||||
>
|
||||
<view class="field-input field-picker">
|
||||
{{ getSelectedLabel(field, form[field.key]) || field.placeholder }}
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<!-- 日期选择器 -->
|
||||
<picker
|
||||
v-else-if="field.type === 'date'"
|
||||
mode="date"
|
||||
:value="form[field.key] || ''"
|
||||
@change="(e: any) => (form[field.key] = e.detail.value)"
|
||||
>
|
||||
<view class="field-input field-picker">
|
||||
{{ form[field.key] || field.placeholder }}
|
||||
</view>
|
||||
</picker>
|
||||
|
||||
<!-- 文本域 -->
|
||||
<textarea
|
||||
v-else-if="field.type === 'textarea'"
|
||||
class="field-input field-textarea"
|
||||
:maxlength="field.maxlength"
|
||||
:placeholder="field.placeholder"
|
||||
:value="form[field.key] || ''"
|
||||
placeholder-class="field-ph"
|
||||
@input="(e: any) => (form[field.key] = e.detail.value)"
|
||||
/>
|
||||
|
||||
<!-- 普通输入框 -->
|
||||
<input
|
||||
v-else
|
||||
class="field-input"
|
||||
:type="field.type"
|
||||
:maxlength="field.maxlength"
|
||||
:placeholder="field.placeholder"
|
||||
:value="form[field.key] || ''"
|
||||
placeholder-class="field-ph"
|
||||
confirm-type="done"
|
||||
@input="(e: any) => (form[field.key] = e.detail.value)"
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 查询按钮 -->
|
||||
<button
|
||||
class="query-btn"
|
||||
:disabled="loading"
|
||||
@tap="handleQuery"
|
||||
>
|
||||
{{ loading ? '查询中...' : '立即查询' }}
|
||||
</button>
|
||||
|
||||
<!-- 错误提示 -->
|
||||
<view v-if="error" class="msg-area msg-error">
|
||||
<text class="msg-text">{{ error }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 结果展示 - 普通键值对 -->
|
||||
<view v-if="resultEntries.length > 0 && tool?.resultType !== 'list'" class="result-area">
|
||||
<view class="result-title">查询结果</view>
|
||||
<view class="result-list">
|
||||
<view
|
||||
v-for="item in resultEntries"
|
||||
:key="item.key"
|
||||
class="result-row"
|
||||
>
|
||||
<text class="result-label">{{ item.label }}</text>
|
||||
<!-- 隐藏字段:点击显示/隐藏 -->
|
||||
<view v-if="item.hidden" class="result-reveal-wrap">
|
||||
<view
|
||||
v-if="!revealedKeys.has(item.key)"
|
||||
class="result-reveal-btn"
|
||||
@tap="toggleReveal(item.key)"
|
||||
>
|
||||
点击查看
|
||||
</view>
|
||||
<template v-else>
|
||||
<text class="result-value">{{ item.value }}</text>
|
||||
<text class="result-hide-btn" @tap="toggleReveal(item.key)">收起</text>
|
||||
</template>
|
||||
</view>
|
||||
<!-- 普通字段 -->
|
||||
<text v-else class="result-value">{{ item.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 结果展示 - 列表型 -->
|
||||
<view v-if="resultList.length > 0" class="result-area">
|
||||
<view class="result-title">查询结果</view>
|
||||
<view class="result-card-list">
|
||||
<view
|
||||
v-for="(item, idx) in resultList"
|
||||
:key="idx"
|
||||
class="result-card-item"
|
||||
>
|
||||
<view
|
||||
v-for="([fieldKey, fieldLabel], fIdx) in listLabelEntries"
|
||||
:key="fieldKey"
|
||||
class="card-item-row"
|
||||
>
|
||||
<template v-if="item[fieldKey] !== undefined && item[fieldKey] !== ''">
|
||||
<text v-if="fIdx === 0" class="card-item-index">{{ idx + 1 }}</text>
|
||||
<text v-else class="card-item-index-placeholder" />
|
||||
<text class="card-item-field-label">{{ typeof fieldLabel === 'object' ? fieldLabel.label : fieldLabel }}</text>
|
||||
<text class="card-item-field-value">{{ item[fieldKey] }}</text>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty">
|
||||
<text class="empty-text">未找到该工具</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-root {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient(180deg, #f8faff 0%, #f3f5fb 100%);
|
||||
}
|
||||
|
||||
.scrollarea {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24rpx 24rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f7f8ff 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx 28rpx;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
box-shadow:
|
||||
0 16rpx 40rpx rgba(15, 35, 52, 0.04),
|
||||
0 0 0 1rpx rgba(255, 255, 255, 0.5) inset;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.form-area {
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.field {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #4e5969;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background: #f7f8fa;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
border-radius: 16rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: #1d2129;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.field-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.field-picker:empty::before {
|
||||
content: attr(placeholder);
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.field-textarea {
|
||||
height: 200rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.field-ph {
|
||||
color: #c9cdd4;
|
||||
}
|
||||
|
||||
.query-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: linear-gradient(135deg, #1768ff, #4e8cff);
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 16rpx;
|
||||
border: none;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.query-btn[disabled] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.msg-area {
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.msg-error {
|
||||
background: #fff2f0;
|
||||
border: 1rpx solid #ffccc7;
|
||||
}
|
||||
|
||||
.msg-text {
|
||||
font-size: 24rpx;
|
||||
color: #f53f3f;
|
||||
}
|
||||
|
||||
.result-area {
|
||||
margin-top: 8rpx;
|
||||
padding-top: 24rpx;
|
||||
border-top: 1rpx solid #f2f3f5;
|
||||
}
|
||||
|
||||
.result-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1d2129;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.result-list {
|
||||
background: #f7f8fa;
|
||||
border-radius: 16rpx;
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.result-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16rpx 24rpx;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
font-size: 26rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
font-size: 26rpx;
|
||||
color: #1d2129;
|
||||
font-weight: 500;
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.result-reveal-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.result-reveal-btn {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(135deg, #1768ff, #4e8cff);
|
||||
padding: 6rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
.result-hide-btn {
|
||||
font-size: 22rpx;
|
||||
color: #86909c;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 120rpx 0;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #86909c;
|
||||
}
|
||||
|
||||
/* 列表型结果卡片 */
|
||||
.result-card-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.result-card-item {
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e5e6f0;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
}
|
||||
|
||||
.card-item-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12rpx;
|
||||
padding: 4rpx 0;
|
||||
}
|
||||
|
||||
.card-item-index {
|
||||
flex-shrink: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
background: linear-gradient(135deg, #1768ff, #4e8cff);
|
||||
color: #ffffff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.card-item-index-placeholder {
|
||||
flex-shrink: 0;
|
||||
width: 40rpx;
|
||||
}
|
||||
|
||||
.card-item-field-label {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #86909c;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
|
||||
.card-item-field-value {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #1d2129;
|
||||
line-height: 1.6;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 自定义选择器样式 */
|
||||
.picker-container {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx 20rpx 0 0;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.picker-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20rpx;
|
||||
height: 88rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.picker-header text {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.picker-header text:first-child {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.picker-header text:last-child {
|
||||
color: #1768ff;
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
.picker-item {
|
||||
line-height: 100rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user