Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a7c6c8e03 | |||
| 76bdcd1aba |
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"version" : "1.0",
|
|
||||||
"configurations" : [
|
|
||||||
{
|
|
||||||
"playground" : "standard",
|
|
||||||
"type" : "uni-app:app-android"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"playground" : "standard",
|
|
||||||
"type" : "uni-app:app-ios"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
# AGENTS
|
|
||||||
|
|
||||||
<skills_system priority="1">
|
|
||||||
|
|
||||||
## Available Skills
|
|
||||||
|
|
||||||
<!-- SKILLS_TABLE_START -->
|
|
||||||
<usage>
|
|
||||||
When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
|
|
||||||
|
|
||||||
How to use skills:
|
|
||||||
- Invoke: `npx openskills read <skill-name>` (run in your shell)
|
|
||||||
- For multiple: `npx openskills read skill-one,skill-two`
|
|
||||||
- The skill content will load with detailed instructions on how to complete the task
|
|
||||||
- Base directory provided in output for resolving bundled resources (references/, scripts/, assets/)
|
|
||||||
|
|
||||||
Usage notes:
|
|
||||||
- Only use skills listed in <available_skills> below
|
|
||||||
- Do not invoke a skill that is already loaded in your context
|
|
||||||
- Each skill invocation is stateless
|
|
||||||
</usage>
|
|
||||||
|
|
||||||
<available_skills>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>algorithmic-art</name>
|
|
||||||
<description>Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>brand-guidelines</name>
|
|
||||||
<description>Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>canvas-design</name>
|
|
||||||
<description>Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>doc-coauthoring</name>
|
|
||||||
<description>Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>docx</name>
|
|
||||||
<description>"Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of \"Word doc\", \"word document\", \".docx\", or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a \"report\", \"memo\", \"letter\", \"template\", or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation."</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>frontend-design</name>
|
|
||||||
<description>Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>internal-comms</name>
|
|
||||||
<description>A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>mcp-builder</name>
|
|
||||||
<description>Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>pdf</name>
|
|
||||||
<description>Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>pptx</name>
|
|
||||||
<description>"Use this skill any time a .pptx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions \"deck,\" \"slides,\" \"presentation,\" or references a .pptx filename, regardless of what they plan to do with the content afterward. If a .pptx file needs to be opened, created, or touched, use this skill."</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>skill-creator</name>
|
|
||||||
<description>Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>slack-gif-creator</name>
|
|
||||||
<description>Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack."</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>template</name>
|
|
||||||
<description>Replace with description of the skill and when Claude should use it.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>theme-factory</name>
|
|
||||||
<description>Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>web-artifacts-builder</name>
|
|
||||||
<description>Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>webapp-testing</name>
|
|
||||||
<description>Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
<skill>
|
|
||||||
<name>xlsx</name>
|
|
||||||
<description>"Use this skill any time a spreadsheet file is the primary input or output. This means any task where the user wants to: open, read, edit, or fix an existing .xlsx, .xlsm, .csv, or .tsv file (e.g., adding columns, computing formulas, formatting, charting, cleaning messy data); create a new spreadsheet from scratch or from other data sources; or convert between tabular file formats. Trigger especially when the user references a spreadsheet file by name or path — even casually (like \"the xlsx in my downloads\") — and wants something done to it or produced from it. Also trigger for cleaning or restructuring messy tabular data files (malformed rows, misplaced headers, junk data) into proper spreadsheets. The deliverable must be a spreadsheet file. Do NOT trigger when the primary deliverable is a Word document, HTML report, standalone Python script, database pipeline, or Google Sheets API integration, even if tabular data is involved."</description>
|
|
||||||
<location>global</location>
|
|
||||||
</skill>
|
|
||||||
|
|
||||||
</available_skills>
|
|
||||||
<!-- SKILLS_TABLE_END -->
|
|
||||||
|
|
||||||
</skills_system>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
wxLogin,
|
alipayLogin,
|
||||||
getUserInfo
|
getUserInfo
|
||||||
} from './util/index'
|
} from './util/index'
|
||||||
|
|
||||||
@@ -11,45 +11,9 @@
|
|||||||
// 注意:语言初始化已移至 main.js,确保每次 reLaunch 都能正确加载新语言
|
// 注意:语言初始化已移至 main.js,确保每次 reLaunch 都能正确加载新语言
|
||||||
},
|
},
|
||||||
onShow: async function() {
|
onShow: async function() {
|
||||||
|
console.log('========================================')
|
||||||
// 检查启动路径,如果设置了启动路径则跳转
|
console.log('=== App onShow 被调用 ===')
|
||||||
try {
|
console.log('时间戳:', new Date().toLocaleTimeString())
|
||||||
const launchPath = uni.getStorageSync('launchPath')
|
|
||||||
if (launchPath) {
|
|
||||||
console.log('检测到启动路径:', launchPath)
|
|
||||||
// 获取当前页面栈
|
|
||||||
const pages = getCurrentPages()
|
|
||||||
const currentPage = pages[pages.length - 1]
|
|
||||||
const currentRoute = currentPage ? ('/' + currentPage.route) : ''
|
|
||||||
|
|
||||||
// 规范化路径格式进行比较
|
|
||||||
const normalizedLaunchPath = launchPath.replace(/\.html$/, '')
|
|
||||||
|
|
||||||
// 只要当前不在目标页面,就进行一次跳转
|
|
||||||
// 下单流程不会受影响,因为在下单离开设备详情页时不会写入 launchPath
|
|
||||||
if (currentRoute !== normalizedLaunchPath) {
|
|
||||||
console.log('当前页面:', currentRoute, '目标页面:', normalizedLaunchPath)
|
|
||||||
// 清除启动路径标记(在跳转前清除,避免重复触发)
|
|
||||||
uni.removeStorageSync('launchPath')
|
|
||||||
// 使用 reLaunch 跳转到启动路径(首页)
|
|
||||||
uni.reLaunch({
|
|
||||||
url: normalizedLaunchPath,
|
|
||||||
success: () => {
|
|
||||||
console.log('成功跳转到启动路径(reLaunch)')
|
|
||||||
},
|
|
||||||
fail: (reLaunchErr) => {
|
|
||||||
console.error('跳转到启动路径失败:', reLaunchErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 如果已经在目标页面,清除启动路径标记
|
|
||||||
console.log('当前已在目标页面,清除启动路径标记')
|
|
||||||
uni.removeStorageSync('launchPath')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('App onShow - 启动路径检查失败:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查并更新语言(uni.reLaunch 会触发 onShow)
|
// 检查并更新语言(uni.reLaunch 会触发 onShow)
|
||||||
try {
|
try {
|
||||||
@@ -75,6 +39,8 @@
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('App onShow - 语言检查失败:', e)
|
console.error('App onShow - 语言检查失败:', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('========================================')
|
||||||
},
|
},
|
||||||
onHide: function() {
|
onHide: function() {
|
||||||
console.log('App Hide')
|
console.log('App Hide')
|
||||||
@@ -83,7 +49,7 @@
|
|||||||
// 保留方法但不调用
|
// 保留方法但不调用
|
||||||
async autoLogin() {
|
async autoLogin() {
|
||||||
try {
|
try {
|
||||||
const loginResult = await wxLogin()
|
const loginResult = await alipayLogin()
|
||||||
// await getUserInfo()
|
// await getUserInfo()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('自动登录失败:', error)
|
console.error('自动登录失败:', error)
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="skeleton-container">
|
|
||||||
<!-- 设备信息卡片骨架 -->
|
|
||||||
<view class="card skeleton-card">
|
|
||||||
<view class="device-location-skeleton">
|
|
||||||
<view class="location-left-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="0" rowsWidth="32rpx" rowsHeight="32rpx"
|
|
||||||
borderRadius="50%"></uv-skeleton>
|
|
||||||
<view style="margin-left: 12rpx;">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="200rpx" rowsHeight="28rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="0" rowsWidth="100rpx" rowsHeight="40rpx"
|
|
||||||
borderRadius="30rpx"></uv-skeleton>
|
|
||||||
</view>
|
|
||||||
<view class="device-info-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="300rpx" rowsHeight="26rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
<view class="device-info-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="350rpx" rowsHeight="26rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 计费规则卡片骨架 -->
|
|
||||||
<view class="card skeleton-card">
|
|
||||||
<view class="card-header-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="150rpx" rowsHeight="32rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="pricing-banner-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="200rpx" rowsHeight="64rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
<view style="margin-top: 16rpx;">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="0" rowsWidth="120rpx" rowsHeight="40rpx"
|
|
||||||
borderRadius="30rpx"></uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="pricing-info-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="100%" rowsHeight="26rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
<view class="pricing-info-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="100%" rowsHeight="26rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 使用说明卡片骨架 -->
|
|
||||||
<view class="card skeleton-card">
|
|
||||||
<view class="card-header-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="150rpx" rowsHeight="32rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
<view class="notice-item-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="0" rowsWidth="12rpx" rowsHeight="12rpx"
|
|
||||||
borderRadius="50%"></uv-skeleton>
|
|
||||||
<view style="flex: 1; margin-left: 16rpx;">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="100%" rowsHeight="28rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="notice-item-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="0" rowsWidth="12rpx" rowsHeight="12rpx"
|
|
||||||
borderRadius="50%"></uv-skeleton>
|
|
||||||
<view style="flex: 1; margin-left: 16rpx;">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="100%" rowsHeight="28rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="notice-item-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="0" rowsWidth="12rpx" rowsHeight="12rpx"
|
|
||||||
borderRadius="50%"></uv-skeleton>
|
|
||||||
<view style="flex: 1; margin-left: 16rpx;">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="100%" rowsHeight="28rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 促销提示框骨架 -->
|
|
||||||
<view class="promotion-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="100%" rowsHeight="60rpx"
|
|
||||||
borderRadius="22rpx"></uv-skeleton>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部按钮骨架 -->
|
|
||||||
<view class="footer-skeleton">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="0" rowsWidth="100%" rowsHeight="96rpx"
|
|
||||||
borderRadius="48rpx"></uv-skeleton>
|
|
||||||
<view style="margin-top: 16rpx;">
|
|
||||||
<uv-skeleton :loading="true" :animate="true" rows="1" rowsWidth="200rpx" rowsHeight="24rpx">
|
|
||||||
</uv-skeleton>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.skeleton-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
padding: 30rpx 30rpx 300rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
|
||||||
padding: 30rpx;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-card {
|
|
||||||
.device-location-skeleton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
|
|
||||||
.location-left-skeleton {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-info-skeleton {
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header-skeleton {
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pricing-banner-skeleton {
|
|
||||||
background: #E6F7EC;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 40rpx 30rpx;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pricing-info-skeleton {
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-item-skeleton {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.promotion-skeleton {
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-skeleton {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 24rpx 30rpx;
|
|
||||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
||||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
|
|
||||||
z-index: 100;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="device-order-card" @click="onCardClick">
|
|
||||||
<!-- 头部:标题和状态 -->
|
|
||||||
<view class="card-header">
|
|
||||||
<view class="header-left">
|
|
||||||
<view class="tag-bar"></view>
|
|
||||||
<text class="title">定制化订单</text>
|
|
||||||
</view>
|
|
||||||
<view class="status-badge">{{ statusText }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 产品信息区域 -->
|
|
||||||
<view class="product-section">
|
|
||||||
<image
|
|
||||||
:src="order.pictureUrl || order.productImage || '/static/default-product.png'"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="product-image"
|
|
||||||
lazy-load="true"
|
|
||||||
></image>
|
|
||||||
<view class="product-info">
|
|
||||||
<view class="product-name">{{ order.productName || order.deviceName || $t('goods.defaultProductNameShort') }}</view>
|
|
||||||
<view style="display: flex;justify-content: space-between;">
|
|
||||||
<view class="product-style">款式:{{ order.optionName || order.style || order.deviceStyle || '标准' }}</view>
|
|
||||||
<view class="product-price">¥ {{ totalAmount }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 分割线 -->
|
|
||||||
<view class="divider-wrapper">
|
|
||||||
<uv-divider :hairline="false" lineColor="#f5f5f5"></uv-divider>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部时间和删除按钮 -->
|
|
||||||
<view class="card-footer">
|
|
||||||
<text class="order-time">{{ order.startTime || order.createTime }}</text>
|
|
||||||
<view class="footer-actions">
|
|
||||||
<!-- 待付款状态显示立即支付按钮 -->
|
|
||||||
<view v-if="isWaitingPayment" class="pay-btn" @click.stop="onPay">
|
|
||||||
<text>立即支付</text>
|
|
||||||
</view>
|
|
||||||
<view class="delete-btn" @click.stop="onDelete">
|
|
||||||
<uv-icon name="trash" size="20" color="#999"></uv-icon>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
order: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['click', 'delete', 'pay']);
|
|
||||||
|
|
||||||
// 订单状态文本
|
|
||||||
const statusText = computed(() => {
|
|
||||||
const status = props.order.orderStatus || props.order.status;
|
|
||||||
if (status === 0 || status === '0') {
|
|
||||||
return '待付款';
|
|
||||||
}
|
|
||||||
if (status === 1 || status === '1') {
|
|
||||||
return '待发货';
|
|
||||||
}
|
|
||||||
if (status === 2 || status === '2') {
|
|
||||||
return '待收货';
|
|
||||||
}
|
|
||||||
if (status === 3 || status === '3') {
|
|
||||||
return '已完成';
|
|
||||||
}
|
|
||||||
if (status === 4 || status === '4') {
|
|
||||||
return '已取消';
|
|
||||||
}
|
|
||||||
if (status === 5 || status === '5') {
|
|
||||||
return '退款中';
|
|
||||||
}
|
|
||||||
return '待付款';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 判断是否为待付款状态
|
|
||||||
const isWaitingPayment = computed(() => {
|
|
||||||
const status = props.order.orderStatus || props.order.status;
|
|
||||||
return status === 0 || status === '0';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 总金额
|
|
||||||
const totalAmount = computed(() => {
|
|
||||||
return props.order.totalAmount || props.order.amount || props.order.payAmount || '99.0';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 卡片点击事件
|
|
||||||
const onCardClick = () => {
|
|
||||||
emit('click', props.order);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除订单
|
|
||||||
const onDelete = () => {
|
|
||||||
emit('delete', props.order);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 立即支付
|
|
||||||
const onPay = () => {
|
|
||||||
emit('pay', props.order);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.device-order-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
// 头部区域
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 24rpx 24rpx 0 24rpx;
|
|
||||||
// border-bottom: 1rpx solid #f5f5f5;
|
|
||||||
|
|
||||||
.header-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.tag-bar {
|
|
||||||
width: 6rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
background: #07c160;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 30rpx;
|
|
||||||
color: #3EAB64;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
padding: 8rpx 20rpx;
|
|
||||||
background: rgba(7, 193, 96, 0.1);
|
|
||||||
color: #07c160;
|
|
||||||
font-size: 24rpx;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 产品信息区域
|
|
||||||
.product-section {
|
|
||||||
display: flex;
|
|
||||||
padding: 24rpx 24rpx 0 24rpx;
|
|
||||||
|
|
||||||
.product-image {
|
|
||||||
width: 120rpx;
|
|
||||||
height: 120rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background: #f5f5f5;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.product-name {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-style {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price {
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #07c160;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分割线区域
|
|
||||||
.divider-wrapper {
|
|
||||||
padding: 0 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部区域
|
|
||||||
.card-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0rpx 24rpx 24rpx;
|
|
||||||
background: #fff;
|
|
||||||
|
|
||||||
.order-time {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16rpx;
|
|
||||||
|
|
||||||
.pay-btn {
|
|
||||||
padding: 12rpx 32rpx;
|
|
||||||
background: linear-gradient(135deg, #07c160, #10d673);
|
|
||||||
border-radius: 24rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn {
|
|
||||||
width: 48rpx;
|
|
||||||
height: 48rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="empty-state" v-if="!isLoading && (!positions || positions.length === 0)">
|
<view class="empty-state" v-if="!isLoading && (!positions || positions.length === 0)">
|
||||||
<image class="empty-icon" src="/static/scan-icon.png" mode="aspectFit" lazy-load="true" />
|
<image class="empty-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
||||||
<text class="empty-text">{{ $t('home.noNearbyDevice') }}</text>
|
<text class="empty-text">{{ $t('home.noNearbyDevice') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: { type: Boolean, default: false },
|
show: { type: Boolean, default: false },
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="map-container" :class="{ 'full-width': props.fullWidth }">
|
<view class="map-container" :class="{ 'full-width': props.fullWidth }" :style="{ '--map-height': props.customHeight || '78vh' }">
|
||||||
<!-- 地图容器 -->
|
<!-- 地图容器 -->
|
||||||
<view class="map-wrapper">
|
<view class="map-wrapper">
|
||||||
<!-- 使用小程序原生地图组件 -->
|
<!-- 支付宝小程序地图组件:使用高德地图 -->
|
||||||
<map
|
<map id="map" class="native-map"
|
||||||
id="map"
|
|
||||||
class="native-map"
|
|
||||||
:longitude="mapCenter.longitude"
|
:longitude="mapCenter.longitude"
|
||||||
:latitude="mapCenter.latitude"
|
:latitude="mapCenter.latitude"
|
||||||
:markers="mapMarkers"
|
:markers="mapMarkers"
|
||||||
@@ -13,82 +11,43 @@
|
|||||||
:show-location="false"
|
:show-location="false"
|
||||||
@regionchange="onMapRegionChange"
|
@regionchange="onMapRegionChange"
|
||||||
@markertap="onMapMarkerTap"
|
@markertap="onMapMarkerTap"
|
||||||
@callouttap="onCalloutTap"
|
@tap="onMapTap"
|
||||||
@updated="onMapUpdated"
|
@updated="onMapUpdated"
|
||||||
@error="onMapError"
|
@error="onMapError">
|
||||||
>
|
|
||||||
<!-- 覆盖在地图上的广告轮播(使用 cover-view 以兼容小程序原生组件层级) -->
|
|
||||||
<cover-view
|
|
||||||
class="index-swiper"
|
|
||||||
v-if="!props.hideControls && !props.hideMapOverlays && currentBannerImage"
|
|
||||||
>
|
|
||||||
<cover-image
|
|
||||||
:src="currentBannerImage"
|
|
||||||
class="index-swiper-img"
|
|
||||||
mode="aspectFill"
|
|
||||||
@tap="handleBannerTap"
|
|
||||||
></cover-image>
|
|
||||||
<!-- 轮播指示器 -->
|
|
||||||
<cover-view
|
|
||||||
class="banner-indicators"
|
|
||||||
v-if="props.bannerImages.length > 1"
|
|
||||||
>
|
|
||||||
<cover-view
|
|
||||||
v-for="(img, idx) in props.bannerImages"
|
|
||||||
:key="idx"
|
|
||||||
class="indicator-dot"
|
|
||||||
:class="{ active: idx === currentBannerIndex }"
|
|
||||||
>
|
|
||||||
</cover-view>
|
|
||||||
</cover-view>
|
|
||||||
</cover-view>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 地图中心固定定位图标 -->
|
|
||||||
<cover-view
|
|
||||||
class="center-location-marker"
|
|
||||||
v-if="!props.hideMapOverlays"
|
|
||||||
>
|
|
||||||
<cover-image
|
|
||||||
src="/static/location-icon.png"
|
|
||||||
class="center-marker-icon"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- 侧边控制按钮 -->
|
|
||||||
<cover-view
|
|
||||||
class="map-side-controls"
|
|
||||||
v-if="!props.hideControls && !props.hideMapOverlays"
|
|
||||||
>
|
|
||||||
<cover-view class="side-btn guide" @tap="handleGuide">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon"
|
|
||||||
src="/static/use_help.png"
|
|
||||||
style="border-radius: 50%;"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
<cover-view class="side-btn locate" @tap="handleRelocate">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon"
|
|
||||||
src="/static/location.png"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
<cover-view class="side-btn search" @tap="handleSearch">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon"
|
|
||||||
src="/static/other_device.png"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
<cover-view class="side-btn service" @tap="handleService">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon"
|
|
||||||
src="/static/customer-service.png"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
</cover-view>
|
|
||||||
</map>
|
</map>
|
||||||
|
|
||||||
|
<!-- 覆盖在地图上的广告轮播(使用 cover-view 以兼容小程序原生组件层级) -->
|
||||||
|
<cover-view class="index-swiper" v-if="!props.hideControls && !props.hideMapOverlays && currentBannerImage">
|
||||||
|
<cover-image :src="currentBannerImage" class="index-swiper-img" mode="aspectFill" @tap="handleBannerTap"></cover-image>
|
||||||
|
<!-- 轮播指示器 -->
|
||||||
|
<cover-view class="banner-indicators" v-if="props.bannerImages.length > 1">
|
||||||
|
<cover-view
|
||||||
|
v-for="(img, idx) in props.bannerImages"
|
||||||
|
:key="idx"
|
||||||
|
class="indicator-dot"
|
||||||
|
:class="{ active: idx === currentBannerIndex }">
|
||||||
|
</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
|
||||||
|
<!-- 地图中心固定定位图标 -->
|
||||||
|
<cover-view class="center-location-marker" v-if="!props.hideMapOverlays">
|
||||||
|
<cover-image src="/static/location-icon.png" class="center-marker-icon"></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
|
||||||
|
<!-- 地图侧边控制按钮:重定位、客服中心、查看附近设备 -->
|
||||||
|
<cover-view class="map-side-controls" v-if="!props.hideControls && !props.hideMapOverlays">
|
||||||
|
<cover-view class="side-btn locate" @tap="handleRelocate">
|
||||||
|
<cover-image class="side-icon" src="/static/location.png"></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="side-btn service" @tap="handleService">
|
||||||
|
<cover-image class="side-icon" src="/static/customer-service.png"></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
<cover-view class="side-btn search" @tap="handleSearch">
|
||||||
|
<cover-image class="side-icon" src="/static/other_device.png"></cover-image>
|
||||||
|
</cover-view>
|
||||||
|
</cover-view>
|
||||||
|
|
||||||
<!-- 地图加载状态 -->
|
<!-- 地图加载状态 -->
|
||||||
<view class="map-loading" v-if="isLoading">
|
<view class="map-loading" v-if="isLoading">
|
||||||
<view class="loading-content">
|
<view class="loading-content">
|
||||||
@@ -118,7 +77,7 @@
|
|||||||
import { useI18n } from '../utils/i18n.js'
|
import { useI18n } from '../utils/i18n.js'
|
||||||
|
|
||||||
// 获取 i18n 实例
|
// 获取 i18n 实例
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
// 引用折叠面板组件的ref
|
// 引用折叠面板组件的ref
|
||||||
const collapseRef = ref(null)
|
const collapseRef = ref(null)
|
||||||
@@ -178,8 +137,7 @@
|
|||||||
'showList',
|
'showList',
|
||||||
'markerTap',
|
'markerTap',
|
||||||
'mapCenterChange',
|
'mapCenterChange',
|
||||||
'bannerClick',
|
'bannerClick'
|
||||||
'guide'
|
|
||||||
])
|
])
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
@@ -296,14 +254,45 @@
|
|||||||
deep: true
|
deep: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 启动广告轮播
|
||||||
|
const startBannerRotation = () => {
|
||||||
|
// 如果只有一张或没有图片,不需要轮播
|
||||||
|
if (!props.bannerImages || props.bannerImages.length <= 1) {
|
||||||
|
console.log('图片数量不足,不启动轮播')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除旧的定时器
|
||||||
|
stopBannerRotation()
|
||||||
|
|
||||||
|
console.log('开始广告轮播定时器')
|
||||||
|
// 每3秒切换一次
|
||||||
|
bannerTimer = setInterval(() => {
|
||||||
|
const nextIndex = (currentBannerIndex.value + 1) % props.bannerImages.length
|
||||||
|
console.log('轮播切换:', currentBannerIndex.value, '->', nextIndex)
|
||||||
|
currentBannerIndex.value = nextIndex
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止广告轮播
|
||||||
|
const stopBannerRotation = () => {
|
||||||
|
if (bannerTimer) {
|
||||||
|
console.log('停止广告轮播')
|
||||||
|
clearInterval(bannerTimer)
|
||||||
|
bannerTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 监听广告图片变化,启动或停止轮播
|
// 监听广告图片变化,启动或停止轮播
|
||||||
watch(() => props.bannerImages, (newImages, oldImages) => {
|
watch(() => props.bannerImages, (newImages, oldImages) => {
|
||||||
|
console.log('广告图片变化:', newImages?.length, '张')
|
||||||
// 先停止旧的轮播
|
// 先停止旧的轮播
|
||||||
stopBannerRotation()
|
stopBannerRotation()
|
||||||
currentBannerIndex.value = 0
|
currentBannerIndex.value = 0
|
||||||
|
|
||||||
// 如果有多张图片,启动新的轮播
|
// 如果有多张图片,启动新的轮播
|
||||||
if (newImages && newImages.length > 1) {
|
if (newImages && newImages.length > 1) {
|
||||||
|
console.log('启动广告轮播,共', newImages.length, '张图片')
|
||||||
// 使用 nextTick 确保 DOM 已更新
|
// 使用 nextTick 确保 DOM 已更新
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
startBannerRotation()
|
startBannerRotation()
|
||||||
@@ -319,31 +308,22 @@
|
|||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 地图区域变化事件(带防抖优化)
|
// 地图区域变化事件(支付宝小程序,带防抖优化)
|
||||||
const onMapRegionChange = (e) => {
|
const onMapRegionChange = (e) => {
|
||||||
|
if (!e) {
|
||||||
// 只处理结束事件
|
|
||||||
if (!e || (e.type !== 'end' && e.type !== 'regionchange')) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const causedBy = e.causedBy || e.detail?.causedBy
|
// 获取触发原因和中心位置
|
||||||
|
const causedBy = e.detail?.causedBy || e.causedBy
|
||||||
|
const centerLocation = e.detail?.centerLocation || e.centerLocation || e.detail?.location
|
||||||
|
|
||||||
// H5 环境下可能没有 causedBy,只要是 end 事件就处理
|
if (causedBy === 'gesture' || causedBy === 'scale' || causedBy === 'drag' || causedBy === 'update') {
|
||||||
const isH5 = false;
|
|
||||||
// #ifdef H5
|
|
||||||
const h5Status = true;
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
if (causedBy === 'gesture' || causedBy === 'scale' || causedBy === 'drag' || causedBy === 'update' || (typeof h5Status !== 'undefined' && e.type === 'end')) {
|
|
||||||
// 清除之前的定时器
|
// 清除之前的定时器
|
||||||
if (regionChangeTimer) {
|
if (regionChangeTimer) {
|
||||||
clearTimeout(regionChangeTimer)
|
clearTimeout(regionChangeTimer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接从事件对象中获取最新的中心点位置
|
|
||||||
const centerLocation = e.detail?.centerLocation || e.centerLocation
|
|
||||||
|
|
||||||
if (centerLocation && centerLocation.longitude && centerLocation.latitude) {
|
if (centerLocation && centerLocation.longitude && centerLocation.latitude) {
|
||||||
// 防抖:500ms后执行查询
|
// 防抖:500ms后执行查询
|
||||||
regionChangeTimer = setTimeout(() => {
|
regionChangeTimer = setTimeout(() => {
|
||||||
@@ -353,13 +333,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
mapCenter.value = newCenter;
|
mapCenter.value = newCenter;
|
||||||
|
|
||||||
|
|
||||||
// 触发父组件查询新位置的场地
|
// 触发父组件查询新位置的场地
|
||||||
emit('mapCenterChange', newCenter)
|
emit('mapCenterChange', newCenter)
|
||||||
}, 500)
|
}, 500)
|
||||||
} else {
|
} else {
|
||||||
// 兜底方案:如果事件中没有centerLocation,才使用API获取
|
// 兜底方案:使用API获取地图中心
|
||||||
regionChangeTimer = setTimeout(() => {
|
regionChangeTimer = setTimeout(() => {
|
||||||
if (mapContext.value) {
|
if (mapContext.value) {
|
||||||
mapContext.value.getCenterLocation({
|
mapContext.value.getCenterLocation({
|
||||||
@@ -383,12 +361,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记点点击事件
|
// 标记点点击事件(支付宝小程序)
|
||||||
const onMapMarkerTap = (e) => {
|
const onMapMarkerTap = (e) => {
|
||||||
const markerId = e.detail?.markerId || e.markerId
|
if (!e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取markerId
|
||||||
|
const markerId = e.detail?.markerId || e.markerId || e.detail?.marker?.id
|
||||||
|
|
||||||
// 查找对应的场地位置信息
|
// 查找对应的场地位置信息
|
||||||
if (props.filteredPositions && props.filteredPositions.length > 0) {
|
if (props.filteredPositions && props.filteredPositions.length > 0 && markerId) {
|
||||||
const position = props.filteredPositions[markerId - 1]
|
const position = props.filteredPositions[markerId - 1]
|
||||||
if (position) {
|
if (position) {
|
||||||
emit('markerTap', position)
|
emit('markerTap', position)
|
||||||
@@ -396,14 +379,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记点气泡点击事件
|
// 地图点击事件(支付宝小程序)
|
||||||
const onCalloutTap = (e) => {
|
const onMapTap = (e) => {
|
||||||
const markerId = e.markerId
|
console.log('地图点击事件:', e)
|
||||||
const marker = mapMarkers.value.find(item => item.id === markerId)
|
|
||||||
|
|
||||||
if (marker && marker.position) {
|
|
||||||
emit('markerTap', marker.position)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 地图错误事件
|
// 地图错误事件
|
||||||
@@ -427,49 +405,23 @@ const handleSearch = () => {
|
|||||||
|
|
||||||
const handleService = () => {
|
const handleService = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/subPackages/service/help/index'
|
url: '/pages/help/index'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleGuide = () => {
|
|
||||||
emit('guide')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleJoinTap = () => {
|
const handleJoinTap = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/subPackages/business/join/index'
|
url: '/pages/join/index'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理广告点击
|
// 处理广告点击
|
||||||
const handleBannerTap = () => {
|
const handleBannerTap = () => {
|
||||||
|
console.log('点击地图广告:', currentBannerIndex.value, currentBannerImage.value)
|
||||||
// 触发父组件处理点击事件
|
// 触发父组件处理点击事件
|
||||||
emit('bannerClick', currentBannerIndex.value)
|
emit('bannerClick', currentBannerIndex.value)
|
||||||
}
|
// 默认跳转到合作加盟页面
|
||||||
|
handleJoinTap()
|
||||||
// 启动广告轮播
|
|
||||||
const startBannerRotation = () => {
|
|
||||||
// 如果只有一张或没有图片,不需要轮播
|
|
||||||
if (!props.bannerImages || props.bannerImages.length <= 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除旧的定时器
|
|
||||||
stopBannerRotation()
|
|
||||||
|
|
||||||
// 每3秒切换一次
|
|
||||||
bannerTimer = setInterval(() => {
|
|
||||||
const nextIndex = (currentBannerIndex.value + 1) % props.bannerImages.length
|
|
||||||
currentBannerIndex.value = nextIndex
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止广告轮播
|
|
||||||
const stopBannerRotation = () => {
|
|
||||||
if (bannerTimer) {
|
|
||||||
clearInterval(bannerTimer)
|
|
||||||
bannerTimer = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScan = () => {
|
const handleScan = () => {
|
||||||
@@ -502,6 +454,7 @@ const handleSearch = () => {
|
|||||||
|
|
||||||
// 初始化广告轮播
|
// 初始化广告轮播
|
||||||
if (props.bannerImages && props.bannerImages.length > 1) {
|
if (props.bannerImages && props.bannerImages.length > 1) {
|
||||||
|
console.log('onMounted: 初始化广告轮播')
|
||||||
startBannerRotation()
|
startBannerRotation()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -542,35 +495,22 @@ const handleSearch = () => {
|
|||||||
/* 地图容器 */
|
/* 地图容器 */
|
||||||
.map-container {
|
.map-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
|
||||||
// position: fixed;
|
// position: fixed;
|
||||||
// top: 0;
|
// top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 94vw;
|
width: 94vw;
|
||||||
// height: var(--map-height, calc(100% - 20rpx)); /* 使用变量或默认高度 */
|
height: calc(100% - 20rpx); /* 减少高度,避免覆盖底部按钮 */
|
||||||
margin: 20rpx;
|
margin: 20rpx;
|
||||||
margin-bottom: 0; /* 底部不需要边距 */
|
margin-bottom: 0; /* 底部不需要边距 */
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
// #ifdef H5
|
|
||||||
height: 78vh;
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
height: 72vh;
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
|
|
||||||
&.full-width {
|
&.full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-wrapper {
|
.map-wrapper {
|
||||||
@@ -659,14 +599,14 @@ const handleSearch = () => {
|
|||||||
// min-width: 160rpx;
|
// min-width: 160rpx;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
// height: 72rpx;
|
// height: 72rpx;
|
||||||
background: rgba(255, 255, 255, 0.96);
|
// background: rgba(255, 255, 255, 0.96);
|
||||||
border-radius: 24rpx;
|
border-radius: 36rpx;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
// box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
|
// box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12);
|
||||||
padding: 13rpx;
|
padding: 20rpx;
|
||||||
border: 2rpx solid #e0e0e0;
|
border: 2rpx solid #e0e0e0;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -698,8 +638,8 @@ const handleSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.side-icon {
|
.side-icon {
|
||||||
width: 40rpx;
|
width: 44rpx;
|
||||||
height: 40rpx;
|
height: 44rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.index-swiper {
|
.index-swiper {
|
||||||
@@ -731,7 +671,6 @@ const handleSearch = () => {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8rpx;
|
gap: 8rpx;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
.indicator-dot {
|
.indicator-dot {
|
||||||
width: 12rpx;
|
width: 12rpx;
|
||||||
|
|||||||
@@ -1,687 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="map-container" :class="{ 'full-width': props.fullWidth }">
|
|
||||||
<!-- 地图容器 -->
|
|
||||||
<view class="map-wrapper">
|
|
||||||
<!-- 使用小程序原生地图组件 -->
|
|
||||||
<map
|
|
||||||
id="map"
|
|
||||||
class="native-map"
|
|
||||||
:longitude="mapCenter.longitude"
|
|
||||||
:latitude="mapCenter.latitude"
|
|
||||||
:markers="mapMarkers"
|
|
||||||
:scale="mapZoom"
|
|
||||||
:show-location="false"
|
|
||||||
@regionchange="onMapRegionChange"
|
|
||||||
@markertap="onMapMarkerTap"
|
|
||||||
@callouttap="onCalloutTap"
|
|
||||||
@updated="onMapUpdated"
|
|
||||||
@error="onMapError"
|
|
||||||
></map>
|
|
||||||
|
|
||||||
<!-- 支付宝小程序:所有 cover-image 和 cover-view 必须放在 map 标签外部 -->
|
|
||||||
<!-- 广告轮播(直接使用 cover-image,不能嵌套在 cover-view 中) -->
|
|
||||||
<cover-image
|
|
||||||
v-if="!props.hideControls && !props.hideMapOverlays && currentBannerImage"
|
|
||||||
:src="currentBannerImage"
|
|
||||||
class="index-swiper-img-alipay"
|
|
||||||
mode="aspectFill"
|
|
||||||
@tap="handleBannerTap"
|
|
||||||
></cover-image>
|
|
||||||
|
|
||||||
<!-- 轮播指示器(每个点都是独立的 cover-view,避免嵌套) -->
|
|
||||||
<template v-if="!props.hideControls && !props.hideMapOverlays && props.bannerImages.length > 1">
|
|
||||||
<cover-view
|
|
||||||
v-for="(img, idx) in props.bannerImages"
|
|
||||||
:key="idx"
|
|
||||||
class="indicator-dot-alipay"
|
|
||||||
:class="{ active: idx === currentBannerIndex }"
|
|
||||||
:style="{
|
|
||||||
left: `calc(50% + ${(idx - (props.bannerImages.length - 1) / 2) * 20}rpx)`,
|
|
||||||
transform: 'translateX(-50%)'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</cover-view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 地图中心固定定位图标(使用 cover-view 嵌套 cover-image) -->
|
|
||||||
<cover-view
|
|
||||||
class="center-location-marker-alipay"
|
|
||||||
v-if="!props.hideMapOverlays"
|
|
||||||
>
|
|
||||||
<cover-image
|
|
||||||
src="/static/location-icon.png"
|
|
||||||
class="center-marker-icon-alipay"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
|
|
||||||
<!-- 侧边控制按钮(使用 cover-view 嵌套 cover-image) -->
|
|
||||||
<cover-view
|
|
||||||
class="map-side-controls-alipay"
|
|
||||||
v-if="!props.hideControls && !props.hideMapOverlays"
|
|
||||||
>
|
|
||||||
<cover-view class="side-btn-alipay guide-alipay" @tap="handleGuide">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon-alipay"
|
|
||||||
src="/static/use_help.png"
|
|
||||||
style="border-radius: 50%;"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
<cover-view class="side-btn-alipay locate-alipay" @tap="handleRelocate">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon-alipay"
|
|
||||||
src="/static/location.png"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
<cover-view class="side-btn-alipay search-alipay" @tap="handleSearch">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon-alipay"
|
|
||||||
src="/static/other_device.png"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
<cover-view class="side-btn-alipay service-alipay" @tap="handleService">
|
|
||||||
<cover-image
|
|
||||||
class="side-icon-alipay"
|
|
||||||
src="/static/customer-service.png"
|
|
||||||
></cover-image>
|
|
||||||
</cover-view>
|
|
||||||
</cover-view>
|
|
||||||
|
|
||||||
<!-- 地图加载状态 -->
|
|
||||||
<view class="map-loading" v-if="isLoading">
|
|
||||||
<view class="loading-content">
|
|
||||||
<view class="loading-spinner"></view>
|
|
||||||
<text>{{ $t('common.loadingMap') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
computed,
|
|
||||||
watch,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted,
|
|
||||||
nextTick,
|
|
||||||
getCurrentInstance
|
|
||||||
} from 'vue'
|
|
||||||
// 导入地图工具函数
|
|
||||||
import {
|
|
||||||
calculateDistanceSync
|
|
||||||
} from '../utils/mapUtils.js'
|
|
||||||
// 导入国际化
|
|
||||||
import { useI18n } from '../utils/i18n.js'
|
|
||||||
|
|
||||||
// 获取 i18n 实例
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
// 引用折叠面板组件的ref
|
|
||||||
const collapseRef = ref(null)
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
userLocation: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
positionList: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
filteredPositions: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
searchKeyword: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
noticeText: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
enableMarkers: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
customHeight: {
|
|
||||||
type: String,
|
|
||||||
default: '' // 自定义高度,如 '48vh', '400rpx' 等
|
|
||||||
},
|
|
||||||
hideControls: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false // 是否隐藏侧边控制按钮
|
|
||||||
},
|
|
||||||
fullWidth: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false // 是否全宽显示(去掉 margin 和固定宽度)
|
|
||||||
},
|
|
||||||
hideMapOverlays: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false // 是否隐藏地图上的覆盖层元素(如中心定位图标、轮播图等)
|
|
||||||
},
|
|
||||||
bannerImages: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [] // 广告图片列表
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits([
|
|
||||||
'relocate',
|
|
||||||
'scan',
|
|
||||||
'showList',
|
|
||||||
'markerTap',
|
|
||||||
'mapCenterChange',
|
|
||||||
'bannerClick',
|
|
||||||
'guide'
|
|
||||||
])
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const isLoading = ref(true)
|
|
||||||
const mapCenter = ref({
|
|
||||||
longitude: 116.397128,
|
|
||||||
latitude: 39.916527
|
|
||||||
})
|
|
||||||
const mapZoom = ref(17)
|
|
||||||
const mapMarkers = ref([]) // 用于地图组件的markers
|
|
||||||
const mapContext = ref(null) // 地图上下文
|
|
||||||
const currentBannerIndex = ref(0) // 当前显示的广告索引
|
|
||||||
let bannerTimer = null // 广告轮播定时器
|
|
||||||
|
|
||||||
// 计算当前显示的广告图片
|
|
||||||
const currentBannerImage = computed(() => {
|
|
||||||
if (props.bannerImages && props.bannerImages.length > 0) {
|
|
||||||
return props.bannerImages[currentBannerIndex.value]
|
|
||||||
}
|
|
||||||
// 降级:如果没有广告,显示默认图片
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 验证坐标有效性
|
|
||||||
const isValidCoordinate = (lat, lng) => {
|
|
||||||
const latitude = parseFloat(lat)
|
|
||||||
const longitude = parseFloat(lng)
|
|
||||||
return !isNaN(latitude) && !isNaN(longitude) &&
|
|
||||||
latitude >= -90 && latitude <= 90 &&
|
|
||||||
longitude >= -180 && longitude <= 180 &&
|
|
||||||
!(latitude === 0 && longitude === 0) // 排除 0,0 这种无效坐标
|
|
||||||
}
|
|
||||||
|
|
||||||
// 防抖定时器
|
|
||||||
let regionChangeTimer = null
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
const updateMapMarkers = () => {
|
|
||||||
const markers = []
|
|
||||||
|
|
||||||
// 只添加周边场地位置点,中心定位图标改用固定的cover-view显示
|
|
||||||
if (props.enableMarkers && props.filteredPositions && props.filteredPositions.length > 0) {
|
|
||||||
props.filteredPositions.forEach((pos, index) => {
|
|
||||||
if (pos.longitude && pos.latitude && isValidCoordinate(pos.latitude, pos.longitude)) {
|
|
||||||
const lat = parseFloat(pos.latitude)
|
|
||||||
const lng = parseFloat(pos.longitude)
|
|
||||||
markers.push({
|
|
||||||
id: index + 1,
|
|
||||||
latitude: lat,
|
|
||||||
longitude: lng,
|
|
||||||
iconPath: '/static/markes_fdz.png',
|
|
||||||
width: 40,
|
|
||||||
height: 40,
|
|
||||||
callout: {
|
|
||||||
content: pos.name,
|
|
||||||
fontSize: 12,
|
|
||||||
borderRadius: 8,
|
|
||||||
bgColor: '#ffffff',
|
|
||||||
padding: 8,
|
|
||||||
display: 'BYCLICK'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
mapMarkers.value = markers
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移动地图到指定位置(直接更新地图中心坐标)
|
|
||||||
const moveToLocation = (location) => {
|
|
||||||
if (!location || !location.longitude || !location.latitude) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const newCenter = {
|
|
||||||
longitude: Number(location.longitude),
|
|
||||||
latitude: Number(location.latitude)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接更新地图中心,触发地图组件的视图更新
|
|
||||||
mapCenter.value = newCenter
|
|
||||||
|
|
||||||
// 更新标记点
|
|
||||||
updateMapMarkers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听用户位置变化
|
|
||||||
watch(() => props.userLocation, (newLocation, oldLocation) => {
|
|
||||||
if (newLocation && newLocation.longitude && newLocation.latitude) {
|
|
||||||
// 检查位置是否真的变化了(避免重复更新)
|
|
||||||
const isChanged = !oldLocation ||
|
|
||||||
oldLocation.longitude !== newLocation.longitude ||
|
|
||||||
oldLocation.latitude !== newLocation.latitude
|
|
||||||
|
|
||||||
if (isChanged) {
|
|
||||||
mapCenter.value = {
|
|
||||||
longitude: newLocation.longitude,
|
|
||||||
latitude: newLocation.latitude
|
|
||||||
}
|
|
||||||
updateMapMarkers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
immediate: true,
|
|
||||||
deep: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听位置列表变化
|
|
||||||
watch(() => props.filteredPositions, (newPositions) => {
|
|
||||||
updateMapMarkers()
|
|
||||||
}, {
|
|
||||||
deep: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听广告图片变化,启动或停止轮播
|
|
||||||
watch(() => props.bannerImages, (newImages, oldImages) => {
|
|
||||||
// 先停止旧的轮播
|
|
||||||
stopBannerRotation()
|
|
||||||
currentBannerIndex.value = 0
|
|
||||||
|
|
||||||
// 如果有多张图片,启动新的轮播
|
|
||||||
if (newImages && newImages.length > 1) {
|
|
||||||
// 使用 nextTick 确保 DOM 已更新
|
|
||||||
nextTick(() => {
|
|
||||||
startBannerRotation()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
immediate: true,
|
|
||||||
deep: true
|
|
||||||
})
|
|
||||||
|
|
||||||
// 地图加载完成事件
|
|
||||||
const onMapUpdated = () => {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 地图区域变化事件(带防抖优化)
|
|
||||||
const onMapRegionChange = (e) => {
|
|
||||||
|
|
||||||
// 只处理结束事件
|
|
||||||
if (!e || (e.type !== 'end' && e.type !== 'regionchange')) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const causedBy = e.causedBy || e.detail?.causedBy
|
|
||||||
|
|
||||||
if (causedBy === 'gesture' || causedBy === 'scale' || causedBy === 'drag' || causedBy === 'update') {
|
|
||||||
// 清除之前的定时器
|
|
||||||
if (regionChangeTimer) {
|
|
||||||
clearTimeout(regionChangeTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接从事件对象中获取最新的中心点位置
|
|
||||||
const centerLocation = e.detail?.centerLocation || e.centerLocation
|
|
||||||
|
|
||||||
if (centerLocation && centerLocation.longitude && centerLocation.latitude) {
|
|
||||||
// 防抖:500ms后执行查询
|
|
||||||
regionChangeTimer = setTimeout(() => {
|
|
||||||
const newCenter = {
|
|
||||||
longitude: Number(centerLocation.longitude),
|
|
||||||
latitude: Number(centerLocation.latitude)
|
|
||||||
}
|
|
||||||
|
|
||||||
mapCenter.value = newCenter;
|
|
||||||
|
|
||||||
|
|
||||||
// 触发父组件查询新位置的场地
|
|
||||||
emit('mapCenterChange', newCenter)
|
|
||||||
}, 500)
|
|
||||||
} else {
|
|
||||||
// 兜底方案:如果事件中没有centerLocation,才使用API获取
|
|
||||||
regionChangeTimer = setTimeout(() => {
|
|
||||||
if (mapContext.value) {
|
|
||||||
mapContext.value.getCenterLocation({
|
|
||||||
success: (res) => {
|
|
||||||
if (res && res.longitude && res.latitude) {
|
|
||||||
const newCenter = {
|
|
||||||
longitude: res.longitude,
|
|
||||||
latitude: res.latitude
|
|
||||||
}
|
|
||||||
mapCenter.value = newCenter
|
|
||||||
emit('mapCenterChange', newCenter)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('获取地图中心失败:', err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记点点击事件
|
|
||||||
const onMapMarkerTap = (e) => {
|
|
||||||
const markerId = e.detail?.markerId || e.markerId
|
|
||||||
|
|
||||||
// 查找对应的场地位置信息
|
|
||||||
if (props.filteredPositions && props.filteredPositions.length > 0) {
|
|
||||||
const position = props.filteredPositions[markerId - 1]
|
|
||||||
if (position) {
|
|
||||||
emit('markerTap', position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记点气泡点击事件
|
|
||||||
const onCalloutTap = (e) => {
|
|
||||||
const markerId = e.markerId
|
|
||||||
const marker = mapMarkers.value.find(item => item.id === markerId)
|
|
||||||
|
|
||||||
if (marker && marker.position) {
|
|
||||||
emit('markerTap', marker.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 地图错误事件
|
|
||||||
const onMapError = (error) => {
|
|
||||||
console.error('地图加载失败:', error)
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRelocate = () => {
|
|
||||||
// 直接委托父级处理定位并移动地图,避免内部重复弹 loading
|
|
||||||
try {
|
|
||||||
emit('relocate')
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSearch = () => {
|
|
||||||
try {
|
|
||||||
uni.navigateTo({ url: '/pages/search/index' })
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleService = () => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/subPackages/service/help/index'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleGuide = () => {
|
|
||||||
emit('guide')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理广告点击
|
|
||||||
const handleBannerTap = () => {
|
|
||||||
// 触发父组件处理点击事件
|
|
||||||
emit('bannerClick', currentBannerIndex.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启动广告轮播
|
|
||||||
const startBannerRotation = () => {
|
|
||||||
// 如果只有一张或没有图片,不需要轮播
|
|
||||||
if (!props.bannerImages || props.bannerImages.length <= 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清除旧的定时器
|
|
||||||
stopBannerRotation()
|
|
||||||
|
|
||||||
// 每3秒切换一次
|
|
||||||
bannerTimer = setInterval(() => {
|
|
||||||
const nextIndex = (currentBannerIndex.value + 1) % props.bannerImages.length
|
|
||||||
currentBannerIndex.value = nextIndex
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 停止广告轮播
|
|
||||||
const stopBannerRotation = () => {
|
|
||||||
if (bannerTimer) {
|
|
||||||
clearInterval(bannerTimer)
|
|
||||||
bannerTimer = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生命周期钩子
|
|
||||||
onMounted(() => {
|
|
||||||
// 初始化地图上下文
|
|
||||||
nextTick(() => {
|
|
||||||
// 需要使用nextTick确保地图组件已经渲染
|
|
||||||
const inst = getCurrentInstance()
|
|
||||||
const vm = (inst && (inst.proxy || inst)) || undefined
|
|
||||||
try {
|
|
||||||
mapContext.value = uni.createMapContext('map', vm)
|
|
||||||
} catch (e) {
|
|
||||||
// 兼容:如果第二参不被支持,退回单参
|
|
||||||
mapContext.value = uni.createMapContext('map')
|
|
||||||
}
|
|
||||||
updateMapMarkers()
|
|
||||||
|
|
||||||
// 初始化折叠面板
|
|
||||||
if (collapseRef.value) {
|
|
||||||
collapseRef.value.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化广告轮播
|
|
||||||
if (props.bannerImages && props.bannerImages.length > 1) {
|
|
||||||
startBannerRotation()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
// 清理工作
|
|
||||||
if (regionChangeTimer) {
|
|
||||||
clearTimeout(regionChangeTimer)
|
|
||||||
regionChangeTimer = null
|
|
||||||
}
|
|
||||||
// 停止广告轮播
|
|
||||||
stopBannerRotation()
|
|
||||||
mapContext.value = null
|
|
||||||
})
|
|
||||||
|
|
||||||
// 暴露给父组件的方法
|
|
||||||
defineExpose({
|
|
||||||
mapCenter: computed(() => mapCenter.value),
|
|
||||||
moveToLocation,
|
|
||||||
updateMapMarkers,
|
|
||||||
initCollapse: () => {
|
|
||||||
if (collapseRef.value) {
|
|
||||||
collapseRef.value.init()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
/* 地图容器 */
|
|
||||||
.map-container {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 94vw;
|
|
||||||
margin: 20rpx;
|
|
||||||
margin-bottom: 0; /* 底部不需要边距 */
|
|
||||||
border-radius: 20rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: 72vh;
|
|
||||||
|
|
||||||
&.full-width {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
overflow: visible; /* 支付宝小程序:允许覆盖层显示在地图外部 */
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
.native-map {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-loading {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
.loading-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
border: 8rpx solid #f3f3f3;
|
|
||||||
border-top: 8rpx solid #3498db;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 支付宝小程序专用样式 - 所有覆盖层元素相对于 map-wrapper 定位 */
|
|
||||||
/* 广告图片样式 */
|
|
||||||
.index-swiper-img-alipay {
|
|
||||||
position: absolute;
|
|
||||||
top: 20rpx;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 90vw;
|
|
||||||
height: 120rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
z-index: 100; /* 确保在地图之上 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 轮播指示器独立定位 */
|
|
||||||
.indicator-dot-alipay {
|
|
||||||
position: absolute;
|
|
||||||
top: 130rpx; /* 广告图片 top(20rpx) + height(120rpx) - 10rpx = 130rpx */
|
|
||||||
width: 12rpx;
|
|
||||||
height: 12rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 101; /* 确保在广告图片之上 */
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
width: 24rpx;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 地图中心定位图标样式 */
|
|
||||||
.center-location-marker-alipay {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
z-index: 99; /* 确保在地图之上 */
|
|
||||||
width: 60rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
margin-left: -30rpx;
|
|
||||||
margin-top: -80rpx;
|
|
||||||
pointer-events: none;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
.center-marker-icon-alipay {
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 侧边控制按钮容器 */
|
|
||||||
.map-side-controls-alipay {
|
|
||||||
position: absolute;
|
|
||||||
right: 20rpx;
|
|
||||||
bottom: 160rpx; /* 向上移动,避免被底部按钮遮挡 */
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
height: 300rpx;
|
|
||||||
margin: auto;
|
|
||||||
gap: 12rpx;
|
|
||||||
z-index: 102; /* 确保在最上层 */
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
.side-btn-alipay {
|
|
||||||
margin: auto;
|
|
||||||
background: rgba(255, 255, 255, 0.96);
|
|
||||||
border-radius: 24rpx;
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 13rpx;
|
|
||||||
border: 2rpx solid #e0e0e0;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-icon-alipay {
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
z-index:1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -8,28 +8,22 @@
|
|||||||
</view>
|
</view>
|
||||||
<view class="header-right">
|
<view class="header-right">
|
||||||
<!-- 支付方式标识(移到头部右侧) -->
|
<!-- 支付方式标识(移到头部右侧) -->
|
||||||
<view class="payment-badge wx-score" v-if="order.payWay == 'wx_score_pay'">
|
<view class="payment-badge alipay-score" v-if="order.payWay == 'alipay_score_pay'">
|
||||||
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon" lazy-load="true"></image>
|
<image src="/static/images/alipay.svg" mode="aspectFit" class="badge-icon"></image>
|
||||||
<view class="badge-text">
|
<view class="badge-text">
|
||||||
<text>{{ $t('order.wxPayScore') }}</text>
|
<text>{{ $t('order.alipayScore') }}</text>
|
||||||
<text class="divider">|</text>
|
<text class="divider">|</text>
|
||||||
<text class="highlight">{{ $t('order.depositFree') }}</text>
|
<text class="highlight">{{ $t('order.depositFree') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="payment-badge whitelist" v-else-if="order.payWay == 'wx_global_pay'">
|
<view class="payment-badge whitelist" v-else-if="order.payWay == 'alipay_global_pay'">
|
||||||
<text class="badge-text">{{ $t('order.whitelistOrder') }}</text>
|
<text class="badge-text">{{ $t('order.whitelistOrder') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="payment-badge member" v-else-if="order.payWay == 'wx_member_pay'">
|
<view class="payment-badge member" v-else-if="order.payWay == 'alipay_member_pay'">
|
||||||
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
|
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="payment-badge member" v-else-if="order.payWay == 'ali_pay'">
|
|
||||||
<text class="badge-text">{{ $t('order.aliPay') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="payment-badge member" v-else-if="order.payWay == 'antom_pay'">
|
|
||||||
<text class="badge-text">{{ $t('order.antomPay') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="payment-badge deposit" v-else>
|
<view class="payment-badge deposit" v-else>
|
||||||
<text class="badge-text">{{ $t('order.wxPay') }}</text>
|
<text class="badge-text">{{ $t('order.alipayPay') }}</text>
|
||||||
<text class="divider">|</text>
|
<text class="divider">|</text>
|
||||||
<text class="badge-text">{{ $t('order.depositPay') }}</text>
|
<text class="badge-text">{{ $t('order.depositPay') }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -69,16 +63,16 @@
|
|||||||
<view class="order-footer">
|
<view class="order-footer">
|
||||||
<view class="footer-left">
|
<view class="footer-left">
|
||||||
<view v-if="isInUse" class="renting">
|
<view v-if="isInUse" class="renting">
|
||||||
<image src="/static/order_time.png" mode="aspectFit" class="icon-time" lazy-load="true"></image>
|
<image src="/static/order_time.png" mode="aspectFit" class="icon-time"></image>
|
||||||
{{ $t('order.renting') }}
|
{{ $t('order.renting') }}
|
||||||
</view>
|
</view>
|
||||||
<view v-else-if="isFinished" class="meta">
|
<view v-else-if="isFinished" class="meta">
|
||||||
<view class="meta-item">
|
<view class="meta-item">
|
||||||
<image src="/static/order_time.png" mode="aspectFit" class="icon-time" lazy-load="true"></image>
|
<image src="/static/order_time.png" mode="aspectFit" class="icon-time"></image>
|
||||||
{{ usedDurationText }}
|
{{ usedDurationText }}
|
||||||
</view>
|
</view>
|
||||||
<view class="meta-item">
|
<view class="meta-item">
|
||||||
<image src="/static/order_price.png" mode="aspectFit" class="icon-price" lazy-load="true"></image>
|
<image src="/static/order_price.png" mode="aspectFit" class="icon-price"></image>
|
||||||
{{ displayAmount }}
|
{{ displayAmount }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -104,7 +98,7 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
order: { type: Object, required: true },
|
order: { type: Object, required: true },
|
||||||
@@ -113,7 +107,7 @@
|
|||||||
|
|
||||||
const emit = defineEmits(['pay', 'cancel', 'return-device', 'details']);
|
const emit = defineEmits(['pay', 'cancel', 'return-device', 'details']);
|
||||||
|
|
||||||
const rawStatus = computed(() => props.order.orderStatus ?? props.order.status);
|
const rawStatus = computed(() => props.order.orderStatus != null ? props.order.orderStatus : props.order.status);
|
||||||
const normalizedStatus = computed(() => {
|
const normalizedStatus = computed(() => {
|
||||||
const s = rawStatus.value;
|
const s = rawStatus.value;
|
||||||
switch (s) {
|
switch (s) {
|
||||||
@@ -134,19 +128,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasPauseTime = computed(() => {
|
|
||||||
const pt = props.order.pauseTime
|
|
||||||
return pt !== undefined && pt !== null && String(pt).trim() !== ''
|
|
||||||
})
|
|
||||||
const isBillingPaused = computed(() => normalizedStatus.value === 'in_used' && hasPauseTime.value)
|
|
||||||
|
|
||||||
const statusDef = computed(() => props.orderStatusMap?.[rawStatus.value] || props.orderStatusMap?.[normalizedStatus.value] || {});
|
const statusDef = computed(() => props.orderStatusMap?.[rawStatus.value] || props.orderStatusMap?.[normalizedStatus.value] || {});
|
||||||
const statusText = computed(() => {
|
const statusText = computed(() => statusDef.value.text || '');
|
||||||
if (isBillingPaused.value) return t('order.orderStatusBillingPaused')
|
|
||||||
return statusDef.value.text || ''
|
|
||||||
});
|
|
||||||
const statusChipClass = computed(() => {
|
const statusChipClass = computed(() => {
|
||||||
if (isBillingPaused.value) return 'chip-paused'
|
|
||||||
const cls = statusDef.value.class || '';
|
const cls = statusDef.value.class || '';
|
||||||
if (cls.includes('status-using')) return 'chip-using';
|
if (cls.includes('status-using')) return 'chip-using';
|
||||||
if (cls.includes('status-waiting')) return 'chip-waiting';
|
if (cls.includes('status-waiting')) return 'chip-waiting';
|
||||||
@@ -160,7 +144,7 @@
|
|||||||
const isFinished = computed(() => normalizedStatus.value === 'used_done');
|
const isFinished = computed(() => normalizedStatus.value === 'used_done');
|
||||||
const isCancelled = computed(() => normalizedStatus.value === 'order_cancelled');
|
const isCancelled = computed(() => normalizedStatus.value === 'order_cancelled');
|
||||||
|
|
||||||
const titleText = computed(() => t('order.rentFan'));
|
const titleText = computed(() => $t('order.rentFan'));
|
||||||
|
|
||||||
// 显示金额(优先后端给定字段)
|
// 显示金额(优先后端给定字段)
|
||||||
const displayAmount = computed(() => props.order.amount || props.order.payAmount || props.order.actualDeviceAmount || props.order.currentFee || '0.00');
|
const displayAmount = computed(() => props.order.amount || props.order.payAmount || props.order.actualDeviceAmount || props.order.currentFee || '0.00');
|
||||||
@@ -174,10 +158,8 @@
|
|||||||
const minutes = Math.floor(diffMs / 60000);
|
const minutes = Math.floor(diffMs / 60000);
|
||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
const mins = minutes % 60;
|
const mins = minutes % 60;
|
||||||
if (hours > 0) return `${hours}${t('time.hour')}${mins}${t('time.minute')}`;
|
if (hours > 0) return `${hours}${$t('time.hour')}${mins}${$t('time.minute')}`;
|
||||||
// 如果小于1分钟,显示"小于1分钟"
|
return `${mins}${$t('time.minute')}`;
|
||||||
if (minutes < 1) return `${t('time.lessThan')}1${t('time.minute')}`;
|
|
||||||
return `${mins}${t('time.minute')}`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseDate(str) {
|
function parseDate(str) {
|
||||||
@@ -222,7 +204,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.chip-text { display: inline-block; transform: skewX(15deg); }
|
.chip-text { display: inline-block; transform: skewX(15deg); }
|
||||||
&.chip-using { background: rgba(7,193,96,0.12); color: #07c160; }
|
&.chip-using { background: rgba(7,193,96,0.12); color: #07c160; }
|
||||||
&.chip-paused { background: rgba(255,152,0,0.16); color: #e65100; }
|
|
||||||
&.chip-waiting { background: rgba(255,152,0,0.12); color: #FF9800; }
|
&.chip-waiting { background: rgba(255,152,0,0.12); color: #FF9800; }
|
||||||
&.chip-finished { background: rgba(76,175,80,0.12); color: #4CAF50; }
|
&.chip-finished { background: rgba(76,175,80,0.12); color: #4CAF50; }
|
||||||
&.chip-cancelled { background: rgba(158,158,158,0.12); color: #9E9E9E; }
|
&.chip-cancelled { background: rgba(158,158,158,0.12); color: #9E9E9E; }
|
||||||
@@ -291,8 +272,8 @@
|
|||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&.wx-score {
|
&.alipay-score {
|
||||||
background: rgba(7, 193, 96, 0.08);
|
background: rgba(0, 122, 255, 0.08);
|
||||||
|
|
||||||
.badge-icon {
|
.badge-icon {
|
||||||
width: 32rpx;
|
width: 32rpx;
|
||||||
@@ -302,7 +283,7 @@
|
|||||||
|
|
||||||
.badge-text {
|
.badge-text {
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
color: #07c160;
|
color: #007AFF;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
|||||||
@@ -1,293 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="h5-home-wrap">
|
|
||||||
<view class="main-content" :style="mainContentStyle">
|
|
||||||
<view class="h5-banner-section" v-if="bannerImages && bannerImages.length > 0">
|
|
||||||
<swiper class="h5-banner-swiper" :indicator-dots="bannerImages.length > 1" :autoplay="true" :interval="4000"
|
|
||||||
:duration="500" circular>
|
|
||||||
<swiper-item v-for="(img, idx) in bannerImages" :key="idx">
|
|
||||||
<view class="h5-banner-item" @click="$emit('bannerClick', idx)">
|
|
||||||
<image :src="img" mode="aspectFill" class="h5-banner-image"></image>
|
|
||||||
</view>
|
|
||||||
</swiper-item>
|
|
||||||
</swiper>
|
|
||||||
</view>
|
|
||||||
<view class="h5-banner-empty" v-else>
|
|
||||||
<text>{{ loadingText }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="h5-cta-card">
|
|
||||||
<view class="h5-cta-header">
|
|
||||||
<text class="h5-cta-title">{{ scanText }}</text>
|
|
||||||
<text class="h5-cta-desc">{{ t('home.h5ScanDesc') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="h5-cta-btn" @click="$emit('scan')">
|
|
||||||
<image class="h5-cta-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
|
||||||
<text class="h5-cta-btn-text">{{ scanText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="h5-section">
|
|
||||||
<view class="h5-section-title">{{ t('home.h5HowItWorks') }}</view>
|
|
||||||
<view class="h5-steps">
|
|
||||||
<view class="h5-step-item">
|
|
||||||
<text class="h5-step-index">1</text>
|
|
||||||
<text class="h5-step-text">{{ t('home.h5StepScan') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="h5-step-item">
|
|
||||||
<text class="h5-step-index">2</text>
|
|
||||||
<text class="h5-step-text">{{ t('home.h5StepUnlock') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="h5-step-item">
|
|
||||||
<text class="h5-step-index">3</text>
|
|
||||||
<text class="h5-step-text">{{ t('home.h5StepReturn') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="h5-section">
|
|
||||||
<view class="h5-section-title">{{ t('home.h5ServiceTitle') }}</view>
|
|
||||||
<view class="h5-tags">
|
|
||||||
<text class="h5-tag">{{ t('home.h5ServiceSupport') }}</text>
|
|
||||||
<text class="h5-tag">{{ t('home.h5ServiceSafePayment') }}</text>
|
|
||||||
<text class="h5-tag">{{ t('home.h5ServiceEasyReturn') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="h5-bottom-actions">
|
|
||||||
<view class="action-btn primary" @click="$emit('scan')">
|
|
||||||
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFit" />
|
|
||||||
<text class="action-label primary-label">{{ scanText }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="action-btn secondary" @click="$emit('my')">
|
|
||||||
<image class="action-icon" src="/static/user.png" mode="aspectFit" />
|
|
||||||
<text class="action-label">{{ personalCenterText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { useI18n } from '../../utils/i18n.js'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
defineEmits(['bannerClick', 'scan', 'buy', 'my'])
|
|
||||||
defineProps({
|
|
||||||
mainContentStyle: { type: Object, default: () => ({}) },
|
|
||||||
bannerImages: { type: Array, default: () => [] },
|
|
||||||
loadingText: { type: String, default: '' },
|
|
||||||
buyDeviceText: { type: String, default: '' },
|
|
||||||
scanText: { type: String, default: '' },
|
|
||||||
personalCenterText: { type: String, default: '' }
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.h5-home-wrap {
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
padding-bottom: 220rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-banner-section {
|
|
||||||
padding: 24rpx 20rpx 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-banner-swiper {
|
|
||||||
width: 100%;
|
|
||||||
height: 320rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
background: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-banner-item,
|
|
||||||
.h5-banner-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-banner-empty {
|
|
||||||
padding: 80rpx 20rpx 0;
|
|
||||||
text-align: center;
|
|
||||||
color: #999;
|
|
||||||
font-size: 26rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-cta-card {
|
|
||||||
margin: 20rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
background: #ffffff;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 20rpx;
|
|
||||||
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-cta-header {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-cta-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #1f7d43;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-cta-desc {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #4f7b61;
|
|
||||||
margin-top: 8rpx;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-cta-btn {
|
|
||||||
height: 76rpx;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
background: #3EAB64;
|
|
||||||
border-radius: 38rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-cta-icon {
|
|
||||||
width: 30rpx;
|
|
||||||
height: 30rpx;
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-cta-btn-text {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-section {
|
|
||||||
margin: 0 20rpx 20rpx;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 20rpx;
|
|
||||||
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-section-title {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #2d2d2d;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-steps {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-step-item {
|
|
||||||
background: #f4f8f6;
|
|
||||||
border-radius: 14rpx;
|
|
||||||
padding: 16rpx 10rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-step-index {
|
|
||||||
width: 34rpx;
|
|
||||||
height: 34rpx;
|
|
||||||
background: #3EAB64;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-step-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #315c46;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-tags {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-tag {
|
|
||||||
padding: 10rpx 16rpx;
|
|
||||||
background: #eef8f1;
|
|
||||||
color: #2e7d4f;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
font-size: 22rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h5-bottom-actions {
|
|
||||||
position: fixed;
|
|
||||||
left: 20rpx;
|
|
||||||
right: 20rpx;
|
|
||||||
bottom: 30rpx;
|
|
||||||
z-index: 1200;
|
|
||||||
padding: 12rpx;
|
|
||||||
background: rgba(255, 255, 255, 0.96);
|
|
||||||
border-radius: 28rpx;
|
|
||||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
min-height: 84rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 0 20rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.primary {
|
|
||||||
flex: 1;
|
|
||||||
background: #3EAB64;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.secondary {
|
|
||||||
width: 180rpx;
|
|
||||||
background: #f4f6f8;
|
|
||||||
border: 2rpx solid #e7eaee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-icon {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.primary .action-icon {
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-label {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-label {
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view>
|
|
||||||
<view class="main-content" :style="mainContentStyle">
|
|
||||||
<!-- 支付宝小程序使用专用组件 -->
|
|
||||||
<!-- #ifdef MP-ALIPAY -->
|
|
||||||
<MapComponentAlipay v-if="!isLoading && userLocation && !locationPermissionDenied" ref="innerMapRef"
|
|
||||||
:userLocation="userLocation" :positionList="positionList" :filteredPositions="filteredPositions"
|
|
||||||
:searchKeyword="searchKeyword" :enableMarkers="true" :bannerImages="bannerImages"
|
|
||||||
:hideMapOverlays="hideMapOverlays" @relocate="$emit('relocate')" @scan="$emit('scan')"
|
|
||||||
@showList="$emit('showList')" @markerTap="$emit('markerTap', $event)"
|
|
||||||
@mapCenterChange="$emit('mapCenterChange', $event)" @bannerClick="$emit('bannerClick', $event)"
|
|
||||||
@guide="$emit('guide')" />
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 非支付宝小程序使用通用组件 -->
|
|
||||||
<!-- #ifndef MP-ALIPAY -->
|
|
||||||
<MapComponent v-if="!isLoading && userLocation && !locationPermissionDenied" ref="innerMapRef"
|
|
||||||
:userLocation="userLocation" :positionList="positionList" :filteredPositions="filteredPositions"
|
|
||||||
:searchKeyword="searchKeyword" :enableMarkers="true" :bannerImages="bannerImages"
|
|
||||||
:hideMapOverlays="hideMapOverlays" @relocate="$emit('relocate')" @scan="$emit('scan')"
|
|
||||||
@showList="$emit('showList')" @markerTap="$emit('markerTap', $event)"
|
|
||||||
@mapCenterChange="$emit('mapCenterChange', $event)" @bannerClick="$emit('bannerClick', $event)"
|
|
||||||
@guide="$emit('guide')" />
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 地图加载状态 -->
|
|
||||||
<!-- #ifdef MP-ALIPAY -->
|
|
||||||
<view v-if="!userLocation" class="location-denied-placeholder">
|
|
||||||
<view class="denied-content">
|
|
||||||
<text class="denied-text">{{ locationPermissionText }}</text>
|
|
||||||
<view class="denied-enable-btn" @click="$emit('enableLocation')">
|
|
||||||
<text class="denied-enable-btn-text">{{ enableLocationText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- #ifndef MP-ALIPAY -->
|
|
||||||
<view v-if="isLoading || !userLocation" class="map-loading-placeholder">
|
|
||||||
<view class="loading-content">
|
|
||||||
<view class="loading-spinner"></view>
|
|
||||||
<text>{{ loadingLocationText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- #endif -->
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="bottom-actions">
|
|
||||||
<view class="action-btn secondary small btn-nearby" @click="$emit('buy')">
|
|
||||||
<view class="icon-wrap">
|
|
||||||
<image src="/static/shop_icon.png" class="action-icon" mode="scaleToFill" lazy-load="true"></image>
|
|
||||||
</view>
|
|
||||||
<text class="action-label">{{ buyDeviceText }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="action-btn primary btn-scan" @click="$emit('scan')">
|
|
||||||
<view class="icon-wrap">
|
|
||||||
<image class="action-icon" src="/static/scan-icon.png" mode="aspectFill" lazy-load="true" />
|
|
||||||
</view>
|
|
||||||
<text class="primary-label">{{ scanText }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="action-btn secondary small btn-my" @click="$emit('my')">
|
|
||||||
<view class="icon-wrap">
|
|
||||||
<image class="action-icon" src="/static/user.png" mode="aspectFit" lazy-load="true" />
|
|
||||||
</view>
|
|
||||||
<text class="action-label">{{ personalCenterText }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
import MapComponent from '../MapComponent.vue'
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
import MapComponentAlipay from '../MapComponentAlipay.vue'
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
defineEmits(['relocate', 'scan', 'showList', 'markerTap', 'mapCenterChange', 'bannerClick', 'guide', 'enableLocation', 'buy', 'my'])
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
mainContentStyle: { type: Object, default: () => ({}) },
|
|
||||||
isLoading: { type: Boolean, default: false },
|
|
||||||
userLocation: { type: Object, default: null },
|
|
||||||
locationPermissionDenied: { type: Boolean, default: false },
|
|
||||||
positionList: { type: Array, default: () => [] },
|
|
||||||
filteredPositions: { type: Array, default: () => [] },
|
|
||||||
searchKeyword: { type: String, default: '' },
|
|
||||||
bannerImages: { type: Array, default: () => [] },
|
|
||||||
hideMapOverlays: { type: Boolean, default: false },
|
|
||||||
locationPermissionText: { type: String, default: '' },
|
|
||||||
enableLocationText: { type: String, default: '' },
|
|
||||||
loadingLocationText: { type: String, default: '' },
|
|
||||||
buyDeviceText: { type: String, default: '' },
|
|
||||||
scanText: { type: String, default: '' },
|
|
||||||
personalCenterText: { type: String, default: '' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const innerMapRef = ref(null)
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
mapCenter: computed(() => innerMapRef.value?.mapCenter || null),
|
|
||||||
moveToLocation: (location) => innerMapRef.value && innerMapRef.value.moveToLocation && innerMapRef.value.moveToLocation(location)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.main-content {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding-bottom: 180rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-loading-placeholder {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 180rpx;
|
|
||||||
background: #f6f7fb;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
.loading-content {
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 40rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
width: 60rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
border: 4rpx solid #f0f0f0;
|
|
||||||
border-top: 4rpx solid #2196F3;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-denied-placeholder {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 180rpx;
|
|
||||||
background: #f6f7fb;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 40rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.denied-content {
|
|
||||||
width: 100%;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
padding: 40rpx;
|
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
.denied-text {
|
|
||||||
display: block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.denied-enable-btn {
|
|
||||||
margin-top: 28rpx;
|
|
||||||
width: 100%;
|
|
||||||
height: 88rpx;
|
|
||||||
background: #3EAB64;
|
|
||||||
border-radius: 44rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.denied-enable-btn-text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-actions {
|
|
||||||
position: fixed;
|
|
||||||
left: 20rpx;
|
|
||||||
right: 20rpx;
|
|
||||||
bottom: 40rpx;
|
|
||||||
z-index: 1200;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.primary {
|
|
||||||
background: #3EAB64;
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 56rpx;
|
|
||||||
height: 112rpx;
|
|
||||||
flex: 1;
|
|
||||||
max-width: 400rpx;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.secondary {
|
|
||||||
color: #333;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
height: 100rpx;
|
|
||||||
width: 140rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
padding: 8rpx 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-wrap {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-scan .icon-wrap {
|
|
||||||
width: 48rpx;
|
|
||||||
height: 48rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-icon {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-scan .action-icon {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-label {
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-label {
|
|
||||||
line-height: 1.2;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import request from '../http'
|
|
||||||
|
|
||||||
// 按场地查询可用优惠券
|
|
||||||
export const getCouponsByPosition = (positionId) => {
|
|
||||||
return request({
|
|
||||||
url: `/device/coupon/app/position/${positionId}`,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建优惠券支付订单
|
|
||||||
export const createCouponPayment = (couponId, paymentPlatform) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/coupon/pay',
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
couponId,
|
|
||||||
// 支付平台类型:WECHAT / ALIPAY / ANTOM(不传则后端默认 WECHAT)
|
|
||||||
...(paymentPlatform ? { paymentPlatform } : {})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询用户优惠券
|
|
||||||
export const getUserCoupons = (status) => {
|
|
||||||
return request({
|
|
||||||
url: '/device/userPurchase/app/my',
|
|
||||||
method: 'get',
|
|
||||||
data: {
|
|
||||||
status
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const cancelCouponPayment = (orderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/device/userPurchase/app/cancel/${orderNo}`,
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -50,14 +50,32 @@ export const transformDeviceData = (device) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 立即租借
|
// 立即租借
|
||||||
export const rentPowerBank = (deviceNo, phone,payway) => {
|
export const rentPowerBank = (deviceNo, phone) => {
|
||||||
return request({
|
return request({
|
||||||
url: `/app/device/rentPowerBank?deviceNo=${deviceNo}`,
|
url: `/app/device/rentPowerBank?deviceNo=${deviceNo}`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
// deviceNo,
|
// deviceNo,
|
||||||
phone,
|
phone
|
||||||
payway
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确认支付并弹出风扇
|
||||||
|
export const confirmPaymentAndRent = (orderId) => {
|
||||||
|
console.log(`确认支付并弹出风扇, orderId: ${orderId}`)
|
||||||
|
return request({
|
||||||
|
url: `/app/device/confirmPaymentAndRent?orderId=${orderId}`,
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制打开空格子
|
||||||
|
export const forcefOpenEmptyGrid = (deviceNo) => {
|
||||||
|
console.log(`强制打开空格子, deviceNo: ${deviceNo}`)
|
||||||
|
return request({
|
||||||
|
url: `/app/device/forcef/${deviceNo}`,
|
||||||
|
method: 'post'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import request from '../http'
|
|
||||||
|
|
||||||
// 创建会员卡支付订单
|
|
||||||
export const createMemberCardPayment = (memberCardId, paymentPlatform) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/member/pay',
|
|
||||||
method: 'post',
|
|
||||||
data: {
|
|
||||||
memberCardId,
|
|
||||||
// 支付平台类型:WECHAT / ALIPAY / ANTOM(不传则后端默认 WECHAT)
|
|
||||||
...(paymentPlatform ? { paymentPlatform } : {})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 根据场地ID查询可用会员卡列表
|
|
||||||
export const getMemberCardsByPosition = (positionId) => {
|
|
||||||
return request({
|
|
||||||
url: `/device/memberCard/app/position/${positionId}`,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//根据状态查询个人会员卡
|
|
||||||
export const getMemberCardsByStatus = () => {
|
|
||||||
return request({
|
|
||||||
url: `/device/userMemberCard/app/my`,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消会员卡支付订单
|
|
||||||
export const cancelMemberCardPayment = (orderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/device/userMemberCard/app/cancel/${orderNo}`,
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -10,16 +10,6 @@ export const getOrderList = (data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户端查询商品订单列表
|
|
||||||
export const getProductOrderList = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/product/order/list',
|
|
||||||
method: 'get',
|
|
||||||
data,
|
|
||||||
hideLoading: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询是否有订单
|
// 查询是否有订单
|
||||||
export const queryHasOrder = (deviceNo) => {
|
export const queryHasOrder = (deviceNo) => {
|
||||||
return request({
|
return request({
|
||||||
@@ -51,6 +41,7 @@ export const createOrder = (data) => {
|
|||||||
|
|
||||||
// 查询订单
|
// 查询订单
|
||||||
export const queryById = (id) => {
|
export const queryById = (id) => {
|
||||||
|
console.log(`查询订单详情, orderId: ${id}`)
|
||||||
return request({
|
return request({
|
||||||
url: `/app/order/${id}`,
|
url: `/app/order/${id}`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@@ -58,15 +49,6 @@ export const queryById = (id) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户查询商品订单详情
|
|
||||||
export const getProductOrderDetail = (id) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/product/order/${id}`,
|
|
||||||
method: 'get',
|
|
||||||
hideLoading: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
export const cancelOrder = (data) => {
|
export const cancelOrder = (data) => {
|
||||||
return request({
|
return request({
|
||||||
@@ -78,6 +60,7 @@ export const cancelOrder = (data) => {
|
|||||||
|
|
||||||
// 结束订单
|
// 结束订单
|
||||||
export const overOrderById = (orderId) => {
|
export const overOrderById = (orderId) => {
|
||||||
|
console.log(`调用结束订单API, orderId: ${orderId}`)
|
||||||
return request({
|
return request({
|
||||||
url: `/app/order/close/${orderId}`,
|
url: `/app/order/close/${orderId}`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
@@ -93,141 +76,21 @@ export const getOrderByOrderNo = (orderNo) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 充电宝未弹出反馈(快捷反馈)
|
// 通过订单号创建支付宝支付订单(芝麻信用免押)
|
||||||
export const reportDeviceNoEject = (data) => {
|
export const getOrderByOrderNoScore = (orderNo) => {
|
||||||
return request({
|
console.log('通过订单号创建支付宝支付订单(芝麻信用免押)', orderNo);
|
||||||
url: '/app/order/report-no-eject',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 充电宝转为自用
|
|
||||||
export const convertToOwned = (orderId) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/order/convert-to-owned/${orderId}`,
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不想还了转为自用(按最高费用)
|
|
||||||
export const closeWithMaxFee = (orderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/order/closeWithMaxFee/${orderNo}`,
|
|
||||||
method: 'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建微信支付订单
|
|
||||||
export const createWxPayment = (orderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/wx-payment/create/${orderNo}`,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建支付宝支付订单(租借押金 H5 支付)
|
|
||||||
// 对应文档《支付宝接口文档》:GET /app/ali-payment/create/{orderNo}
|
|
||||||
export const createAliPayment = (orderNo) => {
|
|
||||||
return request({
|
return request({
|
||||||
url: `/app/ali-payment/create/${orderNo}`,
|
url: `/app/ali-payment/create/${orderNo}`,
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取正在使用中的订单(可传 hideLoading: true 由业务自行控制 loading)
|
|
||||||
export const getInUseOrder = (opts = {}) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/order/inUse',
|
|
||||||
method: 'get',
|
|
||||||
...opts
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询订单是否可申请暂停计费
|
|
||||||
export const getPauseBillingEligible = (orderId) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/order/pauseBilling/eligible/${orderId}`,
|
|
||||||
method: 'get',
|
method: 'get',
|
||||||
hideLoading: true
|
hideLoading: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对订单执行暂停计费
|
// 通过订单号查询支付宝订单支付状态
|
||||||
export const requestPauseBilling = (orderId) => {
|
export const getOrderByOrderNoScorePayStatus = (orderNo) => {
|
||||||
return request({
|
console.log('通过订单号查询支付宝订单支付状态', orderNo);
|
||||||
url: `/app/order/pauseBilling/${orderId}`,
|
|
||||||
method: 'post',
|
|
||||||
hideLoading: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取待支付订单
|
|
||||||
export const getUnpaidOrder = () => {
|
|
||||||
return request({
|
|
||||||
url: '/app/order/unpaid',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询微信支付状态
|
|
||||||
export const getWxPaymentStatus = (orderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/wx-payment/status/${orderNo}`,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询支付宝支付状态
|
|
||||||
// 对应文档:GET /app/ali-payment/status/{orderNo}
|
|
||||||
export const getAliPaymentStatus = (orderNo) => {
|
|
||||||
return request({
|
return request({
|
||||||
url: `/app/ali-payment/status/${orderNo}`,
|
url: `/app/ali-payment/status/${orderNo}`,
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== Antom 支付相关接口 ====================
|
|
||||||
|
|
||||||
// 创建 Antom H5 支付订单
|
|
||||||
export const createAntomPayment = (orderNo, paymentType, osType) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/antom-payment/create/${orderNo}?paymentType=${paymentType}&osType=${osType}`,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取 Antom 可用支付方式列表
|
|
||||||
export const getAntomPaymentMethods = (orderNo, osType) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/antom-payment/consult/${orderNo}?osType=${osType}`,
|
|
||||||
method: 'get',
|
|
||||||
hideLoading: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Antom 支付结果查询
|
|
||||||
export const getAntomPaymentStatus = (orderNo, osType) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/antom-payment/inquiry/${orderNo}?osType=${osType}`,
|
|
||||||
method: 'get',
|
|
||||||
hideLoading: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过订单号获取支付分订单信息
|
|
||||||
export const getOrderByOrderNoScore = (orderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/wx-payment/score/create/${orderNo}`,
|
|
||||||
method: 'get',
|
|
||||||
hideLoading: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过订单号获取支付分订单状态
|
|
||||||
export const getOrderByOrderNoScorePayStatus = (orderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/wx-payment/score/status/${orderNo}`,
|
|
||||||
method: 'get',
|
method: 'get',
|
||||||
hideLoading: true
|
hideLoading: true
|
||||||
})
|
})
|
||||||
@@ -235,6 +98,7 @@ export const getOrderByOrderNoScorePayStatus = (orderNo) => {
|
|||||||
|
|
||||||
// 更新订单套餐信息
|
// 更新订单套餐信息
|
||||||
export const updateOrderPackage = (data) => {
|
export const updateOrderPackage = (data) => {
|
||||||
|
console.log('更新订单套餐信息:', data)
|
||||||
return request({
|
return request({
|
||||||
url: '/app/device/updateOrderPackage',
|
url: '/app/device/updateOrderPackage',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@@ -242,26 +106,15 @@ export const updateOrderPackage = (data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户端删除商品订单(逻辑删除)
|
/*
|
||||||
export const deleteProductOrder = (id) => {
|
* 弃用
|
||||||
|
*/
|
||||||
|
export const getPotionsDetail = (data) => {
|
||||||
|
console.log(data);
|
||||||
return request({
|
return request({
|
||||||
url: `/app/product/order/${id}`,
|
url: '/device/position/positionDetails',
|
||||||
method: 'delete'
|
method: 'get',
|
||||||
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户端取消商品订单支付
|
|
||||||
export const cancelProductOrder = (OutOrderNo) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/product/order/${OutOrderNo}/cancel`,
|
|
||||||
method: 'put'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解决丢包风扇未弹出,尝试重新弹出风扇
|
|
||||||
export const deviceRentByOrderNo = (orderNo)=>{
|
|
||||||
return request({
|
|
||||||
url:`/app/order/tryRent/${orderNo}`,
|
|
||||||
method:'post'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import request from '../http'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商品列表查询接口
|
|
||||||
* @param {Object} params - 查询参数
|
|
||||||
* @param {string} params.productName - 商品名称(可选,模糊查询)
|
|
||||||
* @param {number} params.pageNum - 页码(默认1)
|
|
||||||
* @param {number} params.pageSize - 每页数量(默认10)
|
|
||||||
* @returns {Promise} 分页的商品列表
|
|
||||||
*/
|
|
||||||
export const getProductList = ({ productName = '', pageNum = 1, pageSize = 10 }) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/product/list',
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
productName,
|
|
||||||
pageNum,
|
|
||||||
pageSize
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商品详情查询接口
|
|
||||||
* @param {string|number} id - 商品ID
|
|
||||||
* @returns {Promise} 商品详细信息,包含规格列表(skuList)
|
|
||||||
*/
|
|
||||||
export const getProductDetail = (id) => {
|
|
||||||
return request({
|
|
||||||
url: `/app/product/${id}`,
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建商品支付订单(多支付平台)
|
|
||||||
* 对应《商品购买多支付平台方案》:
|
|
||||||
* paymentPlatform: WECHAT / ALIPAY / ANTOM
|
|
||||||
* 其他字段见文档
|
|
||||||
*/
|
|
||||||
export const createProductOrder = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/product/pay',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商品订单退款
|
|
||||||
* @param {Object} data - 退款数据
|
|
||||||
* @param {number} data.productOrderId - 商品订单ID
|
|
||||||
* @param {number} data.refundAmount - 退款金额(可选,不传则全额退款)
|
|
||||||
* @param {string} data.refundReason - 退款原因(可选)
|
|
||||||
* @returns {Promise} 退款结果
|
|
||||||
*/
|
|
||||||
export const refundProductOrder = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/product/refund',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户收货地址
|
|
||||||
* @returns {Promise} 用户收货地址信息
|
|
||||||
*/
|
|
||||||
export const getUserAddress = () => {
|
|
||||||
return request({
|
|
||||||
url: '/app/product/address',
|
|
||||||
method: 'get'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import request from '../http'
|
import request from '../http'
|
||||||
|
|
||||||
// 获取系统配置
|
// 获取系统配置(预留接口)
|
||||||
// 可传参示例:{ configKey: 'overseas_payment_dana_total' }
|
// 期望后端返回形如:{ code: 200, data: { expressReturnCountdownSeconds: number } }
|
||||||
export const getSystemConfig = (data = {}) => {
|
export const getSystemConfig = () => {
|
||||||
return request({
|
return request({
|
||||||
url: '/system/config/list',
|
url: '/app/system/config',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
data,
|
|
||||||
hideLoading: true
|
hideLoading: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -28,38 +27,12 @@ export const getCommonByBrand = (brandName) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前协议内容
|
// 查询激活的且临近关闭时间最近的活动
|
||||||
export const getCurrentAgreement = (data) => {
|
export const getActiveActivity = () => {
|
||||||
return request({
|
return request({
|
||||||
url: '/device/agreementConfig/current',
|
url: '/device/activity/agent/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
data
|
hideLoading: true
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前广告内容
|
|
||||||
export const getCurrentAdvertisement = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/device/advertisementConfig/current',
|
|
||||||
method: 'get',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前公告内容
|
|
||||||
export const getCurrentAnnouncement = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/device/announcementConfig/current',
|
|
||||||
method: 'get',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getActiveActivity = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/device/activeActivity/current',
|
|
||||||
method: 'get',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import request from '../http'
|
import request from '../http'
|
||||||
import { URL, appid } from '../url'
|
import { URL, appid } from '../url'
|
||||||
|
|
||||||
// 旧登录接口(兼容保留,后端将逐步废弃)
|
// 用户登录
|
||||||
export const login = (data) => {
|
export const login = (data) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/app/user/login',
|
url: '/app/user/login',
|
||||||
@@ -10,42 +10,6 @@ export const login = (data) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一快捷登录接口 /app/user/quickLogin
|
|
||||||
// 对应文档《快捷登录最终方案》中的 QuickLoginDto:
|
|
||||||
// loginType: WECHAT / ALIPAY / SMS
|
|
||||||
// appid: 平台应用ID
|
|
||||||
// openId: 第三方 openId(微信必传)
|
|
||||||
// code: 授权码(微信手机号授权码 / 支付宝 authCode)
|
|
||||||
// phonenumber: 短信登录手机号
|
|
||||||
// smsCode: 短信验证码
|
|
||||||
export const quickLogin = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/user/quickLogin',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送验证码
|
|
||||||
export const sendVerifyCode = (phonenumber) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/user/sms/code',
|
|
||||||
method: 'get',
|
|
||||||
data: { phonenumber }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 手机号+验证码登录
|
|
||||||
export const loginWithCode = (phonenumber, smsCode) => {
|
|
||||||
// 兼容保留:统一走 quickLogin(SMS)
|
|
||||||
return quickLogin({
|
|
||||||
loginType: 'SMS',
|
|
||||||
appid,
|
|
||||||
phonenumber,
|
|
||||||
smsCode
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户退出登录
|
// 用户退出登录
|
||||||
export const userLogout = (data) => {
|
export const userLogout = (data) => {
|
||||||
return request({
|
return request({
|
||||||
@@ -92,8 +56,7 @@ export const uploadUserAvatar = (filePath) => {
|
|||||||
header: {
|
header: {
|
||||||
'appid': appid,
|
'appid': appid,
|
||||||
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
|
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
|
||||||
'Clientid': uni.getStorageSync('client_id'),
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
|
|
||||||
},
|
},
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
try {
|
try {
|
||||||
@@ -120,8 +83,7 @@ export const uploadOssResource = (filePath) => {
|
|||||||
header: {
|
header: {
|
||||||
'appid': appid,
|
'appid': appid,
|
||||||
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
|
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
|
||||||
'Clientid': uni.getStorageSync('client_id'),
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
|
|
||||||
},
|
},
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
try {
|
try {
|
||||||
@@ -153,22 +115,3 @@ export const withdrawDeposit = (orderNo) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取微信用户手机号
|
|
||||||
export const getWxUserPhoneNumber = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/user/getPhoneNumber',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取支付宝用户手机号(复用同一后端接口,由后端按 appid / 参数结构区分平台)
|
|
||||||
// 期望后端返回:{ code:200, data:{ phoneNumber: 'xxx' } }
|
|
||||||
export const getAliUserPhoneNumber = (data) => {
|
|
||||||
return request({
|
|
||||||
url: '/app/user/alipay/getPhone',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
/**
|
|
||||||
* Console 日志配置
|
|
||||||
* 用于控制是否在控制台打印日志
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 配置项:true 表示打印日志,false 表示不打印日志
|
|
||||||
export const CONSOLE_CONFIG = {
|
|
||||||
// 是否启用 console.log
|
|
||||||
enableLog: true,
|
|
||||||
// 是否启用 console.warn
|
|
||||||
enableWarn: false,
|
|
||||||
// 是否启用 console.error
|
|
||||||
enableError: false,
|
|
||||||
// 是否启用 console.info
|
|
||||||
enableInfo: false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存原始的 console 方法
|
|
||||||
const originalConsole = {
|
|
||||||
log: console.log,
|
|
||||||
warn: console.warn,
|
|
||||||
error: console.error,
|
|
||||||
info: console.info
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化 console 控制
|
|
||||||
* 根据配置决定是否打印日志
|
|
||||||
*/
|
|
||||||
export function initConsoleControl() {
|
|
||||||
// 重写 console.log
|
|
||||||
console.log = function(...args) {
|
|
||||||
if (CONSOLE_CONFIG.enableLog) {
|
|
||||||
originalConsole.log.apply(console, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重写 console.warn
|
|
||||||
console.warn = function(...args) {
|
|
||||||
if (CONSOLE_CONFIG.enableWarn) {
|
|
||||||
originalConsole.warn.apply(console, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重写 console.error
|
|
||||||
console.error = function(...args) {
|
|
||||||
if (CONSOLE_CONFIG.enableError) {
|
|
||||||
originalConsole.error.apply(console, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重写 console.info
|
|
||||||
console.info = function(...args) {
|
|
||||||
if (CONSOLE_CONFIG.enableInfo) {
|
|
||||||
originalConsole.info.apply(console, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 恢复原始的 console 方法
|
|
||||||
*/
|
|
||||||
export function restoreConsole() {
|
|
||||||
console.log = originalConsole.log
|
|
||||||
console.warn = originalConsole.warn
|
|
||||||
console.error = originalConsole.error
|
|
||||||
console.info = originalConsole.info
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 动态设置 console 配置
|
|
||||||
* @param {Object} config - 配置对象
|
|
||||||
*/
|
|
||||||
export function setConsoleConfig(config) {
|
|
||||||
Object.assign(CONSOLE_CONFIG, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,15 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
URL,
|
URL,
|
||||||
appid,
|
appid
|
||||||
ZFBappid
|
|
||||||
} from './url'
|
} from './url'
|
||||||
|
|
||||||
// 根据运行平台选择正确的小程序 appid(后端通常依赖该 header 做平台识别)
|
|
||||||
let platformAppid = appid
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
platformAppid = ZFBappid
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 获取多语言翻译文本
|
// 获取多语言翻译文本
|
||||||
const getLoadingText = () => {
|
const getLoadingText = () => {
|
||||||
try {
|
try {
|
||||||
@@ -36,14 +29,12 @@ const request = (option) => {
|
|||||||
method: option.method,
|
method: option.method,
|
||||||
data: option.data,
|
data: option.data,
|
||||||
header: {
|
header: {
|
||||||
"Content-Type": option.headers && option.headers["Content-Type"] ? option.headers[
|
"Content-Type": option.headers && option.headers["Content-Type"] ? option.headers["Content-Type"] : (option.method && option.method.toUpperCase() === 'POST' ? 'application/json' : 'application/x-www-form-urlencoded'),
|
||||||
"Content-Type"] : (option.method && option.method.toUpperCase() === 'POST' ?
|
...option.headers,
|
||||||
'application/json' : 'application/x-www-form-urlencoded'),
|
'appid': appid,
|
||||||
...option.headers,
|
'platform': 'alipay', // 标识支付宝小程序平台
|
||||||
'appid': platformAppid,
|
|
||||||
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
'Clientid': uni.getStorageSync('client_id'),
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
'Content-Language': (uni.getStorageSync('language') || 'zh-CN').replace(/-/g, '_')
|
|
||||||
},
|
},
|
||||||
success(res) {
|
success(res) {
|
||||||
|
|
||||||
@@ -58,9 +49,7 @@ const request = (option) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
reject({
|
reject({msg: `请求失败,状态码:${res.statusCode}`})
|
||||||
msg: `请求失败,状态码:${res.statusCode}`
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,22 +61,12 @@ const request = (option) => {
|
|||||||
// 计算重定向地址
|
// 计算重定向地址
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
const current = pages && pages.length ? pages[pages.length - 1] : null
|
const current = pages && pages.length ? pages[pages.length - 1] : null
|
||||||
const route = current && current.route ? ('/' + current.route) :
|
const route = current && current.route ? ('/' + current.route) : '/pages/index/index'
|
||||||
'/pages/index/index'
|
const query = current && current.options ? Object.keys(current.options).map(k => `${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
|
||||||
const query = current && current.options ? Object.keys(current.options).map(
|
|
||||||
k => `${k}=${encodeURIComponent(current.options[k])}`).join('&') :
|
|
||||||
''
|
|
||||||
const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
|
const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
|
||||||
// console.log(redirect, "===========");
|
|
||||||
// 跳转到登录页
|
// 跳转到登录页
|
||||||
uni.reLaunch({
|
uni.reLaunch({ url: `/pages/login/index?redirect=${redirect}` })
|
||||||
url: "/subPackages/user/login/index"
|
} catch (e) {}
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: "/subPackages/user/login/index"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查业务状态码
|
// 检查业务状态码
|
||||||
@@ -97,7 +76,7 @@ const request = (option) => {
|
|||||||
// 判断是否需要忽略数据为空的错误
|
// 判断是否需要忽略数据为空的错误
|
||||||
if (option.ignoreEmptyError &&
|
if (option.ignoreEmptyError &&
|
||||||
(res.data.code === 500 && res.data.msg &&
|
(res.data.code === 500 && res.data.msg &&
|
||||||
(res.data.msg.includes('未找到') || res.data.msg.includes('不存在')))) {
|
(res.data.msg.includes('未找到') || res.data.msg.includes('不存在')))) {
|
||||||
// 对于指定需要忽略的错误,返回一个标准的"成功但数据为空"的响应
|
// 对于指定需要忽略的错误,返回一个标准的"成功但数据为空"的响应
|
||||||
resolve({
|
resolve({
|
||||||
code: 200,
|
code: 200,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
|
// export const URL = "https://my.gxfs123.com/api" //正式服务器-弃用
|
||||||
// export const URL = "https://manager.fdzpower.com/api" //正式国内服务器
|
export const URL = "https://manager.fdzpower.com/api" //正式服务器
|
||||||
export const URL = "https://ina.fdzpower.com/api" //正式国外服务器
|
|
||||||
// export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
|
// export const URL = "https://fansdev.gxfs123.com/api" //测试服务器
|
||||||
// export const URL = "http://192.168.0.58:8080" //本地调试
|
// export const URL = "http://192.168.5.30:8080" //本地调试
|
||||||
// export const URL = "http://127.0.0.1:8080" //本地调试
|
// export const URL = "http://127.0.0.1:8080" //本地调试
|
||||||
|
|
||||||
export const appid = "wx2165f0be356ae7a9" //微信小程序appid
|
export const appid = "2021006117693332" //支付宝小程序appid
|
||||||
export const ZFBappid = "2021006117693332" //支付宝小程序appid
|
export const ZFBappid = "2021006117693332" //支付宝小程序appid(保留兼容)
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
# 支付宝支付接口文档
|
||||||
|
|
||||||
|
## 接口概述
|
||||||
|
本文档描述支付宝支付相关的API接口,包括创建支付订单和查询支付状态。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 创建支付宝支付订单
|
||||||
|
|
||||||
|
### 接口描述
|
||||||
|
创建支付宝支付订单,用于扫码预下单并返回二维码。
|
||||||
|
|
||||||
|
### 请求信息
|
||||||
|
|
||||||
|
#### 请求URL
|
||||||
|
```
|
||||||
|
GET /app/ali-payment/create/{orderNo}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 请求方式
|
||||||
|
`GET`
|
||||||
|
|
||||||
|
#### 请求参数
|
||||||
|
|
||||||
|
| 参数名 | 参数类型 | 是否必填 | 参数位置 | 参数说明 |
|
||||||
|
|--------|----------|----------|----------|----------|
|
||||||
|
| orderNo | String | 是 | Path | 订单号 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
```http
|
||||||
|
GET /app/ali-payment/create/ORD20231223001
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应信息
|
||||||
|
|
||||||
|
#### 响应参数
|
||||||
|
|
||||||
|
| 参数名 | 参数类型 | 参数说明 |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| code | Integer | 响应状态码,200表示成功 |
|
||||||
|
| msg | String | 响应消息 |
|
||||||
|
| data | Object | 返回数据,包含支付宝支付相关信息 |
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
成功响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"qrCode": "https://qr.alipay.com/xxx",
|
||||||
|
"outTradeNo": "ORD20231223001",
|
||||||
|
"totalAmount": "99.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"msg": "订单不存在或已支付"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误码说明
|
||||||
|
|
||||||
|
| 错误码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| 200 | 创建成功 |
|
||||||
|
| 400 | 参数错误,订单号不能为空 |
|
||||||
|
| 500 | 系统错误或订单状态异常 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 查询订单支付状态
|
||||||
|
|
||||||
|
### 接口描述
|
||||||
|
查询指定订单的支付状态。
|
||||||
|
|
||||||
|
### 请求信息
|
||||||
|
|
||||||
|
#### 请求URL
|
||||||
|
```
|
||||||
|
GET /app/ali-payment/status/{orderNo}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 请求方式
|
||||||
|
`GET`
|
||||||
|
|
||||||
|
#### 请求参数
|
||||||
|
|
||||||
|
| 参数名 | 参数类型 | 是否必填 | 参数位置 | 参数说明 |
|
||||||
|
|--------|----------|----------|----------|----------|
|
||||||
|
| orderNo | String | 是 | Path | 订单号 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
```http
|
||||||
|
GET /app/ali-payment/status/ORD20231223001
|
||||||
|
```
|
||||||
|
|
||||||
|
### 响应信息
|
||||||
|
|
||||||
|
#### 响应参数
|
||||||
|
|
||||||
|
| 参数名 | 参数类型 | 参数说明 |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| code | Integer | 响应状态码,200表示成功 |
|
||||||
|
| msg | String | 响应消息 |
|
||||||
|
| data | Object | 订单支付状态信息 |
|
||||||
|
| data.tradeStatus | String | 交易状态(WAIT_BUYER_PAY-等待支付,TRADE_SUCCESS-支付成功,TRADE_CLOSED-交易关闭) |
|
||||||
|
| data.tradeNo | String | 支付宝交易号 |
|
||||||
|
| data.totalAmount | String | 订单金额 |
|
||||||
|
| data.buyerPayAmount | String | 买家实付金额 |
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
支付成功响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"tradeStatus": "TRADE_SUCCESS",
|
||||||
|
"tradeNo": "2023122322001234567890",
|
||||||
|
"outTradeNo": "ORD20231223001",
|
||||||
|
"totalAmount": "99.00",
|
||||||
|
"buyerPayAmount": "99.00",
|
||||||
|
"buyerLogonId": "158****5620"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
等待支付响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"msg": "操作成功",
|
||||||
|
"data": {
|
||||||
|
"tradeStatus": "WAIT_BUYER_PAY",
|
||||||
|
"outTradeNo": "ORD20231223001",
|
||||||
|
"totalAmount": "99.00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 500,
|
||||||
|
"msg": "订单不存在"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 支付状态说明
|
||||||
|
|
||||||
|
| 状态码 | 说明 |
|
||||||
|
|--------|------|
|
||||||
|
| WAIT_BUYER_PAY | 交易创建,等待买家付款 |
|
||||||
|
| TRADE_CLOSED | 未付款交易超时关闭,或支付完成后全额退款 |
|
||||||
|
| TRADE_SUCCESS | 交易支付成功 |
|
||||||
|
| TRADE_FINISHED | 交易结束,不可退款 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 公共说明
|
||||||
|
|
||||||
|
### 基础路径
|
||||||
|
```
|
||||||
|
http://your-domain.com/app/ali-payment
|
||||||
|
```
|
||||||
|
|
||||||
|
### 请求头
|
||||||
|
```
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
|
||||||
|
1. **订单号格式**:订单号必须唯一,建议使用系统生成的订单编号
|
||||||
|
2. **幂等性**:同一订单号多次调用创建接口,返回相同的支付信息
|
||||||
|
3. **超时处理**:支付订单创建后,建议在15分钟内完成支付
|
||||||
|
4. **状态查询**:建议在支付完成后通过回调通知处理业务逻辑,状态查询接口用于补充查询
|
||||||
|
5. **安全性**:生产环境建议添加签名验证和请求频率限制
|
||||||
|
|
||||||
|
### 业务流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 用户发起租借 → 系统创建订单
|
||||||
|
2. 调用创建支付接口 → 返回支付二维码
|
||||||
|
3. 用户扫码支付 → 支付宝处理支付
|
||||||
|
4. 支付宝回调通知 → 系统更新订单状态
|
||||||
|
5. 前端轮询状态接口 → 确认支付结果
|
||||||
|
6. 支付成功 → 触发开锁逻辑
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题,请联系技术支持团队。
|
||||||
|
|
||||||
|
**文档版本**:v1.0
|
||||||
|
**最后更新**:2025-12-23
|
||||||
|
**维护人员**:开发团队
|
||||||
|
|
||||||
@@ -1,926 +0,0 @@
|
|||||||
export default {
|
|
||||||
common: {
|
|
||||||
confirm: 'Konfirmasi',
|
|
||||||
cancel: 'Batal',
|
|
||||||
and: 'dan',
|
|
||||||
submit: 'Kirim',
|
|
||||||
processing: 'Memproses',
|
|
||||||
submitting: 'Mengirim',
|
|
||||||
uploading: 'Mengunggah...',
|
|
||||||
getting: 'Mengambil...',
|
|
||||||
filling: 'Mengisi...',
|
|
||||||
save: 'Simpan',
|
|
||||||
loadFailed: 'Gagal memuat',
|
|
||||||
invalidUrl: 'Tautan tidak valid',
|
|
||||||
statusCode: 'Kode Status',
|
|
||||||
message: 'Pesan',
|
|
||||||
none: 'Tidak ada',
|
|
||||||
unexpectedError: 'Kesalahan tidak terduga',
|
|
||||||
processException: 'Terjadi pengecualian dalam proses',
|
|
||||||
errorInfo: 'Informasi Kesalahan',
|
|
||||||
edit: 'Edit',
|
|
||||||
delete: 'Hapus',
|
|
||||||
search: 'Cari',
|
|
||||||
loading: 'Memuat...',
|
|
||||||
loadingData: 'Mengambil data...',
|
|
||||||
loadingLocation: 'Mengambil informasi lokasi...',
|
|
||||||
loadingMap: 'Memuat peta...',
|
|
||||||
loadingPosition: 'Mengambil informasi lokasi...',
|
|
||||||
noData: 'Tidak ada data',
|
|
||||||
success: 'Berhasil',
|
|
||||||
failed: 'Gagal',
|
|
||||||
retry: 'Coba lagi',
|
|
||||||
back: 'Kembali',
|
|
||||||
next: 'Selanjutnya',
|
|
||||||
complete: 'Selesai',
|
|
||||||
more: 'Lebih banyak',
|
|
||||||
close: 'Tutup',
|
|
||||||
yes: 'Ya',
|
|
||||||
no: 'Tidak',
|
|
||||||
all: 'Semua',
|
|
||||||
tips: 'Tips',
|
|
||||||
notice: 'Pemberitahuan',
|
|
||||||
warning: 'Peringatan',
|
|
||||||
error: 'Kesalahan',
|
|
||||||
networkError: 'Kesalahan jaringan',
|
|
||||||
systemError: 'Kesalahan sistem',
|
|
||||||
authFailed: 'Autentikasi gagal',
|
|
||||||
unauthorized: 'Tidak diizinkan',
|
|
||||||
loginRequired: 'Harap login terlebih dahulu',
|
|
||||||
operationSuccess: 'Operasi berhasil',
|
|
||||||
operationFailed: 'Operasi gagal',
|
|
||||||
sending: 'Mengirim...',
|
|
||||||
loggingIn: 'Masuk...',
|
|
||||||
refresh: 'Muat ulang',
|
|
||||||
pull: 'Tarik untuk muat ulang',
|
|
||||||
release: 'Lepas untuk muat ulang',
|
|
||||||
noMore: 'Tidak ada lagi',
|
|
||||||
functionDeveloping: 'Fungsi sedang dikembangkan',
|
|
||||||
saveImage: 'Simpan ke Ponsel',
|
|
||||||
saveSuccess: 'Berhasil disimpan',
|
|
||||||
saving: 'Menyimpan...',
|
|
||||||
saveFailed: 'Gagal menyimpan',
|
|
||||||
downloadFailed: 'Gagal mengunduh',
|
|
||||||
reset: 'Atur ulang',
|
|
||||||
preview: 'Pratinjau'
|
|
||||||
},
|
|
||||||
|
|
||||||
nav: {
|
|
||||||
home: 'Beranda',
|
|
||||||
my: 'Saya',
|
|
||||||
orders: 'Pesanan',
|
|
||||||
settings: 'Pengaturan',
|
|
||||||
back: 'Kembali',
|
|
||||||
title: 'Kipas Angin & Power Bank Berbagi Isidaya'
|
|
||||||
},
|
|
||||||
|
|
||||||
app: {
|
|
||||||
name: 'Isidaya',
|
|
||||||
slogan: 'Kipas Angin & Power Bank Berbagi',
|
|
||||||
fullName: 'Isidaya - Kipas Angin & Power Bank Berbagi',
|
|
||||||
welcome: 'Selamat datang menggunakan Isidaya'
|
|
||||||
},
|
|
||||||
|
|
||||||
home: {
|
|
||||||
title: 'Kipas Angin & Power Bank Berbagi Isidaya',
|
|
||||||
nearbyDevices: 'Perangkat Terdekat',
|
|
||||||
scanToUse: 'Pindai untuk Menggunakan',
|
|
||||||
personalCenter: 'Pusat Pribadi',
|
|
||||||
useGuide: 'Panduan Penggunaan',
|
|
||||||
buyDevice: 'Kustomisasi Power Bank',
|
|
||||||
navigate: 'Navigasi',
|
|
||||||
relocate: 'Lokasi Ulang',
|
|
||||||
search: 'Cari',
|
|
||||||
service: 'Layanan Pelanggan',
|
|
||||||
h5ScanDesc: 'Pindai kode QR perangkat untuk mulai menggunakan',
|
|
||||||
h5HowItWorks: 'Cara Penggunaan',
|
|
||||||
h5StepScan: 'Pindai',
|
|
||||||
h5StepUnlock: 'Perangkat Keluar',
|
|
||||||
h5StepReturn: 'Kembalikan',
|
|
||||||
h5ServiceTitle: 'Layanan',
|
|
||||||
h5ServiceSupport: 'Dukungan 24/7',
|
|
||||||
h5ServiceSafePayment: 'Pembayaran Aman',
|
|
||||||
h5ServiceEasyReturn: 'Pengembalian Mudah',
|
|
||||||
searchPlaceholder: 'Cari lokasi terdekat',
|
|
||||||
nearbyDeviceLocation: 'Lokasi Perangkat Terdekat',
|
|
||||||
noNearbyDevice: 'Tidak ada perangkat terdekat',
|
|
||||||
relocating: 'Melokalisasi ulang...',
|
|
||||||
locateSuccess: 'Lokasi berhasil',
|
|
||||||
locateFailed: 'Lokasi gagal, harap periksa izin lokasi',
|
|
||||||
invalidQRCode: 'Kode QR perangkat tidak valid',
|
|
||||||
scanFailed: 'Pemindaian gagal',
|
|
||||||
noticeTitle: 'Pemberitahuan',
|
|
||||||
getLocationFailed: 'Gagal mendapatkan lokasi, menampilkan peta default',
|
|
||||||
locationPermissionOffTip: 'Izin lokasi belum diaktifkan. Sementara tidak dapat mencari perangkat terdekat. Jika ingin mencari perangkat terdekat, klik tombol di bawah untuk mengaktifkan lokasi.',
|
|
||||||
enableLocation: 'Aktifkan Lokasi'
|
|
||||||
},
|
|
||||||
|
|
||||||
scan: {
|
|
||||||
title: 'Pindai untuk Menggunakan',
|
|
||||||
album: 'Album',
|
|
||||||
manualInput: 'Input Manual',
|
|
||||||
manualInputTitle: 'Masukkan Nomor Perangkat',
|
|
||||||
deviceNoPlaceholder: 'Masukkan nomor pada perangkat',
|
|
||||||
initializing: 'Menginisialisasi...',
|
|
||||||
startingCamera: 'Menghidupkan kamera...',
|
|
||||||
alignQRCode: 'Letakkan kode QR dalam bingkai',
|
|
||||||
initFailed: 'Inisialisasi gagal',
|
|
||||||
browserNotSupportCamera: 'Browser Anda tidak mendukung akses kamera',
|
|
||||||
containerNotFound: 'Kontainer pemindai tidak ditemukan',
|
|
||||||
noCameraFound: 'Kamera tidak ditemukan',
|
|
||||||
ensureCameraExists: 'Pastikan perangkat Anda memiliki kamera',
|
|
||||||
cameraPermissionDenied: 'Izin kamera ditolak',
|
|
||||||
cameraPermissionHint: 'Harap izinkan akses kamera di pengaturan browser',
|
|
||||||
cameraInUse: 'Kamera sedang digunakan',
|
|
||||||
closeOtherCameraApps: 'Harap tutup aplikasi lain yang menggunakan kamera',
|
|
||||||
browserNotSupported: 'Browser tidak didukung',
|
|
||||||
useModernBrowser: 'Harap gunakan browser modern',
|
|
||||||
cameraStartFailed: 'Gagal menghidupkan kamera',
|
|
||||||
tryRefreshOrAlternative: 'Coba segarkan halaman atau gunakan cara lain',
|
|
||||||
errorFallbackHint: 'Anda dapat:',
|
|
||||||
errorFallbackAlbum: 'Pilih gambar kode QR dari album',
|
|
||||||
errorFallbackManual: 'Masukkan nomor perangkat secara manual',
|
|
||||||
recognizing: 'Mengenali...',
|
|
||||||
qrNotFound: 'Kode QR tidak terdeteksi',
|
|
||||||
recognizeFailed: 'Pengenalan gagal',
|
|
||||||
h5Only: 'Fitur ini hanya tersedia di H5',
|
|
||||||
deviceNoRequired: 'Harap masukkan nomor perangkat'
|
|
||||||
},
|
|
||||||
|
|
||||||
guide: {
|
|
||||||
title: 'Panduan Penggunaan',
|
|
||||||
step1Title: 'Pindai untuk Menggunakan',
|
|
||||||
step1Desc: 'Temukan perangkat terdekat, pindai kode QR pada perangkat',
|
|
||||||
step2Title: 'Pembayaran Tanpa Deposit',
|
|
||||||
step2Desc: 'Tidak perlu membayar deposit, gunakan skor pembayaran tanpa deposit untuk menyelesaikan penyewaan',
|
|
||||||
step3Title: 'Mulai Menggunakan',
|
|
||||||
step3Desc: 'Perangkat akan terbuka secara otomatis, ambil kipas angin setelah muncul dan mulai gunakan',
|
|
||||||
step4Title: 'Kembalikan Perangkat',
|
|
||||||
step4Desc: 'Setelah selesai digunakan, kembalikan kipas angin sesuai spesifikasi perangkat untuk mengakhiri pesanan'
|
|
||||||
},
|
|
||||||
|
|
||||||
location: {
|
|
||||||
rent: 'Dapat Disewa',
|
|
||||||
return: 'Dapat Dikembalikan',
|
|
||||||
navigate: 'Navigasi',
|
|
||||||
distance: 'Jarak',
|
|
||||||
businessHours: 'Jam Operasional:',
|
|
||||||
navigateHere: 'Navigasi ke Sini',
|
|
||||||
coordinateError: 'Informasi koordinat lokasi ini abnormal',
|
|
||||||
notExist: 'Lokasi tidak ada',
|
|
||||||
supportCouponOrMember: 'Dapat menggunakan kupon, kartu anggota'
|
|
||||||
},
|
|
||||||
|
|
||||||
device: {
|
|
||||||
reportError: 'Laporkan Kesalahan Perangkat',
|
|
||||||
scanToUse: 'Pindai untuk Menggunakan',
|
|
||||||
deviceInfo: 'Informasi Perangkat',
|
|
||||||
deviceNo: 'Nomor Perangkat',
|
|
||||||
deviceName: 'Nama perangkat',
|
|
||||||
location: 'Lokasi',
|
|
||||||
businessHours: 'Jam Operasional',
|
|
||||||
pricing: 'Penagihan',
|
|
||||||
pricingText: 'Rp5/jam, Rp36/24 jam, total ¥899',
|
|
||||||
getDeviceInfoFailed: 'Gagal mendapatkan informasi perangkat',
|
|
||||||
available: 'Tersedia',
|
|
||||||
offline: 'Offline',
|
|
||||||
pricingRules: 'Aturan Penagihan',
|
|
||||||
capLimit: 'Maksimum',
|
|
||||||
detailBillingByUnit: 'Kurang dari {unit} {minute} ditagih sebagai {unit} {minute}. Maksimum ¥{cap}. Penagihan hingga ¥{cap} dianggap sebagai pembelian.',
|
|
||||||
detailBillingIdr: 'Kurang dari 1 {hour} ditagih sebagai 1 {hour}. Maksimum {cap}. Penagihan hingga {cap} dianggap sebagai pembelian.',
|
|
||||||
usageInstructions: 'Instruksi Penggunaan',
|
|
||||||
checkBeforeUse: 'Harap periksa apakah perangkat dalam kondisi baik sebelum digunakan',
|
|
||||||
autoChargeOvertime: 'Melebihi waktu penggunaan akan dikenakan biaya per jam secara otomatis',
|
|
||||||
useInDesignatedArea: 'Harap gunakan perangkat di area yang ditentukan',
|
|
||||||
rentDepositFree: 'Sewa Tanpa Deposit',
|
|
||||||
rentNow: 'Sewa Sekarang',
|
|
||||||
wxPayScoreDesc: 'Skor Pembayaran WeChat | Nikmati dengan 550 poin atau lebih',
|
|
||||||
checking: 'Memeriksa',
|
|
||||||
deviceNoNotRecognized: 'Nomor perangkat tidak dikenali',
|
|
||||||
processFailed: 'Pemrosesan gagal, harap coba lagi nanti',
|
|
||||||
sharedFan: 'Kipas Angin Berbagi',
|
|
||||||
deviceNoRequired: 'Nomor perangkat tidak boleh kosong',
|
|
||||||
rentFailed: 'Penyewaan perangkat gagal',
|
|
||||||
rentSuccess: 'Penyewaan berhasil',
|
|
||||||
rentFailedRetry: 'Penyewaan gagal, harap coba lagi',
|
|
||||||
getPayParamsFailed: 'Gagal mendapatkan parameter pembayaran',
|
|
||||||
payScoreFailedCancelled: 'Panggilan skor pembayaran gagal, pesanan telah dibatalkan',
|
|
||||||
canUsePromotion: 'Tips: Dapat menggunakan kupon, kartu anggota',
|
|
||||||
goToBuy: 'Beli Sekarang'
|
|
||||||
},
|
|
||||||
|
|
||||||
order: {
|
|
||||||
myOrders: 'Pesanan Saya',
|
|
||||||
myDeviceOrders: 'Kustomisasi Saya',
|
|
||||||
noOrderRecord: 'Tidak ada catatan pesanan',
|
|
||||||
getOrderListFailed: 'Gagal mendapatkan daftar pesanan',
|
|
||||||
confirmCancelContent: 'Apakah Anda yakin ingin membatalkan pesanan ini?',
|
|
||||||
confirmDeleteContent: 'Apakah Anda yakin ingin menghapus pesanan ini?',
|
|
||||||
orderDetail: 'Detail Pesanan',
|
|
||||||
orderNo: 'Nomor Pesanan',
|
|
||||||
orderStatus: 'Status Pesanan',
|
|
||||||
deviceNo: 'Nomor Perangkat',
|
|
||||||
rentLocation: 'Lokasi Penyewaan',
|
|
||||||
rentTime: 'Waktu Penyewaan',
|
|
||||||
returnTime: 'Waktu Pengembalian',
|
|
||||||
startTime: 'Waktu Mulai',
|
|
||||||
endTime: 'Waktu Berakhir',
|
|
||||||
duration: 'Durasi Penggunaan',
|
|
||||||
amount: 'Jumlah',
|
|
||||||
totalAmount: 'Jumlah Total',
|
|
||||||
payAmount: 'Jumlah Pembayaran',
|
|
||||||
deposit: 'Deposit',
|
|
||||||
rentFee: 'Biaya Sewa',
|
|
||||||
myCards: 'Diskon Kartu Anggota',
|
|
||||||
myCoupons: 'Diskon Kupon',
|
|
||||||
payNow: 'Bayar Sekarang',
|
|
||||||
cancelOrder: 'Batalkan Pesanan',
|
|
||||||
quickReturn: 'Pengembalian Cepat',
|
|
||||||
returnDevice: 'Kembalikan Perangkat',
|
|
||||||
viewDetails: 'Lihat Detail',
|
|
||||||
orderCompleted: 'Pesanan Selesai',
|
|
||||||
orderCancelled: 'Pesanan Dibatalkan',
|
|
||||||
waitingForPayment: 'Menunggu Pembayaran',
|
|
||||||
inUse: 'Sedang Digunakan',
|
|
||||||
finished: 'Selesai',
|
|
||||||
cancelled: 'Dibatalkan',
|
|
||||||
renting: 'Menyewa',
|
|
||||||
rentFan: 'Sewa Kipas Angin',
|
|
||||||
noOrder: 'Tidak ada pesanan yang sedang digunakan',
|
|
||||||
getOrderFailed: 'Gagal mendapatkan pesanan',
|
|
||||||
paymentSuccess: 'Pembayaran berhasil',
|
|
||||||
paymentFailed: 'Pembayaran gagal',
|
|
||||||
cancelSuccess: 'Pembatalan berhasil',
|
|
||||||
cancelFailed: 'Pembatalan gagal',
|
|
||||||
returnSuccess: 'Pengembalian berhasil',
|
|
||||||
returnFailed: 'Pengembalian gagal',
|
|
||||||
confirmCancel: 'Konfirmasi membatalkan pesanan?',
|
|
||||||
confirmReturn: 'Konfirmasi mengembalikan perangkat?',
|
|
||||||
wxPayScore: 'Skor Pembayaran WeChat',
|
|
||||||
depositFree: 'Sewa Tanpa Deposit',
|
|
||||||
whitelistOrder: 'Pesanan Whitelist',
|
|
||||||
memberOrder: 'Pesanan Anggota',
|
|
||||||
aliPay: 'Alipay',
|
|
||||||
antomPay: 'Antom Pay',
|
|
||||||
wxPay: 'Pembayaran WeChat',
|
|
||||||
depositPay: 'Sewa dengan Deposit',
|
|
||||||
paymentInProgress: 'Sedang membayar',
|
|
||||||
paymentFailedRetry: 'Pembayaran gagal, harap bayar lagi',
|
|
||||||
pleasePaySoon: 'Harap selesaikan pembayaran segera',
|
|
||||||
pleaseReturnInTime: 'Harap simpan perangkat dengan baik dan kembalikan tepat waktu setelah digunakan',
|
|
||||||
returnedThankYou: 'Kipas angin Anda telah dikembalikan, terima kasih telah menggunakan',
|
|
||||||
used: 'Digunakan',
|
|
||||||
rentInfo: 'Informasi Penyewaan',
|
|
||||||
rentInfoExpand: 'Buka',
|
|
||||||
rentInfoCollapse: 'Tutup',
|
|
||||||
fanNo: 'Nomor Kipas Angin',
|
|
||||||
rentMethod: 'Metode Penyewaan',
|
|
||||||
returnLocation: 'Lokasi Pengembalian',
|
|
||||||
paid: 'Dibayar',
|
|
||||||
canExpressReturn: 'Dapat dikembalikan melalui ekspres',
|
|
||||||
pauseBilling: 'Jeda Penagihan',
|
|
||||||
pauseBillingSuccess: 'Penagihan dijeda',
|
|
||||||
pauseBillingFailed: 'Gagal menjeda penagihan, coba lagi.',
|
|
||||||
pauseBillingNotEligible: 'Pesanan ini belum memenuhi syarat jeda penagihan.',
|
|
||||||
billingPausedBadge: 'Penagihan dijeda',
|
|
||||||
orderStatusBillingPaused: 'Penagihan dijeda',
|
|
||||||
billingPausedDurationLabel: 'Dijeda selama',
|
|
||||||
pausedExpressAvailable: 'Pengembalian ekspres dapat diajukan',
|
|
||||||
rentAgain: 'Sewa Lagi',
|
|
||||||
backToHome: 'Kembali ke Beranda',
|
|
||||||
feeAppeal: 'Banding Biaya',
|
|
||||||
orderIdRequired: 'ID Pesanan tidak boleh kosong',
|
|
||||||
refundSuccess: 'Permohonan pengembalian dana berhasil',
|
|
||||||
refundFailed: 'Permohonan pengembalian dana gagal',
|
|
||||||
orderNotExist: 'Informasi pesanan tidak ada',
|
|
||||||
currentFee: 'Biaya Saat Ini',
|
|
||||||
returnInstructions: 'Instruksi Pengembalian',
|
|
||||||
ensureDeviceIntact: 'Harap pastikan perangkat dalam kondisi baik',
|
|
||||||
insertFanBack: 'Masukkan kipas angin ke posisi asli atau soket kosong',
|
|
||||||
autoDetectReturn: 'Sistem akan secara otomatis mendeteksi pengembalian dan memproses pengembalian dana',
|
|
||||||
autoJumpAfterReturn: 'Setelah pengembalian berhasil akan otomatis melompat ke halaman sukses',
|
|
||||||
refreshStatus: 'Muat Ulang Status',
|
|
||||||
countdown: 'Hitungan Mundur',
|
|
||||||
pauseAndExpress: 'Jeda penagihan, kembalikan melalui ekspres',
|
|
||||||
orderInfoMissing: 'Informasi pesanan kurang',
|
|
||||||
returnSuccessMessage: 'Kipas angin telah dikembalikan dengan sukses, sisa deposit akan dikembalikan ke akun Anda',
|
|
||||||
noOrderInUse: 'Tidak ditemukan pesanan yang sedang digunakan',
|
|
||||||
pleaseRefreshManually: 'Harap muat ulang secara manual untuk melihat status pengembalian',
|
|
||||||
cancelling: 'Membatalkan pesanan',
|
|
||||||
cancelFailedContactService: 'Pembatalan pesanan gagal, harap hubungi layanan pelanggan',
|
|
||||||
getOrderStatusFailed: 'Pemeriksaan status pesanan gagal',
|
|
||||||
syncSuccess: 'Sinkronisasi status berhasil',
|
|
||||||
syncFailed: 'Sinkronisasi status gagal',
|
|
||||||
freeRentTime: 'Waktu Gratis',
|
|
||||||
pricingRule: 'Aturan Penagihan',
|
|
||||||
paymentMethod: 'Metode Pembayaran',
|
|
||||||
perHour: 'Per Jam',
|
|
||||||
perMinute: 'Per Menit',
|
|
||||||
perHalfHour: 'Per Setengah Jam',
|
|
||||||
deviceNoEject: 'Power Bank Tidak Muncul',
|
|
||||||
returnReminder: 'Pengingat Pengembalian',
|
|
||||||
canUsePromotion: 'Dapat menggunakan kupon, kartu anggota',
|
|
||||||
usedPromotion: 'Jenis Diskon',
|
|
||||||
convertToOwn: 'Tidak ingin mengembalikan? Klik untuk mengubah menjadi milik sendiri',
|
|
||||||
convertToOwnTitle: 'Ubah menjadi Milik Sendiri',
|
|
||||||
convertToOwnConfirm: 'Hanya perlu membayar ¥99, dapat diubah menjadi milik sendiri, power bank akan menjadi milik Anda, konfirmasi operasi?',
|
|
||||||
convertToOwnSuccess: 'Berhasil diubah menjadi milik sendiri',
|
|
||||||
convertToOwnFailed: 'Operasi gagal, harap coba lagi nanti',
|
|
||||||
convertToOwnConfirmBtn: 'Beli untuk Milik Sendiri',
|
|
||||||
convertToOwnCancelBtn: 'Lanjutkan Menyewa',
|
|
||||||
convertToOwnWithMaxFee: 'Tidak ingin mengembalikan? Ubah menjadi milik sendiri',
|
|
||||||
convertToOwnWithMaxFeeTitle: 'Beli dan Bawa Pulang!',
|
|
||||||
convertToOwnWithMaxFeeConfirm: 'Karena sudah nyaman digunakan, langsung beli dan bawa pulang! Hanya ¥99, perangkat selamanya milik Anda, tidak perlu dikembalikan~\n✅Mendukung pengisian Type-C, sangat nyaman digunakan di rumah~\n✅Setelah dibeli, tidak ada batasan penggunaan, gunakan sesuka hati!',
|
|
||||||
convertToOwnWithMaxFeeSuccess: 'Pembelian berhasil',
|
|
||||||
convertToOwnWithMaxFeeFailed: 'Pembelian gagal, harap coba lagi nanti',
|
|
||||||
deviceNoEjectTitle: 'Power Bank Tidak Muncul',
|
|
||||||
deviceNoEjectConfirm: 'Apakah power bank Anda tidak muncul? Kami akan segera menanganinya untuk Anda, diperkirakan akan diselesaikan dalam 5 menit.',
|
|
||||||
deviceNoEjectSuccess: 'Umpan balik telah diterima, akan diproses dalam 5 menit',
|
|
||||||
deviceNoEjectFailed: 'Pengiriman umpan balik gagal, harap coba lagi nanti',
|
|
||||||
returnProblemTip: 'Setelah produk dikembalikan ke gudang, pesanan masih belum berakhir, harap pergi ke',
|
|
||||||
contactStaff: 'Hubungi staf.',
|
|
||||||
returnLocationMap: 'Peta Lokasi Pengembalian',
|
|
||||||
deviceEjectWait: 'Harap tunggu ',
|
|
||||||
deviceEjectRetry: ' detik sebelum mencoba lagi',
|
|
||||||
deviceEjectProcessing: 'Memproses...',
|
|
||||||
deviceEjectSuccess: 'Percobaan pengeluaran perangkat dimulai',
|
|
||||||
deviceEjectFailed: 'Operasi gagal',
|
|
||||||
deviceEjectError: 'Operasi gagal, harap coba lagi nanti',
|
|
||||||
},
|
|
||||||
|
|
||||||
user: {
|
|
||||||
clickToLogin: 'Klik untuk Login',
|
|
||||||
loginPrompt: 'Setelah otorisasi login, Anda dapat melihat pesanan dan aset',
|
|
||||||
personalCenter: 'Pusat Pribadi',
|
|
||||||
depositBalance: 'Saldo Deposit',
|
|
||||||
withdraw: 'Tarik',
|
|
||||||
commonServices: 'Layanan Umum',
|
|
||||||
quickReturn: 'Pengembalian Cepat',
|
|
||||||
quickReturnDesc: '(Langsung lihat pesanan yang sedang digunakan)',
|
|
||||||
expressReturn: 'Catatan Pengembalian Ekspres',
|
|
||||||
myOrders: 'Pesanan Saya',
|
|
||||||
myCards: 'Kartu Anggota Saya',
|
|
||||||
myCoupons: 'Kupon Saya',
|
|
||||||
customerService: 'Pusat Layanan Pelanggan',
|
|
||||||
feedback: 'Keluhan dan Saran',
|
|
||||||
businessLicense: 'Lisensi Bisnis',
|
|
||||||
cooperation: 'Kerja Sama dan Keanggotaan',
|
|
||||||
settings: 'Pengaturan',
|
|
||||||
userAgreement: '《Perjanjian Pengguna》',
|
|
||||||
settinguserAgreement:'Perjanjian Pengguna',
|
|
||||||
settinguserprivacyPolicy:'Kebijakan Privasi',
|
|
||||||
privacyPolicy: '《Kebijakan Privasi》',
|
|
||||||
version: 'v',
|
|
||||||
logout: 'Keluar',
|
|
||||||
confirmLogout: 'Konfirmasi keluar?',
|
|
||||||
logoutSuccess: 'Keluar berhasil',
|
|
||||||
getUserInfoFailed: 'Gagal mendapatkan informasi pengguna',
|
|
||||||
updateSuccess: 'Informasi berhasil diperbarui',
|
|
||||||
updateFailed: 'Gagal memperbarui informasi pengguna',
|
|
||||||
avatarUpdated: 'Avatar telah diperbarui',
|
|
||||||
avatarUploadFailed: 'Gagal memperbarui avatar',
|
|
||||||
noAvatar: 'Avatar tidak dipilih',
|
|
||||||
noAvatarUrl: 'Alamat avatar tidak diperoleh',
|
|
||||||
avatarDownloadFailed: 'Gagal mengunduh avatar',
|
|
||||||
notLoggedIn: 'Tidak login',
|
|
||||||
phoneNotBound: 'Nomor telepon tidak terikat',
|
|
||||||
balanceDesc: 'Dapat digunakan untuk menyewa perangkat',
|
|
||||||
feedbackRecord: 'Catatan Keluhan'
|
|
||||||
},
|
|
||||||
|
|
||||||
auth: {
|
|
||||||
authTitle: 'Otorisasi Mendapatkan Nomor Telepon',
|
|
||||||
authDesc: 'Untuk memberikan layanan yang lebih baik dan kontak darurat, perlu otorisasi untuk mendapatkan nomor telepon Anda',
|
|
||||||
getPhoneNumber: 'Login Cepat Nomor Telepon',
|
|
||||||
notNow: 'Tidak Sekarang',
|
|
||||||
authRequired: 'Perlu Otorisasi',
|
|
||||||
authSuccess: 'Otorisasi berhasil',
|
|
||||||
authFailed: 'Otorisasi gagal',
|
|
||||||
loginTitle: 'Login',
|
|
||||||
loginDesc: 'Untuk memastikan pengalaman penggunaan, harap selesaikan login terlebih dahulu',
|
|
||||||
getUserInfoSuccess: 'Berhasil mendapatkan informasi pengguna',
|
|
||||||
getUserInfoFailed: 'Gagal mendapatkan informasi pengguna',
|
|
||||||
pleaseUseInWechat: 'Harap gunakan fungsi ini di WeChat Mini Program',
|
|
||||||
agreeToTerms: 'Saya telah membaca dan menyetujui',
|
|
||||||
pleaseAgreeToTerms: 'Harap baca dan setujui 《Perjanjian Pengguna》 dan 《Kebijakan Privasi》 terlebih dahulu',
|
|
||||||
loginSuccess: 'Login berhasil',
|
|
||||||
loginFailed: 'Login gagal',
|
|
||||||
phoneCancelled: 'Otorisasi nomor telepon dibatalkan',
|
|
||||||
goToLogin: 'Pergi ke Login',
|
|
||||||
authDescShort: 'Untuk memberikan layanan yang lebih baik, perlu otorisasi untuk mendapatkan nomor telepon Anda',
|
|
||||||
phoneRequired: 'Perlu otorisasi nomor telepon untuk menggunakan perangkat',
|
|
||||||
getting: 'Mengambil...',
|
|
||||||
phoneSuccess: 'Berhasil mendapatkan nomor telepon',
|
|
||||||
phoneError: 'Kesalahan mendapatkan nomor telepon',
|
|
||||||
phoneGetFailed: 'Gagal mendapatkan nomor telepon',
|
|
||||||
authCodeFailed: 'Gagal mendapatkan kode otorisasi',
|
|
||||||
phoneLogin: 'Login Nomor Telepon',
|
|
||||||
phonePlaceholder: 'Harap masukkan nomor telepon',
|
|
||||||
codePlaceholder: 'Harap masukkan kode verifikasi',
|
|
||||||
getCode: 'Dapatkan Kode Verifikasi',
|
|
||||||
resend: 'Kirim Ulang',
|
|
||||||
loginBtn: 'Login Sekarang',
|
|
||||||
phoneRequired: 'Harap masukkan nomor telepon',
|
|
||||||
phoneInvalid: 'Harap masukkan nomor telepon yang benar',
|
|
||||||
codeRequired: 'Harap masukkan kode verifikasi',
|
|
||||||
codeSent: 'Kode verifikasi telah dikirim',
|
|
||||||
sendCodeFailed: 'Gagal mengirim kode verifikasi',
|
|
||||||
regionNotSupported: 'Pengguna di luar Tiongkok Daratan, Hong Kong, dan Makau harap login melalui otorisasi nomor telepon platform',
|
|
||||||
onlyMainlandSupported: 'Saat ini hanya mendukung wilayah Tiongkok Daratan',
|
|
||||||
getServicePhoneFailed: 'Gagal mendapatkan nomor telepon layanan pelanggan',
|
|
||||||
noAuthToken: 'Login berhasil tetapi tidak mendapatkan token otorisasi'
|
|
||||||
},
|
|
||||||
|
|
||||||
permission: {
|
|
||||||
locationTitle: 'Otorisasi Informasi Lokasi',
|
|
||||||
locationNeed: 'Perlu mendapatkan informasi lokasi Anda untuk menampilkan perangkat terdekat, harap aktifkan izin lokasi di "Pengaturan-Manajemen Izin".',
|
|
||||||
locationDenied: 'Lokasi tidak diotorisasi, tidak dapat menampilkan perangkat terdekat. Anda dapat mengaktifkan kembali izin lokasi di "Pengaturan-Manajemen Izin" nanti.',
|
|
||||||
goToSettings: 'Pergi ke Pengaturan',
|
|
||||||
later: 'Tidak Sekarang',
|
|
||||||
gotIt: 'Mengerti'
|
|
||||||
},
|
|
||||||
|
|
||||||
payment: {
|
|
||||||
paymentAmount: 'Jumlah Pembayaran',
|
|
||||||
paymentMethod: 'Metode Pembayaran',
|
|
||||||
wechatPay: 'Pembayaran WeChat',
|
|
||||||
alipay: 'Alipay',
|
|
||||||
alipayHk: 'Alipay Hong Kong',
|
|
||||||
alipayId: 'Alipay Indonesia',
|
|
||||||
ALIPAYDANA: 'Alipay DANA',
|
|
||||||
balance: 'Pembayaran Saldo',
|
|
||||||
payNow: 'Bayar Sekarang',
|
|
||||||
paying: 'Sedang membayar...',
|
|
||||||
paymentSuccess: 'Pembayaran berhasil',
|
|
||||||
paymentFailed: 'Pembayaran gagal',
|
|
||||||
paymentCancelled: 'Pembayaran dibatalkan',
|
|
||||||
orderPayment: 'Pembayaran Pesanan',
|
|
||||||
waitingForPayment: 'Menunggu Pembayaran',
|
|
||||||
pleasePayIn15Min: 'Harap selesaikan pembayaran dalam 15 menit',
|
|
||||||
orderInfo: 'Informasi Pesanan',
|
|
||||||
createTime: 'Waktu Pembuatan',
|
|
||||||
contactPhone: 'Nomor Telepon Kontak',
|
|
||||||
feeInfo: 'Informasi Biaya',
|
|
||||||
deposit: 'Deposit',
|
|
||||||
package: 'Paket',
|
|
||||||
total: 'Total',
|
|
||||||
paymentFailedRetry: 'Pembayaran gagal, harap coba lagi',
|
|
||||||
createPayOrderFailed: 'Gagal membuat pesanan pembayaran',
|
|
||||||
subscriptionSuccess: 'Berlangganan berhasil',
|
|
||||||
subscriptionFailed: 'Berlangganan gagal, harap coba lagi nanti'
|
|
||||||
},
|
|
||||||
|
|
||||||
feedback: {
|
|
||||||
uploading: 'Mengunggah...',
|
|
||||||
title: 'Keluhan dan Saran',
|
|
||||||
placeholder: 'Harap jelaskan secara detail masalah yang Anda hadapi, agar kami dapat menyelesaikannya dengan lebih baik',
|
|
||||||
submit: 'Kirim Umpan Balik',
|
|
||||||
submitSuccess: 'Umpan balik berhasil',
|
|
||||||
submitFailed: 'Umpan balik gagal',
|
|
||||||
contentRequired: 'Harap masukkan konten',
|
|
||||||
issueType: 'Jenis Masalah',
|
|
||||||
issueDescription: 'Deskripsi Masalah',
|
|
||||||
imageUpload: 'Unggah Gambar (Opsional)',
|
|
||||||
uploadImage: 'Unggah Gambar',
|
|
||||||
contactInfo: 'Informasi Kontak',
|
|
||||||
contactPlaceholder: 'Harap tinggalkan nomor telepon Anda, agar kami dapat menghubungi Anda',
|
|
||||||
pleaseSelectType: 'Harap pilih jenis masalah',
|
|
||||||
pleaseDescribe: 'Harap jelaskan masalah Anda',
|
|
||||||
pleaseContact: 'Harap tinggalkan informasi kontak',
|
|
||||||
imageUploadFailed: 'Gagal mengunggah gambar, harap coba lagi',
|
|
||||||
deviceFault: 'Kesalahan Perangkat',
|
|
||||||
chargingIssue: 'Masalah Biaya',
|
|
||||||
usageSuggestion: 'Saran Penggunaan',
|
|
||||||
other: 'Lainnya',
|
|
||||||
recordList: 'Catatan Keluhan',
|
|
||||||
detail: 'Detail Keluhan',
|
|
||||||
noRecord: 'Tidak ada catatan keluhan',
|
|
||||||
getListFailed: 'Gagal mendapatkan daftar',
|
|
||||||
getDetailFailed: 'Gagal mendapatkan detail',
|
|
||||||
processing: 'Memproses',
|
|
||||||
completed: 'Selesai',
|
|
||||||
pending: 'Menunggu Diproses',
|
|
||||||
complain: 'Keluhan',
|
|
||||||
suggestion: 'Saran',
|
|
||||||
contactPhone: 'Nomor Telepon Kontak',
|
|
||||||
initialSubmit: 'Pengiriman Pertama',
|
|
||||||
submitTime: 'Waktu Pengiriman',
|
|
||||||
uploadedImages: 'Gambar yang Diunggah',
|
|
||||||
platformReplies: 'Balasan Platform',
|
|
||||||
userReplies: 'Balasan Pengguna',
|
|
||||||
platform: 'Layanan Pelanggan Platform',
|
|
||||||
me: 'Saya',
|
|
||||||
replyPlaceholder: 'Harap masukkan balasan Anda...',
|
|
||||||
submitReply: 'Kirim Balasan',
|
|
||||||
replySuccess: 'Balasan berhasil',
|
|
||||||
replyFailed: 'Balasan gagal',
|
|
||||||
pleaseEnterReply: 'Harap masukkan konten balasan',
|
|
||||||
idRequired: 'ID keluhan tidak boleh kosong',
|
|
||||||
viewRecords: 'Lihat Catatan',
|
|
||||||
replyHistory: 'Riwayat Balasan'
|
|
||||||
},
|
|
||||||
|
|
||||||
help: {
|
|
||||||
title: 'Pusat Layanan Pelanggan',
|
|
||||||
commonQuestions: 'Pertanyaan Umum',
|
|
||||||
contactUs: 'Hubungi Kami',
|
|
||||||
phone: 'Telepon',
|
|
||||||
email: 'Email',
|
|
||||||
workingHours: 'Jam Kerja',
|
|
||||||
workingHoursValue: 'Senin hingga Minggu 09:00-22:00',
|
|
||||||
functionDeveloping: 'Fungsi sedang dikembangkan',
|
|
||||||
faq1Question: 'Bagaimana cara menyewa kipas angin?',
|
|
||||||
faq1Answer: 'Klik tombol "Pindai untuk Menyewa" di beranda, gunakan WeChat untuk memindai kode QR pada perangkat, selesaikan pembayaran sesuai petunjuk untuk menggunakan.',
|
|
||||||
faq2Question: 'Bagaimana tarifnya?',
|
|
||||||
faq2Answer: 'Produk ini menyewakan kipas angin dalam bentuk sewa tanpa deposit, tidak perlu membayar deposit, metode penagihan spesifik mengikuti petunjuk pemindaian kode QR kabinet lokasi.',
|
|
||||||
faq3Question: 'Bagaimana cara mengembalikan kipas angin?',
|
|
||||||
faq3Answer: 'Bawa kipas angin ke titik pengembalian mana pun, klik tombol "Pindai untuk Mengembalikan" di beranda, pindai kode QR titik pengembalian untuk menyelesaikan pengembalian.',
|
|
||||||
faq4Question: 'Berapa lama deposit akan dikembalikan?',
|
|
||||||
faq4Answer: 'Setelah perangkat dikembalikan, deposit akan secara otomatis memulai pengembalian dana, diperkirakan akan diterima dalam 0-7 hari kerja.',
|
|
||||||
faq5Question: 'Apa yang harus dilakukan jika perangkat tidak dapat digunakan secara normal?',
|
|
||||||
faq5Answer: 'Anda dapat mengirimkan umpan balik kesalahan melalui "Saya-Keluhan dan Saran", atau langsung menghubungi nomor telepon layanan pelanggan untuk menanganinya.',
|
|
||||||
pauseBillingButton: 'Jeda penagihan',
|
|
||||||
pauseBillingHint: 'Anda dapat mengajukan jeda penagihan saat sewa aktif (rincian di halaman pesanan).'
|
|
||||||
},
|
|
||||||
|
|
||||||
settings: {
|
|
||||||
title: 'Pengaturan',
|
|
||||||
language: 'Bahasa',
|
|
||||||
languageSetting: 'Pengaturan Bahasa',
|
|
||||||
chinese: '简体中文',
|
|
||||||
english: 'English',
|
|
||||||
indonesian: 'Bahasa Indonesia',
|
|
||||||
languageSwitched: 'Bahasa telah diubah, sedang memuat ulang...',
|
|
||||||
notification: 'Notifikasi',
|
|
||||||
privacy: 'Privasi',
|
|
||||||
about: 'Tentang',
|
|
||||||
clearCache: 'Hapus Cache',
|
|
||||||
cacheCleared: 'Cache telah dihapus',
|
|
||||||
logout: 'Keluar',
|
|
||||||
confirmLogout: 'Konfirmasi keluar?',
|
|
||||||
logoutSuccess: 'Keluar berhasil'
|
|
||||||
},
|
|
||||||
|
|
||||||
express: {
|
|
||||||
title: 'Pengembalian Ekspres',
|
|
||||||
addReturn: 'Tambah Pengembalian',
|
|
||||||
returnRecord: 'Catatan Pengembalian Ekspres',
|
|
||||||
expressNo: 'Nomor Ekspres',
|
|
||||||
expressCompany: 'Perusahaan Ekspres',
|
|
||||||
sendTime: 'Waktu Pengiriman',
|
|
||||||
receivedTime: 'Waktu Penerimaan',
|
|
||||||
status: 'Status',
|
|
||||||
pending: 'Menunggu Diproses',
|
|
||||||
shipped: 'Telah Dikirim',
|
|
||||||
received: 'Telah Diterima',
|
|
||||||
detail: 'Detail',
|
|
||||||
recipientInfo: 'Informasi Penerima',
|
|
||||||
recipientName: 'Isidaya 18163601305',
|
|
||||||
recipientAddress: 'Gedung A2, Lantai 623, Taman Sains dan Teknologi Xinchanghaijian, Jalan Lugu, Distrik Yuelu, Changsha, Provinsi Hunan',
|
|
||||||
copyAllInfo: 'Salin Semua Informasi',
|
|
||||||
recipient: 'Penerima',
|
|
||||||
recipientAddressLabel: 'Alamat Penerima',
|
|
||||||
copySuccess: 'Semua informasi telah disalin',
|
|
||||||
copyFailed: 'Gagal menyalin',
|
|
||||||
noReturnRecord: 'Tidak ada catatan pengembalian',
|
|
||||||
toFill: 'Menunggu Diisi',
|
|
||||||
userPhone: 'Nomor Telepon Pengguna',
|
|
||||||
billingPaused: 'Penagihan Dijeda',
|
|
||||||
completed: 'Selesai',
|
|
||||||
processing: 'Memproses',
|
|
||||||
getListFailed: 'Gagal mendapatkan daftar',
|
|
||||||
loadFailed: 'Gagal memuat',
|
|
||||||
returnCompleted: 'Pengembalian Selesai',
|
|
||||||
returnCompletedDesc: 'Ekspres Anda telah berhasil dikembalikan',
|
|
||||||
processingDesc: 'Sedang memproses permintaan pengembalian Anda',
|
|
||||||
pendingDesc: 'Menunggu pemrosesan aplikasi pengembalian',
|
|
||||||
expressInfo: 'Informasi Ekspres',
|
|
||||||
trackingNo: 'Nomor Pelacakan',
|
|
||||||
packageType: 'Jenis Paket',
|
|
||||||
packageWeight: 'Berat Paket',
|
|
||||||
returnInfo: 'Informasi Pengembalian',
|
|
||||||
returnAddress: 'Alamat Pengembalian',
|
|
||||||
returnTime: 'Waktu Pengembalian',
|
|
||||||
processTime: 'Waktu Pemrosesan',
|
|
||||||
completeTime: 'Waktu Penyelesaian',
|
|
||||||
remarkInfo: 'Informasi Catatan',
|
|
||||||
copyTrackingNo: 'Salin Nomor Pelacakan',
|
|
||||||
trackingNoCopied: 'Nomor pelacakan telah disalin',
|
|
||||||
workingHours: 'Senin hingga Minggu 09:00-22:00',
|
|
||||||
call: 'Hubungi',
|
|
||||||
returnDetail: 'Detail Pengembalian',
|
|
||||||
getDetailFailed: 'Gagal mendapatkan detail',
|
|
||||||
fillExpress: 'Pengembalian Ekspres',
|
|
||||||
openTime: 'Waktu Mulai',
|
|
||||||
fillExpressInfo: 'Isi Informasi Pengembalian Ekspres',
|
|
||||||
contactPhone: 'Nomor Telepon Kontak',
|
|
||||||
fillTrackingPlaceholder: 'Harap masukkan nomor ekspres yang perlu diisi',
|
|
||||||
trackingPlaceholder: 'Harap masukkan nomor ekspres (dapat dikosongkan terlebih dahulu)',
|
|
||||||
confirmFill: 'Konfirmasi Isi',
|
|
||||||
submitInfo: 'Kirim Informasi',
|
|
||||||
orderNoMissing: 'Nomor pesanan kurang',
|
|
||||||
getRecordFailed: 'Gagal mendapatkan catatan',
|
|
||||||
existingReturnNotice: 'Sudah ada aplikasi pengembalian ekspres, apakah akan pergi untuk mengisi nomor ekspres?',
|
|
||||||
goToFill: 'Pergi untuk Mengisi',
|
|
||||||
alreadyHasRecord: 'Sudah ada catatan pengembalian',
|
|
||||||
pleaseEnterValidPhone: 'Harap isi nomor telepon kontak yang valid',
|
|
||||||
pleaseEnterTrackingNo: 'Harap isi nomor ekspres',
|
|
||||||
filling: 'Mengisi...',
|
|
||||||
fillSuccess: 'Pengisian berhasil',
|
|
||||||
fillFailed: 'Pengisian gagal',
|
|
||||||
submitSuccess: 'Pengiriman berhasil',
|
|
||||||
submitFailed: 'Pengiriman gagal'
|
|
||||||
},
|
|
||||||
|
|
||||||
join: {
|
|
||||||
title: 'Kerja Sama dan Keanggotaan',
|
|
||||||
cooperationTitle: 'Metode Kerja Sama',
|
|
||||||
contactUs: 'Hubungi Kami',
|
|
||||||
phone: 'Nomor Telepon Kontak',
|
|
||||||
email: 'Email Kontak',
|
|
||||||
submit: 'Kirim Aplikasi',
|
|
||||||
name: 'Nama',
|
|
||||||
contactPhone: 'Informasi Kontak',
|
|
||||||
city: 'Kota',
|
|
||||||
intention: 'Niat Kerja Sama',
|
|
||||||
placeholder: 'Harap jelaskan secara singkat niat kerja sama Anda...',
|
|
||||||
submitSuccess: 'Pengiriman berhasil, kami akan segera menghubungi Anda',
|
|
||||||
submitFailed: 'Pengiriman gagal, harap coba lagi nanti',
|
|
||||||
pageLoadFailed: 'Gagal memuat halaman'
|
|
||||||
},
|
|
||||||
|
|
||||||
legal: {
|
|
||||||
agreement: 'Perjanjian Pengguna',
|
|
||||||
privacy: 'Kebijakan Privasi',
|
|
||||||
termsOfService: 'Ketentuan Layanan',
|
|
||||||
termsAndConditions: 'Syarat & Ketentuan',
|
|
||||||
lastUpdate: 'Pembaruan Terakhir',
|
|
||||||
applicableToService: 'Berlaku untuk layanan sewa kipas angin berbagi "Isidaya"',
|
|
||||||
footerNotice: 'Jika ada pertanyaan tentang perjanjian ini, harap pergi ke "Saya-Layanan Pelanggan" untuk konsultasi',
|
|
||||||
footerNoticePolicy: 'Jika ada pertanyaan tentang kebijakan ini, harap pergi ke "Saya-Layanan Pelanggan" untuk konsultasi',
|
|
||||||
|
|
||||||
// Konten Syarat dan Ketentuan
|
|
||||||
applicableLaw: 'Hukum yang Berlaku',
|
|
||||||
applicableLawContent: 'Ketentuan Layanan ini diatur oleh hukum Republik Rakyat Tiongkok. Dengan menggunakan layanan ini, Anda setuju untuk terikat oleh hukum Tiongkok. Setiap perselisihan yang timbul dari layanan ini harus diselesaikan terlebih dahulu melalui negosiasi bersahabat; jika negosiasi gagal, salah satu pihak dapat mengajukan gugatan ke Pengadilan Rakyat yang memiliki yurisdiksi atas lokasi penyedia layanan.',
|
|
||||||
|
|
||||||
paymentMethods: 'Metode Pembayaran',
|
|
||||||
paymentMethodsContent: 'Kami mendukung berbagai metode pembayaran, termasuk namun tidak terbatas pada: WeChat Pay, Alipay, WeChat Pay Score tanpa deposit, dll. Pengguna perlu menyelesaikan proses pembayaran sebelum menggunakan layanan. Setelah pembayaran berhasil, sistem akan secara otomatis membuka kunci perangkat untuk akses pengguna. Semua transaksi pembayaran dilakukan melalui saluran terenkripsi yang aman untuk memastikan keamanan dana pengguna.',
|
|
||||||
|
|
||||||
refundPolicy: 'Kebijakan Pengembalian Dana',
|
|
||||||
refundPolicyContent: '1. Pengembalian Deposit: Setelah mengembalikan perangkat, deposit akan secara otomatis dikembalikan ke akun pembayaran asli setelah dikurangi biaya sewa yang sesuai, diperkirakan tiba dalam 0-7 hari kerja.\n2. Pembatalan Pesanan: Pesanan yang tidak digunakan dapat dibatalkan sebelum penggunaan dimulai, dan deposit akan dikembalikan sepenuhnya.\n3. Pengembalian Dana Pengecualian: Dalam kasus keadaan khusus seperti kegagalan perangkat, pengguna dapat mengajukan pengembalian dana, yang akan kami proses dalam 3-5 hari kerja setelah verifikasi.\n4. Kartu Keanggotaan/Kupon: Kartu keanggotaan dan kupon yang dibeli umumnya tidak mendukung pengembalian dana. Silakan hubungi layanan pelanggan untuk kasus khusus.',
|
|
||||||
|
|
||||||
serviceTerms: 'Ketentuan Layanan',
|
|
||||||
serviceTermsContent: 'Saat menggunakan layanan ini, pengguna harus mematuhi peraturan berikut: 1) Jaga peralatan yang disewa dengan baik dan jangan sengaja merusak atau memilikinya secara pribadi; 2) Kembalikan peralatan tepat waktu untuk menghindari biaya tambahan; 3) Jangan gunakan peralatan untuk tujuan ilegal; 4) Jika ditemukan kegagalan peralatan, hubungi layanan pelanggan segera. Pelanggaran terhadap peraturan di atas dapat mengakibatkan penghentian layanan dan tanggung jawab.',
|
|
||||||
|
|
||||||
liabilityLimitation: 'Batasan Tanggung Jawab',
|
|
||||||
liabilityLimitationContent: 'Sejauh diizinkan oleh hukum, kami tidak bertanggung jawab atas kerusakan tidak langsung, insidental, khusus, atau konsekuensial yang timbul dari penggunaan atau ketidakmampuan menggunakan layanan ini. Total tanggung jawab kami tidak akan melebihi biaya yang dibayarkan oleh pengguna untuk menggunakan layanan ini. Kami tidak bertanggung jawab atas gangguan atau penundaan layanan yang disebabkan oleh force majeure, kegagalan jaringan, alasan pihak ketiga, dll.',
|
|
||||||
|
|
||||||
disputeResolution: 'Penyelesaian Sengketa',
|
|
||||||
disputeResolutionContent: 'Jika pengguna memiliki pertanyaan atau perselisihan tentang layanan, silakan hubungi kami terlebih dahulu melalui saluran layanan pelanggan. Kami akan merespons dalam 24 jam setelah menerima umpan balik dan bernegosiasi untuk penyelesaian sesegera mungkin. Jika negosiasi gagal, kedua belah pihak setuju untuk menyerahkan perselisihan ke Pengadilan Rakyat dengan yurisdiksi atas lokasi penyedia layanan untuk penyelesaian melalui litigasi. Selama periode penyelesaian sengketa, kedua belah pihak harus terus melaksanakan ketentuan yang tidak dipersengketakan dari perjanjian ini.'
|
|
||||||
},
|
|
||||||
|
|
||||||
search: {
|
|
||||||
title: 'Cari Perangkat',
|
|
||||||
placeholder: 'Harap masukkan nama lokasi atau alamat',
|
|
||||||
history: 'Riwayat Pencarian',
|
|
||||||
clear: 'Hapus Riwayat',
|
|
||||||
noResult: 'Tidak ada hasil pencarian',
|
|
||||||
searching: 'Mencari...',
|
|
||||||
invalidCoordinate: 'Koordinat lokasi ini tidak valid',
|
|
||||||
positionInfoError: 'Informasi lokasi abnormal'
|
|
||||||
},
|
|
||||||
|
|
||||||
share: {
|
|
||||||
title: 'Isidaya - Kipas Angin & Power Bank Berbagi',
|
|
||||||
path: '/pages/index/index'
|
|
||||||
},
|
|
||||||
|
|
||||||
error: {
|
|
||||||
networkError: 'Koneksi jaringan gagal',
|
|
||||||
serverError: 'Kesalahan server',
|
|
||||||
timeout: 'Permintaan timeout',
|
|
||||||
unknown: 'Kesalahan tidak diketahui',
|
|
||||||
tryAgain: 'Harap coba lagi nanti'
|
|
||||||
},
|
|
||||||
|
|
||||||
time: {
|
|
||||||
hour: 'jam',
|
|
||||||
minute: 'menit',
|
|
||||||
second: 'detik',
|
|
||||||
day: 'hari',
|
|
||||||
week: 'minggu',
|
|
||||||
month: 'bulan',
|
|
||||||
year: 'tahun',
|
|
||||||
justNow: 'Baru saja',
|
|
||||||
minutesAgo: 'menit yang lalu',
|
|
||||||
hoursAgo: 'jam yang lalu',
|
|
||||||
daysAgo: 'hari yang lalu',
|
|
||||||
yesterday: 'Kemarin',
|
|
||||||
today: 'Hari ini',
|
|
||||||
tomorrow: 'Besok',
|
|
||||||
hours: 'jam',
|
|
||||||
minutes: 'menit',
|
|
||||||
halfHours: 'setengah jam',
|
|
||||||
lessThan: 'kurang dari'
|
|
||||||
},
|
|
||||||
|
|
||||||
unit: {
|
|
||||||
yuan: 'Yuan',
|
|
||||||
meter: 'meter',
|
|
||||||
km: 'kilometer',
|
|
||||||
piece: 'buah',
|
|
||||||
times: 'kali'
|
|
||||||
},
|
|
||||||
|
|
||||||
waiting: {
|
|
||||||
title: 'Perangkat Sedang Muncul',
|
|
||||||
preparing: 'Sedang menyiapkan perangkat untuk Anda',
|
|
||||||
longTimeNotice: 'Jika tidak muncul dalam waktu lama, harap hubungi staf di lokasi atau coba lagi nanti',
|
|
||||||
deviceEjecting: 'Perangkat sedang muncul, harap tunggu',
|
|
||||||
rentFailed: 'Penyewaan perangkat gagal, pesanan telah dibatalkan',
|
|
||||||
timeout: 'Waktu tunggu habis, harap coba lagi nanti'
|
|
||||||
},
|
|
||||||
|
|
||||||
success: {
|
|
||||||
paymentSuccess: 'Pembayaran berhasil',
|
|
||||||
paymentSuccessDesc: 'Pesanan Anda telah berhasil dibayar',
|
|
||||||
orderInfo: 'Informasi Pesanan',
|
|
||||||
paymentAmount: 'Jumlah Pembayaran',
|
|
||||||
paymentTime: 'Waktu Pembayaran',
|
|
||||||
deviceStatus: 'Status Perangkat',
|
|
||||||
preparingDevice: 'Sedang menyiapkan perangkat Anda, harap tunggu...',
|
|
||||||
deviceReady: 'Perangkat telah muncul, harap ambil kipas angin Anda',
|
|
||||||
deviceFailed: 'Gagal mengeluarkan perangkat, harap hubungi layanan pelanggan',
|
|
||||||
backToHome: 'Kembali ke Beranda',
|
|
||||||
viewOrder: 'Lihat Pesanan',
|
|
||||||
returnSuccess: 'Pengembalian berhasil',
|
|
||||||
returnSuccessDesc: 'Kipas angin Anda telah dikembalikan, biaya telah dipotong dari deposit',
|
|
||||||
usedTime: 'Durasi Penggunaan',
|
|
||||||
packageTime: 'Durasi Paket',
|
|
||||||
extraTime: 'Waktu Tambahan',
|
|
||||||
returnTime: 'Waktu Pengembalian',
|
|
||||||
packageFee: 'Biaya Paket',
|
|
||||||
extraFee: 'Biaya Tambahan',
|
|
||||||
totalFee: 'Total Biaya',
|
|
||||||
depositAmount: 'Deposit',
|
|
||||||
refundAmount: 'Jumlah Pengembalian',
|
|
||||||
refundStatus: 'Status Pengembalian',
|
|
||||||
refundNotice: 'Penjelasan Pengembalian Dana',
|
|
||||||
refundNotice1: 'Jumlah sisa deposit perlu Anda ajukan penarikan secara manual',
|
|
||||||
refundNotice2: 'Setelah aplikasi penarikan diajukan, akan dikembalikan ke akun pembayaran asli dalam 1-3 hari kerja',
|
|
||||||
refundNotice3: 'Jika ada pertanyaan, harap hubungi layanan pelanggan',
|
|
||||||
applyRefund: 'Ajukan Pengembalian Dana',
|
|
||||||
refundWaiting: 'Menunggu Aplikasi',
|
|
||||||
refundProcessing: 'Memproses',
|
|
||||||
refundSuccess: 'Telah Dikembalikan',
|
|
||||||
refundFailed: 'Pengembalian dana gagal'
|
|
||||||
},
|
|
||||||
|
|
||||||
deposit: {
|
|
||||||
title: 'Manajemen Deposit',
|
|
||||||
depositBalance: 'Saldo Deposit',
|
|
||||||
withdraw: 'Tarik',
|
|
||||||
withdrawRecord: 'Catatan Penarikan',
|
|
||||||
withdrawAmount: 'Jumlah Penarikan',
|
|
||||||
withdrawStatus: 'Status Penarikan',
|
|
||||||
applyWithdraw: 'Ajukan Penarikan',
|
|
||||||
withdrawSuccess: 'Penarikan berhasil',
|
|
||||||
withdrawFailed: 'Penarikan gagal',
|
|
||||||
noBalance: 'Tidak ada saldo yang dapat ditarik',
|
|
||||||
confirmWithdraw: 'Konfirmasi Penarikan',
|
|
||||||
withdrawDesc: 'Deposit akan dikembalikan ke jalur asli, diperkirakan akan diterima dalam 0-7 hari kerja',
|
|
||||||
withdrawing: 'Sedang menarik...',
|
|
||||||
withdrawSubmitted: 'Aplikasi penarikan telah diajukan',
|
|
||||||
withdrawNotice: 'Penjelasan Penarikan',
|
|
||||||
withdrawNotice1: 'Jumlah penarikan akan dikembalikan ke akun pembayaran asli',
|
|
||||||
withdrawNotice2: 'Setelah aplikasi penarikan diajukan, diperkirakan akan diterima dalam 0-7 hari kerja',
|
|
||||||
withdrawNotice3: 'Jika tidak diterima setelah waktu habis, harap hubungi layanan pelanggan untuk menanganinya',
|
|
||||||
depositRecord: 'Catatan Deposit',
|
|
||||||
payRecord: 'Catatan Pembayaran',
|
|
||||||
refundRecord: 'Catatan Pengembalian',
|
|
||||||
orderNotReturned: 'Pesanan saat ini belum dikembalikan, harap kembalikan terlebih dahulu sebelum menarik',
|
|
||||||
alreadyRefunded: 'Deposit telah dikembalikan, tidak perlu menarik ulang',
|
|
||||||
refundProcessing: 'Pengembalian deposit sedang diproses, harap tunggu dengan sabar'
|
|
||||||
},
|
|
||||||
|
|
||||||
userProfile: {
|
|
||||||
title: 'Informasi Pribadi',
|
|
||||||
avatar: 'Avatar',
|
|
||||||
nickname: 'Nama Panggilan',
|
|
||||||
phone: 'Nomor Telepon',
|
|
||||||
edit: 'Edit',
|
|
||||||
save: 'Simpan',
|
|
||||||
cancel: 'Batal',
|
|
||||||
clickToChange: 'Klik avatar untuk mengubah',
|
|
||||||
notSet: 'Tidak diatur',
|
|
||||||
notBound: 'Tidak terikat',
|
|
||||||
balance: 'Saldo',
|
|
||||||
enterNickname: 'Harap masukkan nama panggilan baru',
|
|
||||||
nicknameRequired: 'Nama panggilan tidak boleh kosong',
|
|
||||||
saving: 'Menyimpan...',
|
|
||||||
nicknameUpdated: 'Nama panggilan berhasil diubah',
|
|
||||||
updateFailed: 'Gagal mengubah',
|
|
||||||
uploading: 'Mengunggah...'
|
|
||||||
},
|
|
||||||
|
|
||||||
purchase: {
|
|
||||||
title: 'Area Diskon',
|
|
||||||
memberCard: 'Kartu Anggota',
|
|
||||||
coupon: 'Kupon',
|
|
||||||
buyNow: 'Beli Sekarang',
|
|
||||||
myCards: 'Kartu Anggota Saya',
|
|
||||||
myCoupons: 'Kupon Saya',
|
|
||||||
cardDescription: 'Penjelasan Kartu Anggota',
|
|
||||||
couponDescription: 'Penjelasan Kupon',
|
|
||||||
pleaseSelect: 'Harap pilih produk yang ingin dibeli',
|
|
||||||
noCards: 'Tidak ada kartu anggota yang tersedia',
|
|
||||||
noCoupons: 'Tidak ada kupon yang tersedia',
|
|
||||||
cardUseInstruction: 'Instruksi Penggunaan',
|
|
||||||
cardValidityPeriod: 'Periode Validitas',
|
|
||||||
cardRefundPolicy: 'Penjelasan Pengembalian Dana',
|
|
||||||
cardUseDescription: 'Kartu anggota berlaku segera setelah pembelian dan dapat digunakan di lokasi yang ditentukan. Kartu kali dihitung berdasarkan jumlah penggunaan, kartu durasi dihitung berdasarkan durasi penggunaan, harap pilih jenis kartu yang sesuai dengan kebutuhan aktual Anda.',
|
|
||||||
cardValidityDescription: 'Kartu anggota berlaku sejak tanggal pembelian, periode validitas berbeda sesuai dengan jenis kartu. Kartu kali akan kedaluwarsa setelah digunakan dalam periode validitas, kartu durasi akan kedaluwarsa setelah durasi penggunaan kumulatif tercapai dalam periode validitas.',
|
|
||||||
cardRefundDescription: 'Kartu anggota tidak mendukung pengembalian dana setelah pembelian, bagian yang tidak digunakan dapat terus digunakan dalam periode validitas. Jika perlu pengembalian dana dalam situasi khusus, harap hubungi layanan pelanggan untuk menanganinya.',
|
|
||||||
couponUseInstruction: 'Instruksi Penggunaan',
|
|
||||||
couponValidityPeriod: 'Periode Validitas',
|
|
||||||
couponUsageScope: 'Cakupan Penggunaan',
|
|
||||||
couponUseDescription: 'Kupon berlaku segera setelah pembelian dan dapat digunakan saat penyelesaian pesanan. Setiap pesanan hanya dapat menggunakan satu kupon, kupon tidak dapat digunakan bersamaan dengan aktivitas diskon lainnya.',
|
|
||||||
couponValidityDescription: 'Kupon berlaku sejak tanggal pembelian, harap gunakan dalam periode validitas. Setelah kedaluwarsa, kupon akan otomatis tidak berlaku dan tidak dapat diperpanjang.',
|
|
||||||
couponUsageDescription: 'Kupon dapat digunakan di lokasi yang ditentukan, untuk lokasi yang tersedia harap lihat detail kupon. Beberapa kupon memiliki persyaratan minimum konsumsi, harap perhatikan kondisi penggunaan.'
|
|
||||||
},
|
|
||||||
|
|
||||||
myCard: {
|
|
||||||
type: 'Jenis',
|
|
||||||
timesCard: 'Kartu Kali',
|
|
||||||
durationCard: 'Kartu Durasi',
|
|
||||||
remainingTimes: 'Sisa Kali:',
|
|
||||||
remainingDuration: 'Sisa Durasi',
|
|
||||||
hours: 'jam',
|
|
||||||
validPeriod: 'Periode Validitas',
|
|
||||||
active: 'Sedang Digunakan',
|
|
||||||
expired: 'Tidak Berlaku',
|
|
||||||
used: 'Habis',
|
|
||||||
position: 'Lokasi Penggunaan',
|
|
||||||
price: 'Harga Pembelian',
|
|
||||||
noCards: 'Tidak ada kartu anggota',
|
|
||||||
buyNow: 'Beli Sekarang',
|
|
||||||
getListFailed: 'Gagal mendapatkan daftar kartu anggota',
|
|
||||||
dailyLimit: 'Batas Harian',
|
|
||||||
singleTimeLimit: 'Batas Waktu Per Kali',
|
|
||||||
unlimited: 'Tidak Terbatas',
|
|
||||||
times: 'kali',
|
|
||||||
minutes: 'menit',
|
|
||||||
validWithinDays: 'hari berlaku',
|
|
||||||
validFromPurchase: 'Dari waktu pembelian',
|
|
||||||
daysValid: 'hari berlaku',
|
|
||||||
currentCycleUsed: 'Digunakan dalam siklus ini',
|
|
||||||
totalCount: 'Total Kali',
|
|
||||||
expire: 'Kedaluwarsa',
|
|
||||||
expiredOn: 'Kedaluwarsa pada',
|
|
||||||
renew: 'Perpanjang Kartu',
|
|
||||||
toUse: 'Gunakan',
|
|
||||||
onlyForRegionBefore: 'Hanya untuk',
|
|
||||||
onlyForRegionAfter: 'menggunakan'
|
|
||||||
},
|
|
||||||
|
|
||||||
myCoupon: {
|
|
||||||
available: 'Tersedia',
|
|
||||||
used: 'Digunakan',
|
|
||||||
expired: 'Kedaluwarsa',
|
|
||||||
useNow: 'Gunakan',
|
|
||||||
usedStatus: 'Digunakan',
|
|
||||||
expiredStatus: 'Kedaluwarsa',
|
|
||||||
refundedStatus: 'Telah Dikembalikan',
|
|
||||||
noAvailableCoupons: 'Tidak ada kupon yang tersedia',
|
|
||||||
noUsedCoupons: 'Tidak ada kupon yang telah digunakan',
|
|
||||||
noExpiredCoupons: 'Tidak ada kupon yang kedaluwarsa',
|
|
||||||
buyNow: 'Beli Sekarang',
|
|
||||||
getListFailed: 'Gagal mendapatkan daftar kupon',
|
|
||||||
onlyForRegionBefore: 'Hanya untuk',
|
|
||||||
onlyForRegionAfter: 'menggunakan'
|
|
||||||
},
|
|
||||||
|
|
||||||
goods: {
|
|
||||||
title: 'Detail Produk',
|
|
||||||
goodsTitle: 'Detail Kustomisasi',
|
|
||||||
defaultProductNameShort: 'Isidaya 2026',
|
|
||||||
defaultProductNameFull: 'Isidaya 2026 Kipas Angin, Power Bank & Hand Warmer 3-in-1',
|
|
||||||
productName: 'Isidaya Kipas Angin Berbagi + Power Bank + Seri Hand Warmer - Pink Sakura',
|
|
||||||
perUnit: '/buah',
|
|
||||||
buyNow: 'Beli Sekarang',
|
|
||||||
productDetail: 'Detail Kustomisasi',
|
|
||||||
features: {
|
|
||||||
battery: '8000Ahm',
|
|
||||||
batteryDesc: 'Baterai Kapasitas Besar',
|
|
||||||
wind: 'Kipas Angin Efisien',
|
|
||||||
temp: 'Kontrol Suhu Pintar',
|
|
||||||
charge: 'Pengisian Cepat'
|
|
||||||
},
|
|
||||||
description: 'Isidaya kipas angin berbagi, mengintegrasikan tiga fungsi dalam satu: kipas angin, power bank, dan hand warmer. Menggunakan baterai kapasitas besar 8000mAh, daya tahan lama. Desain kipas angin efisien, tiga tingkat angin dapat disesuaikan. Hand warmer kontrol suhu pintar, hangat di musim dingin dan sejuk di musim panas. Teknologi pengisian cepat, mendukung pengisian multi-perangkat. Warna pink sakura, modis dan indah, adalah teman perjalanan terbaik Anda.',
|
|
||||||
confirmPurchase: 'Konfirmasi Pembelian',
|
|
||||||
confirmPurchaseContent: 'Konfirmasi membeli produk ini, perlu membayar ¥{price}?',
|
|
||||||
purchaseSuccess: 'Pembelian berhasil',
|
|
||||||
purchaseFailed: 'Pembelian gagal',
|
|
||||||
processing: 'Sedang memproses...'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import zhCN from './zh-CN.js'
|
import zhCN from './zh-CN.js'
|
||||||
import enUS from './en-US.js'
|
import enUS from './en-US.js'
|
||||||
import idID from './id-ID.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'zh-CN': zhCN,
|
'zh-CN': zhCN,
|
||||||
'en-US': enUS,
|
'en-US': enUS
|
||||||
'id-ID': idID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,125 +4,33 @@ import { createSSRApp } from 'vue'
|
|||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import zhCN from './locale/zh-CN.js'
|
import zhCN from './locale/zh-CN.js'
|
||||||
import enUS from './locale/en-US.js'
|
import enUS from './locale/en-US.js'
|
||||||
import idID from './locale/id-ID.js'
|
|
||||||
import uView from '@climblee/uv-ui'
|
import uView from '@climblee/uv-ui'
|
||||||
import { initConsoleControl } from './config/console.js'
|
|
||||||
import { getSystemConfig } from './config/api/system.js'
|
|
||||||
|
|
||||||
// #ifdef H5
|
|
||||||
// 兼容部分依赖/构建产物在浏览器环境访问 process.env 的场景
|
|
||||||
if (typeof globalThis !== 'undefined' && typeof globalThis.process === 'undefined') {
|
|
||||||
globalThis.process = { env: {} }
|
|
||||||
}
|
|
||||||
if (typeof globalThis !== 'undefined' && globalThis.process && !globalThis.process.env) {
|
|
||||||
globalThis.process.env = {}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 初始化 console 控制
|
|
||||||
initConsoleControl()
|
|
||||||
|
|
||||||
const LANGUAGE_STORAGE_KEY = 'language'
|
|
||||||
const SUPPORTED_LANGUAGES = ['zh-CN', 'en-US', 'id-ID']
|
|
||||||
|
|
||||||
const LANGUAGE_ALIASES = {
|
|
||||||
zh: 'zh-CN',
|
|
||||||
'zh-cn': 'zh-CN',
|
|
||||||
'zh_cn': 'zh-CN',
|
|
||||||
en: 'en-US',
|
|
||||||
'en-us': 'en-US',
|
|
||||||
'en_us': 'en-US',
|
|
||||||
id: 'id-ID',
|
|
||||||
'id-id': 'id-ID',
|
|
||||||
'id_id': 'id-ID',
|
|
||||||
in: 'id-ID',
|
|
||||||
'in-id': 'id-ID',
|
|
||||||
'in_id': 'id-ID'
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizeLanguage = (lang) => {
|
|
||||||
if (!lang || typeof lang !== 'string') return ''
|
|
||||||
const cleaned = lang.trim()
|
|
||||||
if (!cleaned) return ''
|
|
||||||
const lower = cleaned.toLowerCase()
|
|
||||||
if (LANGUAGE_ALIASES[lower]) return LANGUAGE_ALIASES[lower]
|
|
||||||
if (SUPPORTED_LANGUAGES.includes(cleaned)) return cleaned
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测是否为 H5 环境
|
|
||||||
const isH5Platform = () => {
|
|
||||||
try {
|
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
|
||||||
return systemInfo.platform === 'web' || systemInfo.uniPlatform === 'web' ||
|
|
||||||
(typeof window !== 'undefined' && typeof document !== 'undefined')
|
|
||||||
} catch (e) {
|
|
||||||
// 如果获取系统信息失败,尝试通过全局对象判断
|
|
||||||
return typeof window !== 'undefined' && typeof document !== 'undefined'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取系统语言
|
// 获取系统语言
|
||||||
const getSystemLanguage = () => {
|
const getSystemLanguage = () => {
|
||||||
let language = 'zh-CN'
|
let language = 'zh-CN'
|
||||||
try {
|
try {
|
||||||
const systemInfo = uni.getSystemInfoSync() || {}
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
const systemLanguage = normalizeLanguage(systemInfo.language)
|
if (systemInfo && systemInfo.language) {
|
||||||
if (systemLanguage) {
|
language = systemInfo.language === 'zh' || systemInfo.language.indexOf('zh') === 0
|
||||||
language = systemLanguage
|
? 'zh-CN'
|
||||||
} else if (isH5Platform() && typeof navigator !== 'undefined') {
|
: 'en-US'
|
||||||
const browserLanguage = normalizeLanguage(navigator.language || '')
|
|
||||||
if (browserLanguage) language = browserLanguage
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('获取系统语言失败:', e)
|
console.error('获取系统语言失败:', e)
|
||||||
language = 'zh-CN'
|
|
||||||
}
|
}
|
||||||
return language
|
return language
|
||||||
}
|
}
|
||||||
|
|
||||||
const extractLanguageFromConfig = (data) => {
|
|
||||||
if (!data) return ''
|
|
||||||
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
return normalizeLanguage(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
for (const item of data) {
|
|
||||||
const fromItem = extractLanguageFromConfig(item)
|
|
||||||
if (fromItem) return fromItem
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data === 'object') {
|
|
||||||
const direct = normalizeLanguage(
|
|
||||||
data.language || data.lang || data.locale || data.defaultLanguage || data.defaultLang
|
|
||||||
)
|
|
||||||
if (direct) return direct
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(data)) {
|
|
||||||
const keyLower = String(key).toLowerCase()
|
|
||||||
if (keyLower.includes('lang') || keyLower.includes('locale')) {
|
|
||||||
const parsed = extractLanguageFromConfig(value)
|
|
||||||
if (parsed) return parsed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取用户选择的语言
|
// 获取用户选择的语言
|
||||||
const getSavedLanguage = () => {
|
const getSavedLanguage = () => {
|
||||||
try {
|
try {
|
||||||
const savedLang = normalizeLanguage(uni.getStorageSync(LANGUAGE_STORAGE_KEY))
|
const savedLang = uni.getStorageSync('language')
|
||||||
if (savedLang) {
|
if (savedLang) {
|
||||||
return savedLang
|
return savedLang
|
||||||
}
|
}
|
||||||
const systemLang = getSystemLanguage()
|
const systemLang = getSystemLanguage()
|
||||||
uni.setStorageSync(LANGUAGE_STORAGE_KEY, systemLang)
|
uni.setStorageSync('language', systemLang)
|
||||||
return systemLang
|
return systemLang
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('语言设置出错:', e)
|
console.error('语言设置出错:', e)
|
||||||
@@ -137,6 +45,10 @@ function getI18nInstance() {
|
|||||||
// 每次都重新读取当前语言
|
// 每次都重新读取当前语言
|
||||||
const currentLang = getSavedLanguage()
|
const currentLang = getSavedLanguage()
|
||||||
|
|
||||||
|
console.log('=== getI18nInstance 被调用 ===')
|
||||||
|
console.log('缓存中的语言:', currentLang)
|
||||||
|
console.log('当前 i18n 实例存在?', !!i18nInstance)
|
||||||
|
console.log('当前 i18n.global.locale:', i18nInstance?.global?.locale)
|
||||||
|
|
||||||
// 检查是否需要更新语言
|
// 检查是否需要更新语言
|
||||||
if (i18nInstance && i18nInstance.global.locale !== currentLang) {
|
if (i18nInstance && i18nInstance.global.locale !== currentLang) {
|
||||||
@@ -147,51 +59,44 @@ function getI18nInstance() {
|
|||||||
// 直接更新 locale(这应该会触发所有组件重新渲染)
|
// 直接更新 locale(这应该会触发所有组件重新渲染)
|
||||||
i18nInstance.global.locale = currentLang
|
i18nInstance.global.locale = currentLang
|
||||||
|
|
||||||
|
console.log('i18n.global.locale 已更新为:', i18nInstance.global.locale)
|
||||||
|
console.log('测试翻译 (common.loading):', i18nInstance.global.t('common.loading'))
|
||||||
|
console.log('测试翻译 (home.title):', i18nInstance.global.t('home.title'))
|
||||||
|
console.log('===================================')
|
||||||
|
|
||||||
return i18nInstance
|
return i18nInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首次创建实例
|
// 首次创建实例
|
||||||
if (!i18nInstance) {
|
if (!i18nInstance) {
|
||||||
|
console.log('=== 首次创建 i18n 实例 ===')
|
||||||
|
|
||||||
i18nInstance = createI18n({
|
i18nInstance = createI18n({
|
||||||
legacy: true, // 使用 Legacy API 模式,支持全局 $t
|
legacy: true, // 使用 Legacy API 模式,支持全局 $t
|
||||||
locale: currentLang,
|
locale: currentLang,
|
||||||
fallbackLocale: 'zh-CN',
|
fallbackLocale: 'zh-CN',
|
||||||
messages: {
|
messages: {
|
||||||
'zh-CN': zhCN,
|
'zh-CN': zhCN,
|
||||||
'en-US': enUS,
|
'en-US': enUS
|
||||||
'id-ID': idID
|
|
||||||
},
|
},
|
||||||
silentTranslationWarn: true,
|
silentTranslationWarn: true,
|
||||||
silentFallbackWarn: true
|
silentFallbackWarn: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log('i18n 实例已创建,语言:', currentLang)
|
||||||
|
console.log('测试翻译 (common.loading):', i18nInstance.global.t('common.loading'))
|
||||||
|
console.log('测试翻译 (home.title):', i18nInstance.global.t('home.title'))
|
||||||
|
console.log('============================')
|
||||||
}
|
}
|
||||||
|
|
||||||
return i18nInstance
|
return i18nInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncLanguageFromRemoteConfig = async (i18n) => {
|
|
||||||
if (!isH5Platform()) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await getSystemConfig()
|
|
||||||
if (!res || res.code !== 200) return
|
|
||||||
|
|
||||||
const remoteLang = extractLanguageFromConfig(res.data)
|
|
||||||
if (!remoteLang) return
|
|
||||||
|
|
||||||
const current = normalizeLanguage(i18n?.global?.locale || '')
|
|
||||||
if (current !== remoteLang) {
|
|
||||||
uni.setStorageSync(LANGUAGE_STORAGE_KEY, remoteLang)
|
|
||||||
i18n.global.locale = remoteLang
|
|
||||||
console.log('H5 语言已按系统配置更新为:', remoteLang)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('读取系统配置语言失败,使用本地语言设置:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
|
console.log('========================================')
|
||||||
|
console.log('=== createApp 被调用 ===')
|
||||||
|
console.log('时间戳:', new Date().toLocaleTimeString())
|
||||||
|
console.log('========================================')
|
||||||
|
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
|
|
||||||
@@ -204,9 +109,6 @@ export function createApp() {
|
|||||||
// 使用 i18n
|
// 使用 i18n
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|
||||||
// H5 端通过系统配置同步语言(异步,不阻塞应用启动)
|
|
||||||
syncLanguageFromRemoteConfig(i18n)
|
|
||||||
|
|
||||||
// 手动注入 $i18n 到全局属性(确保组件可以访问)
|
// 手动注入 $i18n 到全局属性(确保组件可以访问)
|
||||||
app.config.globalProperties.$i18n = i18n.global
|
app.config.globalProperties.$i18n = i18n.global
|
||||||
|
|
||||||
@@ -224,6 +126,13 @@ export function createApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('=== Vue 3 应用创建完成 ===')
|
||||||
|
console.log('最终 locale:', i18n.global.locale)
|
||||||
|
console.log('app.config.globalProperties.$t 存在?', !!app.config.globalProperties.$t)
|
||||||
|
console.log('app.config.globalProperties.$i18n 存在?', !!app.config.globalProperties.$i18n)
|
||||||
|
console.log('测试 $t 调用:', i18n.global.t('common.loading'))
|
||||||
|
console.log('===========================')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,39 +43,26 @@
|
|||||||
"ios" : {},
|
"ios" : {},
|
||||||
"sdkConfigs" : {
|
"sdkConfigs" : {
|
||||||
"maps" : {
|
"maps" : {
|
||||||
"amap" : {
|
"qqmap" : {
|
||||||
"appkey_ios" : "4c513a688938fd89b88b296e867f66ec",
|
"appkey_ios" : "RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35",
|
||||||
"appkey_android" : "4c513a688938fd89b88b296e867f66ec"
|
"appkey_android" : "RO5BZ-ECZ63-7US3C-RT5QW-TIDZE-2FF35"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"quickapp" : {},
|
"quickapp" : {},
|
||||||
"mp-weixin" : {
|
|
||||||
"appid" : "wx2165f0be356ae7a9",
|
|
||||||
"setting" : {
|
|
||||||
"urlCheck" : false
|
|
||||||
},
|
|
||||||
"usingComponents" : true,
|
|
||||||
"permission" : {
|
|
||||||
"scope.getPhoneNumber" : {
|
|
||||||
"desc" : "您的手机号将用于登录和订单服务"
|
|
||||||
},
|
|
||||||
"scope.userLocation" : {
|
|
||||||
"desc" : "您的位置信息将用于获取附近的设备"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
|
|
||||||
},
|
|
||||||
"mp-alipay" : {
|
"mp-alipay" : {
|
||||||
"component2" : true,
|
"component2": true,
|
||||||
"transpile" : [ "uview-ui", "vue-i18n" ],
|
|
||||||
"skia" : true,
|
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"appid" : "2021006117693332",
|
"appid" : "2021006117693332",
|
||||||
"unipush" : {
|
"unipush" : {
|
||||||
"enable" : false
|
"enable" : false
|
||||||
|
},
|
||||||
|
"permission" : {
|
||||||
|
"scope.userLocation" : {
|
||||||
|
"desc" : "您的位置信息将用于获取附近的设备"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mp-baidu" : {
|
"mp-baidu" : {
|
||||||
@@ -84,20 +71,6 @@
|
|||||||
"mp-toutiao" : {
|
"mp-toutiao" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
},
|
},
|
||||||
"h5" : {
|
|
||||||
"sdkConfigs" : {
|
|
||||||
"maps" : {
|
|
||||||
"qqmap" : {
|
|
||||||
"key" : "DJQBZ-WB53Q-WPS5B-4S6J7-53RMS-X4FJ2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"router" : {
|
|
||||||
"mode" : "history",
|
|
||||||
"base" : "/"
|
|
||||||
},
|
|
||||||
"title" : "Isidaya"
|
|
||||||
},
|
|
||||||
"uniStatistics" : {
|
"uniStatistics" : {
|
||||||
"enable" : false
|
"enable" : false
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
"@climblee/uv-ui": "^1.1.20",
|
"@climblee/uv-ui": "^1.1.20",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"axios-miniprogram-adapter": "0.3.4",
|
"axios-miniprogram-adapter": "0.3.4",
|
||||||
"html5-qrcode": "^2.3.8",
|
|
||||||
"uniapp-axios-adapter": "^0.3.2",
|
"uniapp-axios-adapter": "^0.3.2",
|
||||||
"uview-ui": "1.8.8",
|
"uview-ui": "1.8.8",
|
||||||
"vue-i18n": "9"
|
"vue-i18n": "9"
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
"^uv-(.*)": "@climblee/uv-ui/components/uv-$1/uv-$1.vue"
|
"^uv-(.*)": "@climblee/uv-ui/components/uv-$1/uv-$1.vue"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pages": [
|
"pages": [{
|
||||||
{
|
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
"navigationBarTitleText": "",
|
||||||
@@ -16,6 +15,147 @@
|
|||||||
"enableShareTimeline": true
|
"enableShareTimeline": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/legal/agreement",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/legal/agreement-zh",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户协议",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/legal/agreement-en",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "User Agreement",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/legal/privacy",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/legal/privacy-zh",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "隐私政策",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/legal/privacy-en",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "Privacy Policy",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/my/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#D1FFE1",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/userProfile/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#D1FFE1",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/setting/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/deposit/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/order/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/order/payment",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/expressReturn/addExpressReturn",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/feedback/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/feedback/list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/feedback/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/help/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/device/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"path": "pages/serve/bagCheck/index",
|
"path": "pages/serve/bagCheck/index",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -23,15 +163,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/scan/index",
|
"path": "pages/return/index",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
"navigationBarTitleText": "",
|
||||||
"navigationBarBackgroundColor": "#000000",
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
"navigationBarTextStyle": "white"
|
"navigationBarTextStyle": "black"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/device/detail",
|
"path": "pages/order/success",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/order/return-success",
|
||||||
"style": {
|
"style": {
|
||||||
"navigationBarTitleText": "",
|
"navigationBarTitleText": "",
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
@@ -46,6 +194,21 @@
|
|||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/expressReturn/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationStyle": "default"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/expressReturn/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/search/index",
|
"path": "pages/search/index",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -54,6 +217,22 @@
|
|||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/position/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#D1FFE1",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/join/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
|
"navigationBarTextStyle": "black"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/waiting/index",
|
"path": "pages/waiting/index",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -61,319 +240,6 @@
|
|||||||
"navigationBarBackgroundColor": "#ffffff",
|
"navigationBarBackgroundColor": "#ffffff",
|
||||||
"navigationBarTextStyle": "black"
|
"navigationBarTextStyle": "black"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "toProgram",
|
|
||||||
"style": {
|
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"subPackages": [
|
|
||||||
{
|
|
||||||
"root": "subPackages/user",
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"path": "login/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#C8F4D9",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "login/phone",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#C8F4D9",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "my/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#D1FFE1",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "userProfile/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#D1FFE1",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "setting/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "user/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"root": "subPackages/order",
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"path": "index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "payment",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "success",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "return-success",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "return-map",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#000000",
|
|
||||||
"navigationBarTextStyle": "white"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"root": "subPackages/service",
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"path": "feedback/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "feedback/list",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "feedback/detail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "help/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "expressReturn/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationStyle": "default"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "expressReturn/addExpressReturn",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "expressReturn/detail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "return/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"root": "subPackages/business",
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"path": "purchase/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "优惠专区",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "my-card",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "my-coupon",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "position/detail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#D1FFE1",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "device-goods",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "device-orderList",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "device-orderDetail",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "定制详情",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"root": "subPackages/other",
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"path": "legal/agreement",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "legal/agreement-zh",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "用户协议",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "legal/agreement-en",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "User Agreement",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "legal/privacy",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "legal/privacy-zh",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "隐私政策",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "legal/privacy-en",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "Privacy Policy",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "legal/terms",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "join/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "webview/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": "",
|
|
||||||
"navigationBarBackgroundColor": "#ffffff",
|
|
||||||
"navigationBarTextStyle": "black",
|
|
||||||
"navigationStyle": "custom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "serve/bagCheck/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "deposit/index",
|
|
||||||
"style": {
|
|
||||||
"navigationBarTitleText": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
|
|||||||
@@ -0,0 +1,341 @@
|
|||||||
|
<template>
|
||||||
|
<view class="deposit-container">
|
||||||
|
<!-- 押金金额卡片 -->
|
||||||
|
<view class="deposit-card">
|
||||||
|
<view class="title">{{ $t('deposit.depositBalance') }}</view>
|
||||||
|
<view class="amount">¥{{ depositAmount }}</view>
|
||||||
|
<button class="withdraw-btn" @click="handleWithdraw" :disabled="depositAmount <= 0">{{ $t('deposit.withdraw') }}</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 提现说明 -->
|
||||||
|
<view class="notice-card">
|
||||||
|
<view class="notice-title">
|
||||||
|
<view class="dot"></view>
|
||||||
|
<text>{{ $t('deposit.withdrawNotice') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="notice-content">
|
||||||
|
<view class="notice-item">1. {{ $t('deposit.withdrawNotice1') }}</view>
|
||||||
|
<view class="notice-item">2. {{ $t('deposit.withdrawNotice2') }}</view>
|
||||||
|
<view class="notice-item">3. {{ $t('deposit.withdrawNotice3') }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 押金记录 -->
|
||||||
|
<view class="record-card" v-if="records.length > 0">
|
||||||
|
<view class="record-title">{{ $t('deposit.depositRecord') }}</view>
|
||||||
|
<view class="record-list">
|
||||||
|
<view class="record-item" v-for="(item, index) in records" :key="index">
|
||||||
|
<view class="record-info">
|
||||||
|
<text class="record-type">{{ item.type }}</text>
|
||||||
|
<text class="record-time">{{ item.time }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="record-amount" :class="item.type === '退还' ? 'refund' : ''">
|
||||||
|
{{ item.type === '退还' ? '+' : '-' }}¥{{ item.amount }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getUserInfo } from '../../util/index.js'
|
||||||
|
import { withdrawDeposit } from '../../config/api/user.js'
|
||||||
|
import { queryById } from '../../config/api/order.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
depositAmount: '0.00',
|
||||||
|
orderNo: '',
|
||||||
|
records: [],
|
||||||
|
orderId:''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// 设置页面标题
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: this.$t('deposit.title')
|
||||||
|
})
|
||||||
|
// this.loadUserInfo()
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.loadUserInfo()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadUserInfo() {
|
||||||
|
try {
|
||||||
|
const res = await getUserInfo()
|
||||||
|
console.log('loadUserInfo',res);
|
||||||
|
if (res.code === 200) {
|
||||||
|
this.depositAmount = res.data.balanceAmount || '0.00'
|
||||||
|
this.orderNo = res.data.latestOrderNo || ''
|
||||||
|
this.orderId = res.data.latestOrderId||''
|
||||||
|
|
||||||
|
// 如果存在余额,获取押金记录
|
||||||
|
if (parseFloat(this.depositAmount) > 0 && this.orderNo) {
|
||||||
|
this.records = [
|
||||||
|
{
|
||||||
|
type: '支付',
|
||||||
|
time: this.formatDate(new Date()),
|
||||||
|
amount: this.depositAmount
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
this.records = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户信息失败:', error)
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('user.getUserInfoFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleWithdraw() {
|
||||||
|
if (parseFloat(this.depositAmount) <= 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('deposit.noBalance'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(this.orderId.length!=0||this.orderNo.length!=0){
|
||||||
|
const res = await queryById(Number(this.orderId))
|
||||||
|
console.log(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if(this.orderNo.length!=0){
|
||||||
|
// uni.showToast({
|
||||||
|
// title:'当前存在进行中的订单',
|
||||||
|
// icon:'none'
|
||||||
|
// })
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
uni.showModal({
|
||||||
|
title: this.$t('deposit.confirmWithdraw'),
|
||||||
|
content: this.$t('deposit.withdrawDesc'),
|
||||||
|
success: async (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.showLoading({
|
||||||
|
title: this.$t('deposit.withdrawing')
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('发起提现请求,订单号:', this.orderNo)
|
||||||
|
const result = await withdrawDeposit(this.orderNo)
|
||||||
|
console.log('提现响应:', result)
|
||||||
|
|
||||||
|
if (result.code === 200) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('deposit.withdrawSubmitted'),
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 更新余额为0
|
||||||
|
this.depositAmount = '0.00'
|
||||||
|
this.records.push({
|
||||||
|
type: '退还',
|
||||||
|
time: this.formatDate(new Date()),
|
||||||
|
amount: this.depositAmount
|
||||||
|
})
|
||||||
|
|
||||||
|
// 重新加载用户信息
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadUserInfo()
|
||||||
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
throw new Error(result.msg || this.$t('deposit.withdrawFailed'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提现失败:', error)
|
||||||
|
uni.hideLoading()
|
||||||
|
|
||||||
|
// 更详细的错误处理
|
||||||
|
let errorMessage = this.$t('deposit.withdrawFailed');
|
||||||
|
|
||||||
|
// 如果有具体错误信息,使用它
|
||||||
|
if (error.message) {
|
||||||
|
// 常见错误消息处理
|
||||||
|
if (error.message.includes('尚未归还')) {
|
||||||
|
errorMessage = this.$t('deposit.orderNotReturned');
|
||||||
|
} else if (error.message.includes('已退还')) {
|
||||||
|
errorMessage = this.$t('deposit.alreadyRefunded');
|
||||||
|
} else if (error.message.includes('处理中')) {
|
||||||
|
errorMessage = this.$t('deposit.refundProcessing');
|
||||||
|
} else if (error.message.includes('余额为0')) {
|
||||||
|
errorMessage = this.$t('deposit.noBalance');
|
||||||
|
} else {
|
||||||
|
// 使用后端返回的具体错误消息
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示错误提示
|
||||||
|
uni.showModal({
|
||||||
|
title: this.$t('deposit.withdrawFailed'),
|
||||||
|
content: errorMessage,
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
formatDate(date) {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||||
|
const day = date.getDate().toString().padStart(2, '0')
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0')
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||||||
|
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.deposit-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8f8f8;
|
||||||
|
padding: 30rpx;
|
||||||
|
|
||||||
|
.deposit-card {
|
||||||
|
background: linear-gradient(135deg, #1976D2, #64B5F6);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 40rpx;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(25,118,210,0.2);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-size: 72rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.withdraw-btn {
|
||||||
|
background: #fff;
|
||||||
|
color: #1976D2;
|
||||||
|
width: 80%;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
background: rgba(255,255,255,0.6);
|
||||||
|
color: rgba(25,118,210,0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-card {
|
||||||
|
margin-top: 30rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||||
|
|
||||||
|
.notice-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
background: #1976D2;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice-content {
|
||||||
|
.notice-item {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
padding-left: 22rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-card {
|
||||||
|
margin-top: 30rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||||
|
|
||||||
|
.record-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
border-left: 8rpx solid #1976D2;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-list {
|
||||||
|
.record-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-info {
|
||||||
|
.record-type {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-amount {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.refund {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,25 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<!-- 骨架屏 -->
|
|
||||||
<DeviceDetailSkeleton v-if="loading&&!deviceInfo" />
|
|
||||||
|
|
||||||
<!-- 实际内容 -->
|
|
||||||
<view v-else>
|
|
||||||
<!-- 设备信息卡片 -->
|
<!-- 设备信息卡片 -->
|
||||||
<view class="card device-info-card">
|
<view class="card device-info-card">
|
||||||
<view class="device-location">
|
<view class="device-location">
|
||||||
<view class="location-left">
|
<view class="location-left">
|
||||||
<image src="/static/device_location.png" mode="aspectFit" class="location-icon" lazy-load="true"></image>
|
<image src="/static/images/location-map.svg" mode="aspectFit" class="location-icon"></image>
|
||||||
<text class="location-name">{{ deviceLocation }}</text>
|
<text class="location-name">{{ deviceLocation }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="device-status" :class="deviceStatus.class">
|
<view class="device-status" :class="deviceStatus.class">
|
||||||
<text class="status-text">{{ deviceStatus.text }}</text>
|
<text class="status-text">{{ deviceStatus.text }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="device-id">
|
|
||||||
<text class="id-label">{{ $t('order.deviceName') }}:</text>
|
|
||||||
<text class="id-value">{{ deviceInfo.name }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="device-id">
|
<view class="device-id">
|
||||||
<text class="id-label">{{ $t('device.deviceNo') }}:</text>
|
<text class="id-label">{{ $t('device.deviceNo') }}:</text>
|
||||||
<text class="id-value">{{ deviceId }}</text>
|
<text class="id-value">{{ deviceId }}</text>
|
||||||
@@ -32,16 +23,16 @@
|
|||||||
<text class="card-title">{{ $t('device.pricingRules') }}</text>
|
<text class="card-title">{{ $t('device.pricingRules') }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="pricing-banner">
|
<view class="pricing-banner">
|
||||||
<view class="pricing-main">
|
<view class="pricing-main">
|
||||||
<text class="price-symbol">{{ displayCurrencySymbol }}</text>
|
<text class="price-symbol">¥</text>
|
||||||
<text class="price">{{ displayHourlyPrice }}</text>
|
<text class="price">{{ deviceFeeConfig.maxHourPrice || '5.00' }}</text>
|
||||||
<text class="unit">/{{ getPriceUnit() }}</text>
|
<text class="unit">/{{ getPriceUnit() }}</text>
|
||||||
</view>
|
|
||||||
<view class="cap-badge">
|
|
||||||
<text class="cap-text">{{ displayDepositCap }}{{ $t('device.capLimit') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="cap-badge">
|
||||||
|
<text class="cap-text">{{ deviceInfo.depositAmount || '99' }}{{ $t('device.capLimit') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="pricing-info">
|
<view class="pricing-info">
|
||||||
<view class="info-icon">
|
<view class="info-icon">
|
||||||
@@ -78,26 +69,15 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="promotion-tip" @click="goToPurchase">
|
|
||||||
<view class="tip-left">
|
|
||||||
<text class="tip-text">{{ $t('device.canUsePromotion') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="tip-right">
|
|
||||||
<text class="buy-text">{{ $t('device.goToBuy') }}</text>
|
|
||||||
<image src="/static/gotoBuy.png" mode="aspectFit" class="arrow-icon"></image>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部操作区 -->
|
<!-- 底部操作区 -->
|
||||||
<view class="footer">
|
<view class="footer">
|
||||||
<view class="rent-button" :class="{ 'return-button': hasActiveOrder }"
|
<button class="rent-button" :class="{ 'return-button': hasActiveOrder }"
|
||||||
@click="handleRent">
|
@click="handleRent('alipay-score-pay')">
|
||||||
<text>{{ hasActiveOrder ? $t('order.returnDevice') : getRentButtonText() }}</text>
|
<text>{{ hasActiveOrder ? $t('order.returnDevice') : $t('device.rentDepositFree') }}</text>
|
||||||
</view>
|
</button>
|
||||||
<!-- 微信支付分标识仅在微信小程序环境显示 -->
|
<view class="alipay-credit">
|
||||||
<view class="wechat-credit" v-if="isWechatMiniProgram">
|
<image src="/static/images/alipay.svg" mode="aspectFit" class="alipay-icon"></image>
|
||||||
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="wx-icon" lazy-load="true"></image>
|
<text class="credit-text">{{ $t('device.alipayScoreDesc') }}</text>
|
||||||
<text class="credit-text">{{ $t('device.wxPayScoreDesc') }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -121,7 +101,6 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -129,12 +108,10 @@
|
|||||||
import {
|
import {
|
||||||
ref,
|
ref,
|
||||||
reactive,
|
reactive,
|
||||||
computed,
|
|
||||||
onMounted
|
onMounted
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import {
|
import {
|
||||||
onLoad,
|
onLoad
|
||||||
onUnload
|
|
||||||
} from '@dcloudio/uni-app'
|
} from '@dcloudio/uni-app'
|
||||||
import {
|
import {
|
||||||
getDeviceInfo,
|
getDeviceInfo,
|
||||||
@@ -143,27 +120,22 @@
|
|||||||
import {
|
import {
|
||||||
getOrderByOrderNoScore,
|
getOrderByOrderNoScore,
|
||||||
getOrderByOrderNo,
|
getOrderByOrderNo,
|
||||||
cancelOrder,
|
cancelOrder
|
||||||
getInUseOrder,
|
|
||||||
getUnpaidOrder
|
|
||||||
} from '@/config/api/order.js'
|
} from '@/config/api/order.js'
|
||||||
import {
|
import {
|
||||||
initiateWeChatScorePayment,
|
initiateAlipayPayment,
|
||||||
getUserInfo,
|
getUserInfo,
|
||||||
getUserPhoneNumber
|
getUserPhoneNumber
|
||||||
} from '@/util/index.js'
|
} from '@/util/index.js'
|
||||||
import {
|
import {
|
||||||
useI18n,
|
useI18n
|
||||||
showModalI18n
|
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
import DeviceDetailSkeleton from '@/components/DeviceDetailSkeleton.vue'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
t
|
t: $t
|
||||||
} = useI18n()
|
} = useI18n()
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const loading = ref(true)
|
|
||||||
const deviceInfo = ref({})
|
const deviceInfo = ref({})
|
||||||
const deviceId = ref('')
|
const deviceId = ref('')
|
||||||
const deviceFeeConfig = ref({})
|
const deviceFeeConfig = ref({})
|
||||||
@@ -171,37 +143,15 @@
|
|||||||
const deviceLocation = ref('一号教学楼大厅')
|
const deviceLocation = ref('一号教学楼大厅')
|
||||||
const hasActiveOrder = ref(false)
|
const hasActiveOrder = ref(false)
|
||||||
const deviceStatus = reactive({
|
const deviceStatus = reactive({
|
||||||
text: t('device.available'),
|
text: $t('device.available'),
|
||||||
class: 'available'
|
class: 'available'
|
||||||
})
|
})
|
||||||
const isLoggedIn = ref(true)
|
const isLoggedIn = ref(true)
|
||||||
const phoneNumber = ref('')
|
const phoneNumber = ref('')
|
||||||
const showPhoneAuthPopup = ref(false)
|
const showPhoneAuthPopup = ref(false)
|
||||||
const isWechatMiniProgram = ref(false)
|
|
||||||
const isAlipayMiniProgram = ref(false)
|
|
||||||
const isH5 = ref(false)
|
|
||||||
|
|
||||||
// 生命周期 onLoad 钩子
|
// 生命周期 onLoad 钩子
|
||||||
onLoad(async (options) => {
|
onLoad(async (options) => {
|
||||||
// 普通链接二维码进入时,参数通常在 options.q(且为编码后的完整 URL)
|
|
||||||
if (!options.deviceNo && options.q) {
|
|
||||||
const fullUrl = decodeURIComponent(options.q)
|
|
||||||
const queryStr = fullUrl.includes('?') ? fullUrl.split('?')[1] : ''
|
|
||||||
if (queryStr) {
|
|
||||||
const params = queryStr.split('&').reduce((acc, pair) => {
|
|
||||||
if (!pair) return acc
|
|
||||||
const idx = pair.indexOf('=')
|
|
||||||
const rawKey = idx >= 0 ? pair.slice(0, idx) : pair
|
|
||||||
const rawVal = idx >= 0 ? pair.slice(idx + 1) : ''
|
|
||||||
const key = decodeURIComponent(rawKey || '').trim()
|
|
||||||
const val = decodeURIComponent(rawVal || '').trim()
|
|
||||||
if (key) acc[key] = val
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
if (params.deviceNo) options.deviceNo = params.deviceNo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.deviceNo != uni.getStorageSync('deviceId') || !uni.getStorageSync('deviceId')) {
|
if (options.deviceNo != uni.getStorageSync('deviceId') || !uni.getStorageSync('deviceId')) {
|
||||||
deviceId.value = options.deviceNo
|
deviceId.value = options.deviceNo
|
||||||
uni.setStorageSync('deviceId', options.deviceNo)
|
uni.setStorageSync('deviceId', options.deviceNo)
|
||||||
@@ -214,43 +164,12 @@
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('device.deviceInfo')
|
title: $t('device.deviceInfo')
|
||||||
})
|
})
|
||||||
// 检测当前运行环境:微信小程序 / 支付宝小程序 / H5
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
isWechatMiniProgram.value = true
|
|
||||||
isAlipayMiniProgram.value = false
|
|
||||||
isH5.value = false
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
isWechatMiniProgram.value = false
|
|
||||||
isAlipayMiniProgram.value = true
|
|
||||||
isH5.value = false
|
|
||||||
// #endif
|
|
||||||
// #ifdef H5
|
|
||||||
isWechatMiniProgram.value = false
|
|
||||||
isAlipayMiniProgram.value = false
|
|
||||||
isH5.value = true
|
|
||||||
// #endif
|
|
||||||
await checkUserPhone()
|
await checkUserPhone()
|
||||||
await fetchDeviceInfo()
|
await fetchDeviceInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 页面卸载时设置默认启动路径为首页(仅在非下单流程时生效)
|
|
||||||
onUnload(() => {
|
|
||||||
// 如果是下单流程跳转(在提交订单时设置了标记),则本次不设置启动路径
|
|
||||||
const skipSetLaunchPathOnce = uni.getStorageSync('skipSetLaunchPathOnce')
|
|
||||||
if (skipSetLaunchPathOnce) {
|
|
||||||
console.log('下单流程离开设备详情页,本次不设置启动路径')
|
|
||||||
uni.removeStorageSync('skipSetLaunchPathOnce')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 正常离开设备详情页(比如返回、关闭小程序)时,记录启动路径为首页
|
|
||||||
uni.setStorageSync('launchPath', '/pages/index/index')
|
|
||||||
console.log('设备详情页卸载,已设置启动路径为首页')
|
|
||||||
})
|
|
||||||
|
|
||||||
const checkUserPhone = async () => {
|
const checkUserPhone = async () => {
|
||||||
try {
|
try {
|
||||||
const userInfoRes = await getUserInfo()
|
const userInfoRes = await getUserInfo()
|
||||||
@@ -274,7 +193,7 @@
|
|||||||
// 用户拒绝授权的情况
|
// 用户拒绝授权的情况
|
||||||
if (e.detail.errMsg && e.detail.errMsg.includes('deny')) {
|
if (e.detail.errMsg && e.detail.errMsg.includes('deny')) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('auth.phoneRequired'),
|
title: $t('auth.phoneRequired'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -282,9 +201,9 @@
|
|||||||
|
|
||||||
// 获取到授权code
|
// 获取到授权code
|
||||||
if (e.detail.code) {
|
if (e.detail.code) {
|
||||||
// uni.showLoading({
|
uni.showLoading({
|
||||||
// title: t('auth.getting')
|
title: $t('auth.getting')
|
||||||
// })
|
})
|
||||||
|
|
||||||
console.log('获取到的授权code:', e.detail.code)
|
console.log('获取到的授权code:', e.detail.code)
|
||||||
|
|
||||||
@@ -293,12 +212,12 @@
|
|||||||
getUserPhoneNumber(e.detail.code)
|
getUserPhoneNumber(e.detail.code)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
console.log('获取手机号API响应原始数据:', JSON.stringify(res))
|
console.log('获取手机号API响应原始数据:', JSON.stringify(res))
|
||||||
// uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
// 不立即抛出错误,而是记录问题并继续处理
|
// 不立即抛出错误,而是记录问题并继续处理
|
||||||
if (!res) {
|
if (!res) {
|
||||||
console.error('API返回数据为空')
|
console.error('API返回数据为空')
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: '数据异常',
|
title: '数据异常',
|
||||||
content: 'API返回为空',
|
content: 'API返回为空',
|
||||||
showCancel: false
|
showCancel: false
|
||||||
@@ -315,43 +234,43 @@
|
|||||||
showPhoneAuthPopup.value = false
|
showPhoneAuthPopup.value = false
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('auth.phoneSuccess'),
|
title: $t('auth.phoneSuccess'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// 记录详细信息,不抛出错误
|
// 记录详细信息,不抛出错误
|
||||||
console.warn('获取手机号响应异常:', res.msg || '未知错误')
|
console.warn('获取手机号响应异常:', res.msg || '未知错误')
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('auth.phoneError'),
|
title: $t('auth.phoneError'),
|
||||||
content: `${t('common.statusCode')}: ${res.code}, ${t('common.message')}: ${res.msg || t('common.none')}`,
|
content: `${$t('common.statusCode')}: ${res.code}, ${$t('common.message')}: ${res.msg || $t('common.none')}`,
|
||||||
showCancel: false
|
showCancel: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
// uni.hideLoading()
|
uni.hideLoading()
|
||||||
console.error('获取手机号码失败(catch):', err)
|
console.error('获取手机号码失败(catch):', err)
|
||||||
|
|
||||||
// 显示更详细的错误信息
|
// 显示更详细的错误信息
|
||||||
let errMsg = err.message || err.toString()
|
let errMsg = err.message || err.toString()
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('auth.phoneGetFailed'),
|
title: $t('auth.phoneGetFailed'),
|
||||||
content: t('common.errorInfo') + ': ' + errMsg,
|
content: $t('common.errorInfo') + ': ' + errMsg,
|
||||||
showCancel: false
|
showCancel: false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} catch (outerError) {
|
} catch (outerError) {
|
||||||
// uni.hideLoading()
|
uni.hideLoading()
|
||||||
console.error('获取手机号外部错误:', outerError)
|
console.error('获取手机号外部错误:', outerError)
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('common.unexpectedError'),
|
title: $t('common.unexpectedError'),
|
||||||
content: t('common.processException') + ': ' + (outerError.message || outerError),
|
content: $t('common.processException') + ': ' + (outerError.message || outerError),
|
||||||
showCancel: false
|
showCancel: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('auth.authCodeFailed'),
|
title: $t('auth.authCodeFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -359,117 +278,95 @@
|
|||||||
|
|
||||||
// 检查登录状态和订单
|
// 检查登录状态和订单
|
||||||
const fetchDeviceInfo = async () => {
|
const fetchDeviceInfo = async () => {
|
||||||
try {
|
const res = await getDeviceInfo(deviceId.value)
|
||||||
loading.value = true
|
if (res.code == 200) {
|
||||||
// console.log(deviceId.value);
|
deviceInfo.value = res.data.device || {}
|
||||||
const res = await getDeviceInfo(deviceId.value)
|
|
||||||
if (res.code == 200) {
|
|
||||||
deviceInfo.value = res.data.device || {}
|
|
||||||
|
|
||||||
// 保存 position 信息
|
// 保存 position 信息
|
||||||
if (res.data.position) {
|
if (res.data.position) {
|
||||||
positionInfo.value = res.data.position
|
positionInfo.value = res.data.position
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新设备位置信息
|
// 更新设备位置信息
|
||||||
if (deviceInfo.value.deviceLocation) {
|
if (deviceInfo.value.deviceLocation) {
|
||||||
deviceLocation.value = deviceInfo.value.deviceLocation
|
deviceLocation.value = deviceInfo.value.deviceLocation
|
||||||
} else if (res.data.position && res.data.position.name) {
|
} else if (res.data.position && res.data.position.name) {
|
||||||
deviceLocation.value = res.data.position.name
|
deviceLocation.value = res.data.position.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 更新设备状态
|
// 更新设备状态
|
||||||
if (deviceInfo.value.status) {
|
if (deviceInfo.value.status) {
|
||||||
if (deviceInfo.value.status === 'online') {
|
if (deviceInfo.value.status === 'online') {
|
||||||
deviceStatus.text = t('device.available')
|
deviceStatus.text = $t('device.available')
|
||||||
deviceStatus.class = 'available'
|
deviceStatus.class = 'available'
|
||||||
} else if (deviceInfo.value.status === 'offline') {
|
} else if (deviceInfo.value.status === 'offline') {
|
||||||
deviceStatus.text = t('device.offline')
|
deviceStatus.text = $t('device.offline')
|
||||||
deviceStatus.class = 'offline'
|
deviceStatus.class = 'offline'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceInfo.value.feeConfig) {
|
if (deviceInfo.value.feeConfig) {
|
||||||
deviceFeeConfig.value = JSON.parse(deviceInfo.value.feeConfig)[0] || {}
|
deviceFeeConfig.value = JSON.parse(deviceInfo.value.feeConfig)[0] || {}
|
||||||
console.log('deviceFeeConfig', deviceFeeConfig.value);
|
console.log('deviceFeeConfig', deviceFeeConfig.value);
|
||||||
} else {
|
} else {
|
||||||
deviceFeeConfig.value = {
|
deviceFeeConfig.value = {
|
||||||
maxHourPrice: '5.00',
|
maxHourPrice: '5.00',
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
|
|
||||||
// uni.reLaunch({
|
|
||||||
// url:'/pages/index/index'
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
}catch(error){
|
|
||||||
console.error('获取设备信息失败:', error)
|
|
||||||
}
|
}
|
||||||
finally {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示登录提示
|
// 显示登录提示
|
||||||
const showLoginTip = () => {
|
const showLoginTip = () => {
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('common.tips'),
|
title: $t('common.tips'),
|
||||||
content: t('common.loginRequired'),
|
content: $t('common.loginRequired'),
|
||||||
confirmText: t('auth.goToLogin'),
|
confirmText: $t('auth.goToLogin'),
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/subPackages/user/login/index'
|
url: '/pages/login/index'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到优惠专区
|
|
||||||
const goToPurchase = () => {
|
|
||||||
const positionId = positionInfo.value?.positionId || positionInfo.value?.id
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/subPackages/business/purchase/index?positionId=${positionId}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查订单状态
|
// 检查订单状态
|
||||||
const checkOrderStatus = async () => {
|
const checkOrderStatus = async () => {
|
||||||
try {
|
try {
|
||||||
// 调用接口检查是否有进行中的订单
|
// 调用接口检查是否有进行中的订单
|
||||||
const inUseRes = await getInUseOrder()
|
const result = await uni.$api.checkActiveOrder()
|
||||||
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
|
|
||||||
const order = inUseRes.data
|
|
||||||
// 如果有正在进行的订单,跳转到归还页面,带上设备ID
|
|
||||||
uni.redirectTo({
|
|
||||||
url: `/pages/order/detail?orderId=${order.orderId}`
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有待支付的订单
|
if (result.hasOrder) {
|
||||||
const unpaidRes = await getUnpaidOrder()
|
const order = result.order // 假设后端返回 order 对象
|
||||||
if (unpaidRes && unpaidRes.code === 200 && unpaidRes.data) {
|
|
||||||
const order = unpaidRes.data
|
// 检查订单状态
|
||||||
// 跳转支付页面,带上订单ID
|
if (order.status === 'waiting_for_payment') {
|
||||||
uni.redirectTo({
|
// 跳转支付页面,带上订单ID
|
||||||
url: `/pages/order/payment?orderId=${order.orderId}&deviceId=${deviceId.value}`
|
uni.redirectTo({
|
||||||
})
|
url: `/pages/order/payment?orderId=${order.orderId}&deviceId=${deviceId.value}`
|
||||||
|
})
|
||||||
|
} else if (order.status === 'in_used') {
|
||||||
|
// 如果有正在进行的订单,跳转到归还页面,带上设备ID
|
||||||
|
uni.redirectTo({
|
||||||
|
url: `/pages/device/return?deviceId=${deviceId.value}`
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('检查订单状态失败:', error)
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.getOrderStatusFailed'),
|
title: $t('order.getOrderStatusFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理租借操作
|
// 处理租借操作
|
||||||
const handleRent = () => {
|
const handleRent = (payWay) => {
|
||||||
if (!isLoggedIn.value) {
|
if (!isLoggedIn.value) {
|
||||||
showLoginTip()
|
showLoginTip()
|
||||||
return
|
return
|
||||||
@@ -481,35 +378,21 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据运行环境选择不同的租借/支付流程
|
// 提交订单
|
||||||
// 微信小程序:走微信支付分免押租借
|
submitRentOrder(payWay)
|
||||||
if (isWechatMiniProgram.value) {
|
|
||||||
submitRentOrder('wx-score-pay')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 支付宝小程序:走押金租借,后续在支付页内调起支付宝支付
|
|
||||||
if (isAlipayMiniProgram.value) {
|
|
||||||
submitRentOrder('wx-pay')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// H5 等其他环境:统一走押金租借,支付页内根据平台选择支付方式(Antom 等)
|
|
||||||
submitRentOrder('wx-pay')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取价格单位文本
|
// 获取价格单位文本
|
||||||
const getPriceUnit = () => {
|
const getPriceUnit = () => {
|
||||||
if (isIdrCurrency.value) return t('time.hour')
|
|
||||||
console.log(deviceInfo.value);
|
console.log(deviceInfo.value);
|
||||||
// 按分钟计费
|
// 按分钟计费
|
||||||
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
|
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
|
||||||
return '分钟'
|
return '分钟'
|
||||||
} else if (deviceInfo.value && deviceFeeConfig.value.hourPrice == '0.5') {
|
}else if(deviceInfo.value && deviceFeeConfig.value.hourPrice == '0.5'){
|
||||||
return '30分钟'
|
return '30分钟'
|
||||||
}
|
}
|
||||||
// 按小时计费(默认)
|
// 按小时计费(默认)
|
||||||
return t('time.hour')
|
return $t('time.hour')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算计费单位时间(分钟)
|
// 计算计费单位时间(分钟)
|
||||||
@@ -549,9 +432,6 @@
|
|||||||
|
|
||||||
// 生成计费说明文本
|
// 生成计费说明文本
|
||||||
const getPricingInfoText = () => {
|
const getPricingInfoText = () => {
|
||||||
if (isIdrCurrency.value) {
|
|
||||||
return `${displayCurrencySymbol.value}${displayHourlyPrice.value}/${t('time.hour')}`
|
|
||||||
}
|
|
||||||
const unitPrice = getBillingUnitPrice()
|
const unitPrice = getBillingUnitPrice()
|
||||||
const maxHourPrice = deviceFeeConfig.value.maxHourPrice || '5'
|
const maxHourPrice = deviceFeeConfig.value.maxHourPrice || '5'
|
||||||
|
|
||||||
@@ -567,99 +447,40 @@
|
|||||||
|
|
||||||
// 生成详细说明文本
|
// 生成详细说明文本
|
||||||
const getDetailInfoText = () => {
|
const getDetailInfoText = () => {
|
||||||
if (isIdrCurrency.value) {
|
const freeMinutes = getFreeMinutes()
|
||||||
const cap = `${displayCurrencySymbol.value}${displayDepositCap.value}`
|
|
||||||
return t('device.detailBillingIdr', {
|
|
||||||
hour: t('time.hour'),
|
|
||||||
cap
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const unitMinutes = getBillingUnitMinutes()
|
const unitMinutes = getBillingUnitMinutes()
|
||||||
const depositAmount = deviceInfo.value.depositAmount || '99'
|
const depositAmount = deviceInfo.value.depositAmount || '99'
|
||||||
return t('device.detailBillingByUnit', {
|
|
||||||
unit: unitMinutes,
|
|
||||||
minute: t('time.minute'),
|
|
||||||
cap: depositAmount
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取租借按钮文本
|
// 按分钟计费
|
||||||
const getRentButtonText = () => {
|
if (deviceInfo.value && deviceInfo.value.feeType === 'minute') {
|
||||||
if (isWechatMiniProgram.value) {
|
return `不足${unitMinutes}分钟按${unitMinutes}分钟计费,封顶${depositAmount}元,持续计费至${depositAmount}元视为买断`
|
||||||
return t('device.rentDepositFree')
|
|
||||||
} else {
|
|
||||||
return t('device.rentNow')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按小时计费
|
||||||
|
return `不足${unitMinutes}分钟按${unitMinutes}分钟计费,封顶${depositAmount}元,持续计费至${depositAmount}元视为买断`
|
||||||
}
|
}
|
||||||
|
|
||||||
const currencyCode = computed(() => {
|
|
||||||
return (positionInfo.value?.currency || '').toUpperCase()
|
|
||||||
})
|
|
||||||
|
|
||||||
const isIdrCurrency = computed(() => currencyCode.value === 'IDR')
|
|
||||||
|
|
||||||
const displayCurrencySymbol = computed(() => (isIdrCurrency.value ? 'Rp ' : '¥'))
|
|
||||||
|
|
||||||
const displayHourlyPrice = computed(() => {
|
|
||||||
return deviceFeeConfig.value.maxHourPrice || '5.00'
|
|
||||||
})
|
|
||||||
|
|
||||||
const displayDepositCap = computed(() => {
|
|
||||||
return deviceInfo.value.depositAmount || '99'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 提交租借订单
|
// 提交租借订单
|
||||||
const submitRentOrder = async (payWay) => {
|
const submitRentOrder = async (payWay) => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.processing')
|
title: $t('common.processing')
|
||||||
})
|
})
|
||||||
// --- 第一步:先请求订阅消息(必须在用户点击的同步上下文中)---
|
// --- 支付宝小程序不需要订阅消息,移除相关代码 ---
|
||||||
if (payWay === 'wx-score-pay') {
|
// 支付宝小程序使用消息推送,不需要订阅消息
|
||||||
console.log('准备请求订阅消息(在异步操作之前),时间:', new Date().toLocaleTimeString());
|
|
||||||
try {
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
uni.requestSubscribeMessage({
|
|
||||||
tmplIds: ['o7OMTIcHnFBR7mvsggxFtdt8FfIgSl-v0swVUefGx6w'],
|
|
||||||
success: (subscribeRes) => {
|
|
||||||
console.log('订阅消息success回调,时间:', new Date()
|
|
||||||
.toLocaleTimeString(), subscribeRes);
|
|
||||||
resolve(subscribeRes);
|
|
||||||
},
|
|
||||||
fail: (subscribeErr) => {
|
|
||||||
console.log('订阅消息fail回调,时间:', new Date().toLocaleTimeString(),
|
|
||||||
subscribeErr);
|
|
||||||
// 订阅失败不影响主流程
|
|
||||||
resolve(subscribeErr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
console.log('订阅消息完成,时间:', new Date().toLocaleTimeString());
|
|
||||||
} catch (subscribeError) {
|
|
||||||
console.log('订阅消息异常', subscribeError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// --- 订阅消息请求完成 ---
|
|
||||||
|
|
||||||
console.log(deviceId.value);
|
console.log(deviceId.value);
|
||||||
// 调用设备租借接口
|
// 调用设备租借接口
|
||||||
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value,payWay)
|
const rentResult = await rentPowerBank(deviceId.value, phoneNumber.value)
|
||||||
if (rentResult.code !== 200) {
|
if (rentResult.code !== 200) {
|
||||||
throw new Error(rentResult.msg || t('device.rentFailed'))
|
throw new Error(rentResult.msg || $t('device.rentFailed'))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取后端返回的订单信息
|
// 获取后端返回的订单信息
|
||||||
const order = rentResult.data
|
const order = rentResult.data
|
||||||
console.log('订单信息', order);
|
console.log('订单信息', order);
|
||||||
|
|
||||||
// 标记:本次是从设备详情页发起的下单流程,离开页面时不设置启动路径
|
if (payWay == 'alipay-pay') {
|
||||||
try {
|
|
||||||
uni.setStorageSync('skipSetLaunchPathOnce', true)
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('设置 skipSetLaunchPathOnce 失败:', e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payWay == 'wx-pay') {
|
|
||||||
// 当支付方式为押金支付时
|
// 当支付方式为押金支付时
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
const res = await getOrderByOrderNo(order.orderNo);
|
const res = await getOrderByOrderNo(order.orderNo);
|
||||||
@@ -671,36 +492,35 @@
|
|||||||
|
|
||||||
// 跳转到订单支付页面
|
// 跳转到订单支付页面
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/subPackages/order/payment?orderId=${order.orderId}&packagePrice=${packagePrice}&totalAmount=${totalAmount}&depositAmount=${deposit}${deviceInfo.value && deviceInfo.value.feeConfig ? '&feeConfig=' + encodeURIComponent(deviceInfo.value.feeConfig) : ''}`
|
url: `/pages/order/payment?orderId=${order.orderId}&packagePrice=${packagePrice}&totalAmount=${totalAmount}&depositAmount=${deposit}${deviceInfo.value && deviceInfo.value.feeConfig ? '&feeConfig=' + encodeURIComponent(deviceInfo.value.feeConfig) : ''}`
|
||||||
})
|
})
|
||||||
|
|
||||||
} else if (payWay == 'wx-score-pay') {
|
} else if (payWay == 'alipay-score-pay') {
|
||||||
// 当支付方式为支付分支付时
|
// 当支付方式为支付宝信用免押支付时
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
// 获取支付分所需参数
|
// 获取支付宝信用免押所需参数
|
||||||
const res = await getOrderByOrderNoScore(order.orderNo);
|
const res = await getOrderByOrderNoScore(order.orderNo);
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
|
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
try {
|
try {
|
||||||
// 调用微信支付分小程序
|
// 调用支付宝信用免押小程序
|
||||||
const payResult = await initiateWeChatScorePayment(res);
|
const payResult = await initiateAlipayPayment(res);
|
||||||
console.log('支付分调用结果', payResult);
|
console.log('支付宝信用免押调用结果', payResult);
|
||||||
// 成功则跳转到等待页面
|
// 成功则跳转到等待页面
|
||||||
if (payResult.errCode == '0' && payResult.extraData && Object.keys(payResult.extraData)
|
if (payResult && payResult.success !== false) {
|
||||||
.length > 0) {
|
console.log('支付宝信用免押授权成功,准备跳转到等待页,时间:', new Date().toLocaleTimeString());
|
||||||
console.log('支付分授权成功,准备跳转到等待页,时间:', new Date().toLocaleTimeString());
|
// 跳转到等待页面
|
||||||
// 跳转到等待页面(订阅消息已经在前面完成了)
|
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/pages/waiting/index?orderNo=${order.orderNo}&orderId=${order.orderId}&deviceId=${deviceId.value}`
|
url: `/pages/waiting/index?orderNo=${order.orderNo}&orderId=${order.orderId}&deviceId=${deviceId.value}`
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
console.log('支付分未完成授权或用户取消,extraData:', payResult.extraData);
|
console.log('支付宝信用免押未完成授权或用户取消:', payResult);
|
||||||
// 用户取消授权,需要取消订单
|
// 用户取消授权,需要取消订单
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('order.cancelling')
|
title: $t('order.cancelling')
|
||||||
});
|
});
|
||||||
const cancelRes = await cancelOrder({
|
const cancelRes = await cancelOrder({
|
||||||
orderId: order.orderNo
|
orderId: order.orderNo
|
||||||
@@ -709,7 +529,7 @@
|
|||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.orderCancelled'),
|
title: $t('order.orderCancelled'),
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
});
|
});
|
||||||
@@ -724,7 +544,7 @@
|
|||||||
console.error('取消订单失败:', cancelError);
|
console.error('取消订单失败:', cancelError);
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.cancelFailedContactService'),
|
title: $t('order.cancelFailedContactService'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -735,7 +555,7 @@
|
|||||||
// 支付分调用异常,也需要取消订单
|
// 支付分调用异常,也需要取消订单
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('order.cancelling')
|
title: $t('order.cancelling')
|
||||||
});
|
});
|
||||||
const cancelRes = await cancelOrder({
|
const cancelRes = await cancelOrder({
|
||||||
orderId: order.orderNo
|
orderId: order.orderNo
|
||||||
@@ -748,7 +568,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('device.payScoreFailedCancelled'),
|
title: $t('device.payScoreFailedCancelled'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -760,7 +580,7 @@
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res?.msg || t('device.getPayParamsFailed'),
|
title: res?.msg || $t('device.getPayParamsFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -768,7 +588,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: error.message || t('device.rentFailedRetry'),
|
title: error.message || $t('device.rentFailedRetry'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -819,15 +639,15 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.location-icon {
|
.location-icon {
|
||||||
width: 32rpx;
|
width: 40rpx;
|
||||||
height: 32rpx;
|
height: 40rpx;
|
||||||
margin-right: 12rpx;
|
margin-right: 12rpx;
|
||||||
// background-color: #10d673;
|
background-color: #10d673;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-name {
|
.location-name {
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
color: #333;
|
color: #333;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -836,7 +656,7 @@
|
|||||||
.device-status {
|
.device-status {
|
||||||
padding: 8rpx 24rpx;
|
padding: 8rpx 24rpx;
|
||||||
border-radius: 30rpx;
|
border-radius: 30rpx;
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
|
|
||||||
&.available {
|
&.available {
|
||||||
background-color: #d4f4dd;
|
background-color: #d4f4dd;
|
||||||
@@ -863,10 +683,9 @@
|
|||||||
.device-id {
|
.device-id {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 5rpx;
|
|
||||||
|
|
||||||
.id-label {
|
.id-label {
|
||||||
font-size: 26rpx;
|
font-size: 28rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -880,7 +699,7 @@
|
|||||||
// 计费规则卡片
|
// 计费规则卡片
|
||||||
.pricing-card {
|
.pricing-card {
|
||||||
.pricing-banner {
|
.pricing-banner {
|
||||||
background: #E6F7EC;
|
background: linear-gradient(135deg, #e8f5e9, #c8e6c9);
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
padding: 40rpx 30rpx;
|
padding: 40rpx 30rpx;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
@@ -894,21 +713,21 @@
|
|||||||
margin-bottom: 16rpx;
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
.price-symbol {
|
.price-symbol {
|
||||||
font-size: 36rpx;
|
font-size: 48rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
margin-right: 4rpx;
|
margin-right: 4rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.price {
|
.price {
|
||||||
font-size: 64rpx;
|
font-size: 80rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unit {
|
.unit {
|
||||||
font-size: 28rpx;
|
font-size: 32rpx;
|
||||||
color: #07c160;
|
color: #07c160;
|
||||||
margin-left: 8rpx;
|
margin-left: 8rpx;
|
||||||
}
|
}
|
||||||
@@ -916,11 +735,11 @@
|
|||||||
|
|
||||||
.cap-badge {
|
.cap-badge {
|
||||||
background-color: #07c160;
|
background-color: #07c160;
|
||||||
padding: 10rpx 28rpx;
|
padding: 10rpx 32rpx;
|
||||||
border-radius: 30rpx;
|
border-radius: 30rpx;
|
||||||
line-height: 1;
|
|
||||||
.cap-text {
|
.cap-text {
|
||||||
font-size: 24rpx;
|
font-size: 26rpx;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -992,51 +811,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 促销提示框
|
|
||||||
.promotion-tip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 20rpx 30rpx;
|
|
||||||
background: rgba(255, 244, 227, 1);
|
|
||||||
border-radius: 22rpx;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.tip-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #A16300;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12rpx;
|
|
||||||
|
|
||||||
.buy-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #A16300;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow-icon {
|
|
||||||
width: 20rpx;
|
|
||||||
height: 20rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部操作区
|
// 底部操作区
|
||||||
.footer {
|
.footer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -1077,22 +851,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.wechat-credit {
|
.alipay-credit {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 16rpx;
|
margin-top: 16rpx;
|
||||||
|
|
||||||
.wx-icon {
|
.alipay-icon {
|
||||||
width: 48rpx;
|
width: 48rpx;
|
||||||
height: 38rpx;
|
height: 38rpx;
|
||||||
margin-right: 8rpx;
|
margin-right: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.credit-text {
|
.credit-text {
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,13 +60,13 @@
|
|||||||
getExpressReturnDetail,
|
getExpressReturnDetail,
|
||||||
fillExpressTrackingNumber
|
fillExpressTrackingNumber
|
||||||
} from '@/config/api/expressReturn.js'
|
} from '@/config/api/expressReturn.js'
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('express.fillExpress')
|
title: $t('express.fillExpress')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
if (!orderId.value) {
|
if (!orderId.value) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('express.orderNoMissing'),
|
title: $t('express.orderNoMissing'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
const loadOrder = async () => {
|
const loadOrder = async () => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.loading')
|
title: $t('common.loading')
|
||||||
})
|
})
|
||||||
const res = await queryById(orderId.value)
|
const res = await queryById(orderId.value)
|
||||||
if (res?.code === 200 && res.data) {
|
if (res?.code === 200 && res.data) {
|
||||||
@@ -121,11 +121,11 @@
|
|||||||
// 默认联系电话可回填订单上的手机号(若有)
|
// 默认联系电话可回填订单上的手机号(若有)
|
||||||
if (res.data.phone && !phone.value) phone.value = res.data.phone
|
if (res.data.phone && !phone.value) phone.value = res.data.phone
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || t('order.getOrderFailed'))
|
throw new Error(res?.msg || $t('order.getOrderFailed'))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: e.message || t('express.loadFailed'),
|
title: e.message || $t('express.loadFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
const loadRecordAndOrderByRecord = async () => {
|
const loadRecordAndOrderByRecord = async () => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.loading')
|
title: $t('common.loading')
|
||||||
})
|
})
|
||||||
const res = await getExpressReturnDetail(recordId.value)
|
const res = await getExpressReturnDetail(recordId.value)
|
||||||
if (res?.code === 200 && res.data) {
|
if (res?.code === 200 && res.data) {
|
||||||
@@ -146,11 +146,11 @@
|
|||||||
}
|
}
|
||||||
if (res.data.userPhone && !phone.value) phone.value = res.data.userPhone
|
if (res.data.userPhone && !phone.value) phone.value = res.data.userPhone
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || t('express.getRecordFailed'))
|
throw new Error(res?.msg || $t('express.getRecordFailed'))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: e.message || t('express.loadFailed'),
|
title: e.message || $t('express.loadFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -165,11 +165,11 @@
|
|||||||
const rec = res.data
|
const rec = res.data
|
||||||
if (rec.status === 0) {
|
if (rec.status === 0) {
|
||||||
recordId.value = rec.id
|
recordId.value = rec.id
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('common.tips'),
|
title: $t('common.tips'),
|
||||||
content: t('express.existingReturnNotice'),
|
content: $t('express.existingReturnNotice'),
|
||||||
confirmText: t('express.goToFill'),
|
confirmText: $t('express.goToFill'),
|
||||||
cancelText: t('common.cancel'),
|
cancelText: $t('common.cancel'),
|
||||||
success: (r) => {
|
success: (r) => {
|
||||||
if (r.confirm) {
|
if (r.confirm) {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('express.alreadyHasRecord'),
|
title: $t('express.alreadyHasRecord'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -201,14 +201,14 @@
|
|||||||
const digits = (phone.value || '').replace(/\D/g, '')
|
const digits = (phone.value || '').replace(/\D/g, '')
|
||||||
if (!digits || digits.length < 5) {
|
if (!digits || digits.length < 5) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('express.pleaseEnterValidPhone'),
|
title: $t('express.pleaseEnterValidPhone'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (isFillMode.value && !trackingNumber.value) {
|
if (isFillMode.value && !trackingNumber.value) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('express.pleaseEnterTrackingNo'),
|
title: $t('express.pleaseEnterTrackingNo'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return false
|
return false
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: isFillMode.value ? t('common.filling') : t('common.submitting')
|
title: isFillMode.value ? $t('common.filling') : $t('common.submitting')
|
||||||
})
|
})
|
||||||
let res
|
let res
|
||||||
if (isFillMode.value) {
|
if (isFillMode.value) {
|
||||||
@@ -238,18 +238,18 @@
|
|||||||
}
|
}
|
||||||
if (res && res.code === 200) {
|
if (res && res.code === 200) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: isFillMode.value ? t('express.fillSuccess') : t('express.submitSuccess'),
|
title: isFillMode.value ? '补填成功' : '提交成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}, 800)
|
}, 800)
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || (isFillMode.value ? t('express.fillFailed') : t('express.submitFailed')))
|
throw new Error(res?.msg || (isFillMode.value ? '补填失败' : '提交失败'))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: e.message || (isFillMode.value ? t('express.fillFailed') : t('express.submitFailed')),
|
title: e.message || (isFillMode.value ? '补填失败' : '提交失败'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -89,9 +89,9 @@
|
|||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { getExpressReturnDetail } from '@/config/api/expressReturn.js'
|
import { getExpressReturnDetail } from '@/config/api/expressReturn.js'
|
||||||
import { getCustomerPhone } from '@/util/index.js'
|
import { getCustomerPhone } from '@/util/index.js'
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
// 详情数据
|
// 详情数据
|
||||||
const detailData = ref({
|
const detailData = ref({
|
||||||
@@ -131,21 +131,21 @@ const getStatusIcon = (status) => {
|
|||||||
// 获取状态文本
|
// 获取状态文本
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const textMap = {
|
const textMap = {
|
||||||
'completed': t('express.returnCompleted'),
|
'completed': $t('express.returnCompleted'),
|
||||||
'processing': t('express.processing'),
|
'processing': $t('express.processing'),
|
||||||
'pending': t('express.pending')
|
'pending': $t('express.pending')
|
||||||
}
|
}
|
||||||
return textMap[status] || t('express.pending')
|
return textMap[status] || $t('express.pending')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取状态描述
|
// 获取状态描述
|
||||||
const getStatusDesc = (status) => {
|
const getStatusDesc = (status) => {
|
||||||
const descMap = {
|
const descMap = {
|
||||||
'completed': t('express.returnCompletedDesc'),
|
'completed': $t('express.returnCompletedDesc'),
|
||||||
'processing': t('express.processingDesc'),
|
'processing': $t('express.processingDesc'),
|
||||||
'pending': t('express.pendingDesc')
|
'pending': $t('express.pendingDesc')
|
||||||
}
|
}
|
||||||
return descMap[status] || t('express.pendingDesc')
|
return descMap[status] || $t('express.pendingDesc')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制运单号
|
// 复制运单号
|
||||||
@@ -154,7 +154,7 @@ const handleCopyTracking = () => {
|
|||||||
data: detailData.value.trackingNumber,
|
data: detailData.value.trackingNumber,
|
||||||
success: () => {
|
success: () => {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('express.trackingNoCopied'),
|
title: $t('express.trackingNoCopied'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -164,11 +164,11 @@ const handleCopyTracking = () => {
|
|||||||
// 联系客服
|
// 联系客服
|
||||||
const handleContactService = () => {
|
const handleContactService = () => {
|
||||||
const customerPhone = getCustomerPhone()
|
const customerPhone = getCustomerPhone()
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('user.customerService'),
|
title: $t('user.customerService'),
|
||||||
content: `${t('help.phone')}:${customerPhone}\n${t('help.workingHours')}:${t('express.workingHours')}`,
|
content: `${$t('help.phone')}:${customerPhone}\n${$t('help.workingHours')}:${$t('express.workingHours')}`,
|
||||||
confirmText: t('express.call'),
|
confirmText: $t('express.call'),
|
||||||
cancelText: t('common.cancel'),
|
cancelText: $t('common.cancel'),
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.makePhoneCall({
|
uni.makePhoneCall({
|
||||||
@@ -182,7 +182,7 @@ const handleContactService = () => {
|
|||||||
// 页面加载时获取详情数据
|
// 页面加载时获取详情数据
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('express.returnDetail')
|
title: $t('express.returnDetail')
|
||||||
})
|
})
|
||||||
|
|
||||||
const pages = getCurrentPages()
|
const pages = getCurrentPages()
|
||||||
@@ -190,7 +190,7 @@ onMounted(async () => {
|
|||||||
const options = currentPage.options || {}
|
const options = currentPage.options || {}
|
||||||
if (!options.id) return
|
if (!options.id) return
|
||||||
try {
|
try {
|
||||||
uni.showLoading({ title: t('common.loading') })
|
uni.showLoading({ title: $t('common.loading') })
|
||||||
const res = await getExpressReturnDetail(options.id)
|
const res = await getExpressReturnDetail(options.id)
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res && res.code === 200 && res.data) {
|
||||||
const r = res.data
|
const r = res.data
|
||||||
@@ -208,10 +208,10 @@ onMounted(async () => {
|
|||||||
remark: r.remark || ''
|
remark: r.remark || ''
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || t('express.getDetailFailed'))
|
throw new Error(res?.msg || $t('express.getDetailFailed'))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({ title: e.message || t('express.loadFailed'), icon: 'none' })
|
uni.showToast({ title: e.message || $t('express.loadFailed'), icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ import { ref, onMounted } from 'vue'
|
|||||||
import { getExpressReturnList } from '@/config/api/expressReturn.js'
|
import { getExpressReturnList } from '@/config/api/expressReturn.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
const returnList = ref([])
|
const returnList = ref([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -62,12 +62,13 @@ const query = ref({ pageNum: 1, pageSize: 20 })
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('express.returnRecord')
|
title: $t('express.returnRecord')
|
||||||
})
|
})
|
||||||
loadList()
|
loadList()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 收件信息(名称走多语言,地址暂为固定配置)
|
// 收件信息
|
||||||
|
const recipientName = '风电者 18163601305'
|
||||||
const recipientAddress = '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623'
|
const recipientAddress = '湖南省长沙市岳麓区麓谷街道新长海尖科技园A2栋623'
|
||||||
|
|
||||||
const loadList = async () => {
|
const loadList = async () => {
|
||||||
@@ -79,12 +80,12 @@ const loadList = async () => {
|
|||||||
const rows = (res.data && (res.data.rows || res.data)) || []
|
const rows = (res.data && (res.data.rows || res.data)) || []
|
||||||
returnList.value = rows.map(r => ({
|
returnList.value = rows.map(r => ({
|
||||||
id: r.id,
|
id: r.id,
|
||||||
expressCompany: r.expressCompany || r.company || t('express.toFill'),
|
expressCompany: r.expressCompany || r.company || '待填写',
|
||||||
trackingNumber: r.logisticsTrackingNumber || r.trackingNumber || t('express.toFill'),
|
trackingNumber: r.logisticsTrackingNumber || r.trackingNumber || '待填写',
|
||||||
returnAddress: r.returnAddress || r.address || t('express.toFill'),
|
returnAddress: r.returnAddress || r.address || '待填写',
|
||||||
returnTime: r.expressFillTime || r.createTime || r.returnTime || t('express.toFill'),
|
returnTime: r.expressFillTime || r.createTime || r.returnTime || '待填写',
|
||||||
packageType: r.packageType || t('express.toFill'),
|
packageType: r.packageType || '待填写',
|
||||||
weight: r.weight || t('express.toFill'),
|
weight: r.weight || '待填写',
|
||||||
status: mapStatus(r.status),
|
status: mapStatus(r.status),
|
||||||
rawStatus: r.status,
|
rawStatus: r.status,
|
||||||
userPhone: r.userPhone,
|
userPhone: r.userPhone,
|
||||||
@@ -92,10 +93,10 @@ const loadList = async () => {
|
|||||||
remark: r.remark
|
remark: r.remark
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || t('express.getListFailed'))
|
throw new Error(res?.msg || $t('express.getListFailed'))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.showToast({ title: e.message || t('express.loadFailed'), icon: 'none' })
|
uni.showToast({ title: e.message || $t('express.loadFailed'), icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -117,33 +118,34 @@ const getStatusClass = (status) => ({
|
|||||||
}[status] || 'status-pending')
|
}[status] || 'status-pending')
|
||||||
|
|
||||||
const getStatusText = (status) => ({
|
const getStatusText = (status) => ({
|
||||||
'completed': t('express.billingPaused'),
|
'completed': $t('express.billingPaused'),
|
||||||
'processing': t('express.billingPaused'),
|
'processing': $t('express.billingPaused'),
|
||||||
'pending': t('express.billingPaused')
|
'pending': $t('express.billingPaused')
|
||||||
}[status] || t('express.billingPaused'))
|
}[status] || $t('express.billingPaused'))
|
||||||
|
|
||||||
const getStatusBadge = (status) => ({
|
const getStatusBadge = (status) => ({
|
||||||
'completed': t('express.completed'),
|
'completed': $t('express.completed'),
|
||||||
'processing': t('express.processing'),
|
'processing': $t('express.processing'),
|
||||||
'pending': t('express.pending')
|
'pending': $t('express.pending')
|
||||||
}[status] || t('express.pending'))
|
}[status] || $t('express.pending'))
|
||||||
|
|
||||||
// 一键复制全部信息
|
// 一键复制全部信息
|
||||||
const copyAllInfo = () => {
|
const copyAllInfo = () => {
|
||||||
const allInfo = `${t('express.recipient')}:${t('express.recipientName')}\n${t('express.recipientAddressLabel')}:${recipientAddress}`
|
const allInfo = `${$t('express.recipient')}:${recipientName}\n${$t('express.recipientAddressLabel')}:${recipientAddress}`
|
||||||
uni.setClipboardData({
|
uni.setClipboardData({
|
||||||
data: allInfo,
|
data: allInfo,
|
||||||
success: () => {
|
success: () => {
|
||||||
uni.showToast({ title: t('express.copySuccess'), icon: 'success' })
|
uni.showToast({ title: $t('express.copySuccess'), icon: 'success' })
|
||||||
},
|
},
|
||||||
fail: () => {
|
fail: () => {
|
||||||
uni.showToast({ title: t('express.copyFailed'), icon: 'none' })
|
uni.showToast({ title: $t('express.copyFailed'), icon: 'none' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击列表项
|
// 点击列表项
|
||||||
const handleItemClick = (item) => {
|
const handleItemClick = (item) => {
|
||||||
|
console.log('点击了归还记录:', item)
|
||||||
// 未填写(status=0 -> mapped 'pending')时跳转到补填页,其它跳详情
|
// 未填写(status=0 -> mapped 'pending')时跳转到补填页,其它跳详情
|
||||||
if (item && item.rawStatus === 0) {
|
if (item && item.rawStatus === 0) {
|
||||||
uni.navigateTo({ url: `/pages/expressReturn/addExpressReturn?id=${item.id}` })
|
uni.navigateTo({ url: `/pages/expressReturn/addExpressReturn?id=${item.id}` })
|
||||||
@@ -92,19 +92,19 @@
|
|||||||
getFeedbackDetail,
|
getFeedbackDetail,
|
||||||
getFeedbackMessages,
|
getFeedbackMessages,
|
||||||
sendFeedbackMessage
|
sendFeedbackMessage
|
||||||
} from '@/config/api/feedback.js';
|
} from '../../config/api/feedback.js';
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
t
|
t: $t
|
||||||
} = useI18n()
|
} = useI18n()
|
||||||
|
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('feedback.detail')
|
title: $t('feedback.detail')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
await loadDetail();
|
await loadDetail();
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.idRequired'),
|
title: $t('feedback.idRequired'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
try {
|
try {
|
||||||
if (shouldShowLoading) {
|
if (shouldShowLoading) {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.loading')
|
title: $t('common.loading')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
await loadMessages(res.data.messages);
|
await loadMessages(res.data.messages);
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.msg || t('feedback.getDetailFailed'),
|
title: res.msg || $t('feedback.getDetailFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取投诉详情失败:', error);
|
console.error('获取投诉详情失败:', error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.getDetailFailed'),
|
title: $t('feedback.getDetailFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
const submitReply = async () => {
|
const submitReply = async () => {
|
||||||
if (!replyContent.value.trim()) {
|
if (!replyContent.value.trim()) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.pleaseEnterReply'),
|
title: $t('feedback.pleaseEnterReply'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -215,7 +215,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.submitting')
|
title: $t('common.submitting')
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await sendFeedbackMessage(feedbackId.value, {
|
const res = await sendFeedbackMessage(feedbackId.value, {
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
|
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.replySuccess'),
|
title: $t('feedback.replySuccess'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
replyContent.value = '';
|
replyContent.value = '';
|
||||||
@@ -234,14 +234,14 @@
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.msg || t('feedback.replyFailed'),
|
title: res.msg || $t('feedback.replyFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('提交回复失败:', error);
|
console.error('提交回复失败:', error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.replyFailed'),
|
title: $t('feedback.replyFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -252,11 +252,11 @@
|
|||||||
// 获取状态文本
|
// 获取状态文本
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'pending': t('feedback.pending'),
|
'pending': $t('feedback.pending'),
|
||||||
'in_progress': t('feedback.processing'),
|
'in_progress': $t('feedback.processing'),
|
||||||
'resolved': t('feedback.completed')
|
'resolved': $t('feedback.completed')
|
||||||
};
|
};
|
||||||
return statusMap[status] || t('feedback.pending');
|
return statusMap[status] || $t('feedback.pending');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取状态样式类
|
// 获取状态样式类
|
||||||
@@ -272,8 +272,8 @@
|
|||||||
// 获取类型文本
|
// 获取类型文本
|
||||||
const getTypeText = (type) => {
|
const getTypeText = (type) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'complain': t('feedback.complain'),
|
'complain': $t('feedback.complain'),
|
||||||
'suggestion': t('feedback.suggestion')
|
'suggestion': $t('feedback.suggestion')
|
||||||
};
|
};
|
||||||
return typeMap[type] || type || '-';
|
return typeMap[type] || type || '-';
|
||||||
};
|
};
|
||||||
@@ -306,7 +306,7 @@
|
|||||||
// 获取图片列表(支持字符串或数组)
|
// 获取图片列表(支持字符串或数组)
|
||||||
const getImageList = (item) => {
|
const getImageList = (item) => {
|
||||||
if (!item) return [];
|
if (!item) return [];
|
||||||
const pictureSource = item.pictureUrls ?? item.picturePath;
|
const pictureSource = item.pictureUrls != null ? item.pictureUrls : item.picturePath;
|
||||||
if (!pictureSource) return [];
|
if (!pictureSource) return [];
|
||||||
if (Array.isArray(pictureSource)) {
|
if (Array.isArray(pictureSource)) {
|
||||||
return pictureSource.filter(img => !!img);
|
return pictureSource.filter(img => !!img);
|
||||||
@@ -72,37 +72,34 @@
|
|||||||
} from "@dcloudio/uni-app"
|
} from "@dcloudio/uni-app"
|
||||||
import {
|
import {
|
||||||
addUserFeedback
|
addUserFeedback
|
||||||
} from '@/config/api/feedback'
|
} from '../../config/api/feedback'
|
||||||
import {
|
import {
|
||||||
uploadOssResource
|
uploadOssResource
|
||||||
} from '@/config/api/user'
|
} from '../../config/api/user'
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
const {
|
const {
|
||||||
t
|
t: $t
|
||||||
} = useI18n()
|
} = useI18n()
|
||||||
|
|
||||||
// 跳转到投诉记录列表
|
// 跳转到投诉记录列表
|
||||||
const navigateToRecord = () => {
|
const navigateToRecord = () => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/subPackages/service/feedback/list'
|
url: '/pages/feedback/list'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('feedback.title')
|
title: $t('feedback.title')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onLoad((options) => {
|
onLoad(() => {
|
||||||
if (uni.getStorageSync("userInfo").phone) {
|
if (uni.getStorageSync("userInfo").phone) {
|
||||||
contact.value = uni.getStorageSync('userInfo').phone;
|
contact.value = uni.getStorageSync('userInfo').phone;
|
||||||
}
|
}
|
||||||
if(options.selectedType) {
|
|
||||||
selectedType.value = parseInt(options.selectedType);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
@@ -137,7 +134,7 @@
|
|||||||
const submitFeedback = async () => {
|
const submitFeedback = async () => {
|
||||||
if (selectedType.value === -1) {
|
if (selectedType.value === -1) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.pleaseSelectType'),
|
title: $t('feedback.pleaseSelectType'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -145,7 +142,7 @@
|
|||||||
|
|
||||||
if (!description.value.trim()) {
|
if (!description.value.trim()) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.pleaseDescribe'),
|
title: $t('feedback.pleaseDescribe'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -153,7 +150,7 @@
|
|||||||
|
|
||||||
if (!contact.value) {
|
if (!contact.value) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.pleaseContact'),
|
title: $t('feedback.pleaseContact'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -169,7 +166,7 @@
|
|||||||
try {
|
try {
|
||||||
// 显示上传进度
|
// 显示上传进度
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('feedback.uploading') || '上传中...',
|
title: $t('feedback.uploading') || '上传中...',
|
||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -185,7 +182,7 @@
|
|||||||
console.error(`文件 ${i + 1} 上传失败:`, err)
|
console.error(`文件 ${i + 1} 上传失败:`, err)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.imageUploadFailed'),
|
title: $t('feedback.imageUploadFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -208,7 +205,7 @@
|
|||||||
// 处理响应
|
// 处理响应
|
||||||
if (res && (res.code === 200 || res === true || res?.success === true)) {
|
if (res && (res.code === 200 || res === true || res?.success === true)) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.submitSuccess'),
|
title: $t('feedback.submitSuccess'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -216,7 +213,7 @@
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: (res && (res.msg || res.message)) || t('feedback.submitFailed'),
|
title: (res && (res.msg || res.message)) || $t('feedback.submitFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -224,7 +221,7 @@
|
|||||||
console.error('feedback submit failed:', err)
|
console.error('feedback submit failed:', err)
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('error.networkError') || '网络错误,请重试',
|
title: $t('error.networkError') || '网络错误,请重试',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -72,19 +72,19 @@
|
|||||||
} from '@dcloudio/uni-app';
|
} from '@dcloudio/uni-app';
|
||||||
import {
|
import {
|
||||||
getFeedbackList
|
getFeedbackList
|
||||||
} from '@/config/api/feedback.js';
|
} from '../../config/api/feedback.js';
|
||||||
import {
|
import {
|
||||||
useI18n
|
useI18n
|
||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
t
|
t: $t
|
||||||
} = useI18n()
|
} = useI18n()
|
||||||
|
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('feedback.recordList')
|
title: $t('feedback.recordList')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -100,25 +100,25 @@
|
|||||||
// 状态标签
|
// 状态标签
|
||||||
const statusTabs = reactive([{
|
const statusTabs = reactive([{
|
||||||
get text() {
|
get text() {
|
||||||
return t('common.all')
|
return $t('common.all')
|
||||||
},
|
},
|
||||||
status: ''
|
status: ''
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
get text() {
|
get text() {
|
||||||
return t('feedback.pending')
|
return $t('feedback.pending')
|
||||||
},
|
},
|
||||||
status: 'pending'
|
status: 'pending'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
get text() {
|
get text() {
|
||||||
return t('feedback.processing')
|
return $t('feedback.processing')
|
||||||
},
|
},
|
||||||
status: 'in_progress'
|
status: 'in_progress'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
get text() {
|
get text() {
|
||||||
return t('feedback.completed')
|
return $t('feedback.completed')
|
||||||
},
|
},
|
||||||
status: 'resolved'
|
status: 'resolved'
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
const status = statusTabs[currentTab.value].status;
|
const status = statusTabs[currentTab.value].status;
|
||||||
const params = {
|
const params = {
|
||||||
pageNum: currentPage.value,
|
pageNum: currentPage.value,
|
||||||
pageSize: pageSize.value,
|
pageSize: pageSize.value
|
||||||
};
|
};
|
||||||
if (status) {
|
if (status) {
|
||||||
params.status = status;
|
params.status = status;
|
||||||
@@ -176,14 +176,14 @@
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: res.msg || t('feedback.getListFailed'),
|
title: res.msg || $t('feedback.getListFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取投诉列表失败:', error);
|
console.error('获取投诉列表失败:', error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('feedback.getListFailed'),
|
title: $t('feedback.getListFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -211,11 +211,11 @@
|
|||||||
// 获取状态文本
|
// 获取状态文本
|
||||||
const getStatusText = (status) => {
|
const getStatusText = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'pending': t('feedback.pending'),
|
'pending': $t('feedback.pending'),
|
||||||
'in_progress': t('feedback.processing'),
|
'in_progress': $t('feedback.processing'),
|
||||||
'resolved': t('feedback.completed')
|
'resolved': $t('feedback.completed')
|
||||||
};
|
};
|
||||||
return statusMap[status] || t('feedback.pending');
|
return statusMap[status] || $t('feedback.pending');
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取状态样式类
|
// 获取状态样式类
|
||||||
@@ -231,8 +231,8 @@
|
|||||||
// 获取类型文本
|
// 获取类型文本
|
||||||
const getTypeText = (type) => {
|
const getTypeText = (type) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'complain': t('feedback.complain'),
|
'complain': $t('feedback.complain'),
|
||||||
'suggestion': t('feedback.suggestion')
|
'suggestion': $t('feedback.suggestion')
|
||||||
};
|
};
|
||||||
return typeMap[type] || type || '-';
|
return typeMap[type] || type || '-';
|
||||||
};
|
};
|
||||||
@@ -270,7 +270,7 @@
|
|||||||
// 跳转到详情页
|
// 跳转到详情页
|
||||||
const navigateToDetail = (item) => {
|
const navigateToDetail = (item) => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/subPackages/service/feedback/detail?id=${item.id || item.feedbackId}`
|
url: `/pages/feedback/detail?id=${item.id || item.feedbackId}`
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
<template>
|
||||||
|
<view class="help-container">
|
||||||
|
<!-- 常见问题 -->
|
||||||
|
<view class="faq-section">
|
||||||
|
<uv-collapse :border="false">
|
||||||
|
<uv-collapse-item
|
||||||
|
v-for="(item, index) in faqList"
|
||||||
|
:key="index"
|
||||||
|
:title="$t(item.question)"
|
||||||
|
:name="index"
|
||||||
|
>
|
||||||
|
<view class="answer-content">
|
||||||
|
<text class="answer-text">{{ $t(item.answer) }}</text>
|
||||||
|
</view>
|
||||||
|
</uv-collapse-item>
|
||||||
|
</uv-collapse>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 联系客服 -->
|
||||||
|
<view class="contact-card">
|
||||||
|
<view class="contact-title">{{ $t('help.contactUs') }}</view>
|
||||||
|
<view class="contact-content">
|
||||||
|
<view class="contact-item">
|
||||||
|
<text class="label">{{ $t('help.phone') }}</text>
|
||||||
|
<text class="value" @click="makePhoneCall">{{ customerPhone }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="contact-item">
|
||||||
|
<text class="label">{{ $t('help.workingHours') }}</text>
|
||||||
|
<text class="value">{{ HELP_CONTENT.CONTACT.SERVICE_TIME.VALUE }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { HELP_CONTENT } from '@/constants/help'
|
||||||
|
import { getCustomerPhone } from '@/util/index.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
HELP_CONTENT,
|
||||||
|
faqList: HELP_CONTENT.FAQ_LIST,
|
||||||
|
customerPhone: HELP_CONTENT.CONTACT.PHONE.VALUE // 默认客服电话
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// 设置页面标题
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: this.$t('help.title')
|
||||||
|
})
|
||||||
|
// 从缓存读取客服电话
|
||||||
|
this.customerPhone = getCustomerPhone()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
makePhoneCall() {
|
||||||
|
uni.makePhoneCall({
|
||||||
|
phoneNumber: this.customerPhone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.help-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8f8f8;
|
||||||
|
padding: 30rpx;
|
||||||
|
|
||||||
|
.faq-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.answer-content {
|
||||||
|
padding: 20rpx 30rpx 30rpx;
|
||||||
|
background: #f9f9f9;
|
||||||
|
|
||||||
|
.answer-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
||||||
|
|
||||||
|
.contact-title {
|
||||||
|
position: relative;
|
||||||
|
z-index: 4;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
padding-bottom: 8rpx;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
z-index: -1;
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 8rpx;
|
||||||
|
width: 88%;
|
||||||
|
height: 16rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #07C160;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-content {
|
||||||
|
.contact-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
// 外部网页地址
|
// 外部网页地址
|
||||||
const webUrl = ref('https://joininvestment.gxfs123.com/')
|
const webUrl = ref('https://joininvestment.gxfs123.com/')
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
const handleError = (e) => {
|
const handleError = (e) => {
|
||||||
console.error('web-view 加载错误:', e)
|
console.error('web-view 加载错误:', e)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('join.pageLoadFailed'),
|
title: $t('join.pageLoadFailed'),
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('join.title')
|
title: $t('join.title')
|
||||||
})
|
})
|
||||||
console.log('招商页面加载,外部网址:', webUrl.value)
|
console.log('招商页面加载,外部网址:', webUrl.value)
|
||||||
})
|
})
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<view class="p">Before using {{ brandName }}, please carefully read and fully understand all contents of this Agreement, especially the terms highlighted in bold (including but not limited to liability limitations, dispute resolution, applicable law, protection of minors, etc.). By clicking "Login/Use" or actually using the service, you are deemed to have read and agreed to be bound by this Agreement.</view>
|
<view class="p">Before using {{ brandName }}, please carefully read and fully understand all contents of this Agreement, especially the terms highlighted in bold (including but not limited to liability limitations, dispute resolution, applicable law, protection of minors, etc.). By clicking "Login/Use" or actually using the service, you are deemed to have read and agreed to be bound by this Agreement.</view>
|
||||||
|
|
||||||
<view class="h1">II. Account and Login</view>
|
<view class="h1">II. Account and Login</view>
|
||||||
<view class="p">2.1 You can log in and use this service through WeChat authorization. To complete deposit-free rental and order settlement, you agree that we conduct credit assessment and post-order settlement based on WeChat Payment Score.</view>
|
<view class="p">2.1 You can log in and use this service through Alipay authorization. To complete deposit-free rental and order settlement, you agree that we conduct credit assessment and post-order settlement based on Sesame Credit.</view>
|
||||||
<view class="p">2.2 You should ensure that the information provided is true, accurate, and complete, and update it in a timely manner. Any service restrictions, order abnormalities, or losses caused by untrue information or failure to update in time shall be borne by you.</view>
|
<view class="p">2.2 You should ensure that the information provided is true, accurate, and complete, and update it in a timely manner. Any service restrictions, order abnormalities, or losses caused by untrue information or failure to update in time shall be borne by you.</view>
|
||||||
<view class="p">2.3 You shall be responsible for all activities under the account, properly keep the device and account credentials, and shall not lend, rent, or otherwise provide them to others.</view>
|
<view class="p">2.3 You shall be responsible for all activities under the account, properly keep the device and account credentials, and shall not lend, rent, or otherwise provide them to others.</view>
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
<view class="p">3.2 Usage specifications: Please use the device properly, avoid water ingress, dropping, unauthorized disassembly or modification; do not approach open flames and high-temperature environments; avoid outdoor use in rainy days; children should use under supervision.</view>
|
<view class="p">3.2 Usage specifications: Please use the device properly, avoid water ingress, dropping, unauthorized disassembly or modification; do not approach open flames and high-temperature environments; avoid outdoor use in rainy days; children should use under supervision.</view>
|
||||||
<view class="p">3.3 Prohibited behaviors: Using the device for illegal or improper purposes; affecting the normal operation of the device or system in any way; circumventing billing or return processes through abnormal means.</view>
|
<view class="p">3.3 Prohibited behaviors: Using the device for illegal or improper purposes; affecting the normal operation of the device or system in any way; circumventing billing or return processes through abnormal means.</view>
|
||||||
|
|
||||||
<view class="h1">IV. Billing and Settlement (Including WeChat Payment Score)</view>
|
<view class="h1">IV. Billing and Settlement (Including Sesame Credit)</view>
|
||||||
<view class="p"><text class="bold">4.1 Billing rules</text>: Subject to the real-time billing rules displayed in the mini program, which may include duration billing, capped prices, service fees, etc. Charges will be based on this after order generation.</view>
|
<view class="p"><text class="bold">4.1 Billing rules</text>: Subject to the real-time billing rules displayed in the mini program, which may include duration billing, capped prices, service fees, etc. Charges will be based on this after order generation.</view>
|
||||||
<view class="p"><text class="bold">4.2 WeChat Payment Score deposit-free</text>: If you activate and pass the credit assessment, you can enjoy deposit-free rental; if the assessment fails, pre-authorization or deposit may be required. Please refer to the page prompts for details.</view>
|
<view class="p"><text class="bold">4.2 Sesame Credit deposit-free</text>: If you activate and pass the credit assessment, you can enjoy deposit-free rental; if the assessment fails, pre-authorization or deposit may be required. Please refer to the page prompts for details.</view>
|
||||||
<view class="p">4.3 Settlement and deduction: After the order ends, we will complete the settlement based on actual usage and platform rules, and deduct through WeChat Payment Score/WeChat Pay.</view>
|
<view class="p">4.3 Settlement and deduction: After the order ends, we will complete the settlement based on actual usage and platform rules, and deduct through Sesame Credit/Alipay.</view>
|
||||||
<view class="p">4.4 Exceptions and disputes: If you have any objection to billing or settlement, please submit it through "My-Customer Service" within 48 hours after order completion; overdue submissions may affect processing results.</view>
|
<view class="p">4.4 Exceptions and disputes: If you have any objection to billing or settlement, please submit it through "My-Customer Service" within 48 hours after order completion; overdue submissions may affect processing results.</view>
|
||||||
|
|
||||||
<view class="h1">V. Device Return and Overdue Handling</view>
|
<view class="h1">V. Device Return and Overdue Handling</view>
|
||||||
<view class="p">5.1 Return method: Return at designated outlets according to mini program instructions, or send back through the "Express Return" function. Non-designated methods may lead to order abnormalities and additional fees.</view>
|
<view class="p">5.1 Return method: Return at designated outlets according to mini program instructions, or send back through the "Express Return" function. Non-designated methods may lead to order abnormalities and additional fees.</view>
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<view class="p">9.2 You should be responsible for your own use. Any losses caused by your violation of this Agreement or improper storage and use of equipment shall be borne by you or compensated to relevant parties.</view>
|
<view class="p">9.2 You should be responsible for your own use. Any losses caused by your violation of this Agreement or improper storage and use of equipment shall be borne by you or compensated to relevant parties.</view>
|
||||||
|
|
||||||
<view class="h1">X. Privacy and Personal Information Protection</view>
|
<view class="h1">X. Privacy and Personal Information Protection</view>
|
||||||
<view class="p">10.1 We strictly handle your personal information in accordance with the Privacy Policy, including WeChat login information, mobile phone number (obtained after your authorization), device and order information, location and outlet information, etc.</view>
|
<view class="p">10.1 We strictly handle your personal information in accordance with the Privacy Policy, including Alipay login information, mobile phone number (obtained after your authorization), device and order information, location and outlet information, etc.</view>
|
||||||
<view class="p">10.2 For details, please refer to the Privacy Policy in this mini program.</view>
|
<view class="p">10.2 For details, please refer to the Privacy Policy in this mini program.</view>
|
||||||
|
|
||||||
<view class="h1">XI. Service Changes and Termination</view>
|
<view class="h1">XI. Service Changes and Termination</view>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = 'Isidaya'
|
const brandName = 'WindPower'
|
||||||
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
const disputeVenue = 'the location of the platform'
|
const disputeVenue = 'the location of the platform'
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<view class="p">在使用{{ brandName }}前,请您务必仔细阅读并充分理解本协议全部内容,尤其是以加粗方式提示的条款(包括但不限于责任限制、争议解决、适用法律、未成年人保护等)。您点击"登录/使用"或实际使用服务即视为您已阅读并同意受本协议约束。</view>
|
<view class="p">在使用{{ brandName }}前,请您务必仔细阅读并充分理解本协议全部内容,尤其是以加粗方式提示的条款(包括但不限于责任限制、争议解决、适用法律、未成年人保护等)。您点击"登录/使用"或实际使用服务即视为您已阅读并同意受本协议约束。</view>
|
||||||
|
|
||||||
<view class="h1">二、账号与登录</view>
|
<view class="h1">二、账号与登录</view>
|
||||||
<view class="p">2.1 您可通过微信授权登录使用本服务。为完成免押租借与订单结算,您同意我们基于微信支付分进行信用评估及订单后结等必要处理。</view>
|
<view class="p">2.1 您可通过支付宝授权登录使用本服务。为完成免押租借与订单结算,您同意我们基于芝麻信用进行信用评估及订单后结等必要处理。</view>
|
||||||
<view class="p">2.2 您应保证提供信息真实、准确、完整,并及时更新。因您提供的信息不真实或未及时更新导致的服务受限、订单异常或损失,由您自行承担。</view>
|
<view class="p">2.2 您应保证提供信息真实、准确、完整,并及时更新。因您提供的信息不真实或未及时更新导致的服务受限、订单异常或损失,由您自行承担。</view>
|
||||||
<view class="p">2.3 您应对账户下的全部行为负责,妥善保管设备与账户凭证,不得转借、出租或以其他方式提供给他人使用。</view>
|
<view class="p">2.3 您应对账户下的全部行为负责,妥善保管设备与账户凭证,不得转借、出租或以其他方式提供给他人使用。</view>
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@
|
|||||||
<view class="p">3.2 使用规范:请合理使用设备,避免进水、摔落、私自拆卸或改装;请勿靠近明火与高温环境;室外雨天请避免使用;儿童应在监护下使用。</view>
|
<view class="p">3.2 使用规范:请合理使用设备,避免进水、摔落、私自拆卸或改装;请勿靠近明火与高温环境;室外雨天请避免使用;儿童应在监护下使用。</view>
|
||||||
<view class="p">3.3 禁止行为:将设备用于违法或不当用途;以任何方式影响设备或系统的正常运行;通过非正常手段规避计费或归还流程。</view>
|
<view class="p">3.3 禁止行为:将设备用于违法或不当用途;以任何方式影响设备或系统的正常运行;通过非正常手段规避计费或归还流程。</view>
|
||||||
|
|
||||||
<view class="h1">四、计费与结算(含微信支付分)</view>
|
<view class="h1">四、计费与结算(含芝麻信用)</view>
|
||||||
<view class="p"><text class="bold">4.1 计费规则</text>:以小程序展示的实时计费规则为准,可能包含时长计费、封顶价、服务费等。订单生成后将据此计费。</view>
|
<view class="p"><text class="bold">4.1 计费规则</text>:以小程序展示的实时计费规则为准,可能包含时长计费、封顶价、服务费等。订单生成后将据此计费。</view>
|
||||||
<view class="p"><text class="bold">4.2 微信支付分免押</text>:若您开通并通过信用评估,可享受免押租借;如评估未通过,可能需预授权或押金。具体以页面提示为准。</view>
|
<view class="p"><text class="bold">4.2 芝麻信用免押</text>:若您开通并通过信用评估,可享受免押租借;如评估未通过,可能需预授权或押金。具体以页面提示为准。</view>
|
||||||
<view class="p">4.3 结算与扣款:订单结束后,我们将基于实际使用情况与平台规则完成结算并通过微信支付分/微信支付进行扣款。</view>
|
<view class="p">4.3 结算与扣款:订单结束后,我们将基于实际使用情况与平台规则完成结算并通过芝麻信用/支付宝支付进行扣款。</view>
|
||||||
<view class="p">4.4 异常与争议:如对计费或结算有异议,请在订单完成后48小时内通过"我的-客服"提交;逾期可能影响处理结果。</view>
|
<view class="p">4.4 异常与争议:如对计费或结算有异议,请在订单完成后48小时内通过"我的-客服"提交;逾期可能影响处理结果。</view>
|
||||||
|
|
||||||
<view class="h1">五、设备归还与逾期处理</view>
|
<view class="h1">五、设备归还与逾期处理</view>
|
||||||
<view class="p">5.1 归还方式:按照小程序指引在指定网点归还,或通过"快递归还"功能寄回。非指定方式可能导致订单异常与额外费用。</view>
|
<view class="p">5.1 归还方式:按照小程序指引在指定网点归还,或通过"快递归还"功能寄回。非指定方式可能导致订单异常与额外费用。</view>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = 'Isidaya'
|
const brandName = '风电者'
|
||||||
const companyName = '深圳乐慕智云科技有限公司'
|
const companyName = '深圳乐慕智云科技有限公司'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
const disputeVenue = '平台所在地'
|
const disputeVenue = '平台所在地'
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
import { getCurrentAgreement } from '@/config/api/system.js'
|
import { URL } from '@/config/url.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -50,6 +50,13 @@
|
|||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 将语言代码转换为后端接受的格式
|
||||||
|
const convertLanguageCode = (lang) => {
|
||||||
|
// zh-CN -> zh-CN (保持不变)
|
||||||
|
// en-US -> en_US (转换下划线)
|
||||||
|
return lang.replace(/-/g, '_')
|
||||||
|
}
|
||||||
|
|
||||||
// 加载协议内容
|
// 加载协议内容
|
||||||
const loadAgreement = async () => {
|
const loadAgreement = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -57,22 +64,35 @@
|
|||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('加载用户协议')
|
// 获取当前语言设置
|
||||||
|
const currentLang = uni.getStorageSync('language') || 'zh-CN'
|
||||||
|
const languageCode = convertLanguageCode(currentLang)
|
||||||
|
|
||||||
|
console.log('加载用户协议,语言:', currentLang, '转换后:', languageCode)
|
||||||
|
|
||||||
// 调用接口获取协议内容
|
// 调用接口获取协议内容
|
||||||
const res = await getCurrentAgreement({
|
const res = await uni.request({
|
||||||
agreementCode: 'USER_AGREEMENT',
|
url: `${URL}/device/agreementConfig/current`,
|
||||||
appPlatform: 'wechat',
|
method: 'GET',
|
||||||
appType: 'user'
|
header: {
|
||||||
|
'Content-Language': languageCode,
|
||||||
|
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
agreementCode: 'USER_AGREEMENT',
|
||||||
|
appPlatform: 'wechat',
|
||||||
|
appType: 'user'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('用户协议响应:', res)
|
console.log('用户协议响应:', res)
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
|
||||||
agreementData.value = {
|
agreementData.value = {
|
||||||
title: res.data.title || t('legal.agreement'),
|
title: res.data.data.title || $t('legal.agreement'),
|
||||||
content: res.data.content || '',
|
content: res.data.data.content || '',
|
||||||
remark: res.data.remark || ''
|
remark: res.data.data.remark || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
@@ -80,12 +100,12 @@
|
|||||||
title: agreementData.value.title
|
title: agreementData.value.title
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || t('common.loadFailed'))
|
throw new Error(res.data.msg || $t('common.loadFailed'))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载用户协议失败:', err)
|
console.error('加载用户协议失败:', err)
|
||||||
error.value = true
|
error.value = true
|
||||||
errorMessage.value = err.message || t('common.loadFailed')
|
errorMessage.value = err.message || $t('common.loadFailed')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -16,18 +16,18 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="h1">II. Information We Collect</view>
|
<view class="h1">II. Information We Collect</view>
|
||||||
<view class="p">2.1 Account information: WeChat login identifier (such as openId/unionId), nickname and avatar (with your authorization), mobile phone number (obtained through WeChat after your authorization).</view>
|
<view class="p">2.1 Account information: Alipay login identifier (such as userId), nickname and avatar (with your authorization), mobile phone number (obtained through Alipay after your authorization).</view>
|
||||||
<view class="p">2.2 Order and device information: rental records, usage duration, fees, return points, device status, abnormal records, etc.</view>
|
<view class="p">2.2 Order and device information: rental records, usage duration, fees, return points, device status, abnormal records, etc.</view>
|
||||||
<view class="p">2.3 Location and outlet information: used to find nearby outlets and navigation after your authorization, and will not be obtained without authorization.</view>
|
<view class="p">2.3 Location and outlet information: used to find nearby outlets and navigation after your authorization, and will not be obtained without authorization.</view>
|
||||||
<view class="p">2.4 Log information: To ensure service security and stability, we may record operation logs, network requests, and error information.</view>
|
<view class="p">2.4 Log information: To ensure service security and stability, we may record operation logs, network requests, and error information.</view>
|
||||||
|
|
||||||
<view class="h1">III. Purpose of Information Use</view>
|
<view class="h1">III. Purpose of Information Use</view>
|
||||||
<view class="p">3.1 Provide core functions: identity verification, deposit-free rental (WeChat Payment Score assessment), order billing and settlement, customer service and after-sales.</view>
|
<view class="p">3.1 Provide core functions: identity verification, deposit-free rental (Sesame Credit assessment), order billing and settlement, customer service and after-sales.</view>
|
||||||
<view class="p">3.2 Security risk control: prevent fraud, violations and risk control; ensure system and device security.</view>
|
<view class="p">3.2 Security risk control: prevent fraud, violations and risk control; ensure system and device security.</view>
|
||||||
<view class="p">3.3 Product optimization: statistics and analysis to improve experience (conducted after de-identification/anonymization).</view>
|
<view class="p">3.3 Product optimization: statistics and analysis to improve experience (conducted after de-identification/anonymization).</view>
|
||||||
|
|
||||||
<view class="h1">IV. WeChat Payment Score and Payment</view>
|
<view class="h1">IV. Sesame Credit and Payment</view>
|
||||||
<view class="p">4.1 To implement deposit-free rental, we will conduct necessary data interaction with WeChat Payment Score (such as credit assessment results and order settlement). Related data processing follows the rules of WeChat Pay and WeChat Payment Score.</view>
|
<view class="p">4.1 To implement deposit-free rental, we will conduct necessary data interaction with Sesame Credit (such as credit assessment results and order settlement). Related data processing follows the rules of Alipay and Sesame Credit.</view>
|
||||||
<view class="p">4.2 If you fail the assessment, pre-authorization or deposit processing may be required, subject to page prompts.</view>
|
<view class="p">4.2 If you fail the assessment, pre-authorization or deposit processing may be required, subject to page prompts.</view>
|
||||||
|
|
||||||
<view class="h1">V. Sharing, Transfer and Public Disclosure</view>
|
<view class="h1">V. Sharing, Transfer and Public Disclosure</view>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = 'Isidaya'
|
const brandName = 'WindPower'
|
||||||
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
const companyName = 'Shenzhen Lemu Zhiyun Technology Co., Ltd.'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
</script>
|
</script>
|
||||||
@@ -22,12 +22,12 @@
|
|||||||
<view class="p">2.4 日志信息:为保障服务安全与稳定,我们可能记录操作日志、网络请求与错误信息。</view>
|
<view class="p">2.4 日志信息:为保障服务安全与稳定,我们可能记录操作日志、网络请求与错误信息。</view>
|
||||||
|
|
||||||
<view class="h1">三、信息使用目的</view>
|
<view class="h1">三、信息使用目的</view>
|
||||||
<view class="p">3.1 提供核心功能:身份验证、免押租借(微信支付分评估)、订单计费结算、客服与售后。</view>
|
<view class="p">3.1 提供核心功能:身份验证、免押租借(芝麻信用评估)、订单计费结算、客服与售后。</view>
|
||||||
<view class="p">3.2 安全风控:防范欺诈、违规与风险控制;保障系统与设备安全。</view>
|
<view class="p">3.2 安全风控:防范欺诈、违规与风险控制;保障系统与设备安全。</view>
|
||||||
<view class="p">3.3 产品优化:统计与分析以改进体验(在去标识化/匿名化后进行)。</view>
|
<view class="p">3.3 产品优化:统计与分析以改进体验(在去标识化/匿名化后进行)。</view>
|
||||||
|
|
||||||
<view class="h1">四、微信支付分与支付</view>
|
<view class="h1">四、芝麻信用与支付</view>
|
||||||
<view class="p">4.1 为实现免押租借,我们将与微信支付分进行必要的数据交互(如信用评估结果、订单结算)。相关数据处理遵循微信支付与微信支付分的规则。</view>
|
<view class="p">4.1 为实现免押租借,我们将与芝麻信用进行必要的数据交互(如信用评估结果、订单结算)。相关数据处理遵循支付宝与芝麻信用的规则。</view>
|
||||||
<view class="p">4.2 如您未通过评估,可能需进行预授权或押金处理,以页面提示为准。</view>
|
<view class="p">4.2 如您未通过评估,可能需进行预授权或押金处理,以页面提示为准。</view>
|
||||||
|
|
||||||
<view class="h1">五、共享、转移与公开披露</view>
|
<view class="h1">五、共享、转移与公开披露</view>
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const brandName = 'Isidaya'
|
const brandName = '风电者'
|
||||||
const companyName = '深圳乐慕智云科技有限公司'
|
const companyName = '深圳乐慕智云科技有限公司'
|
||||||
const effectiveDate = '2025-10-13'
|
const effectiveDate = '2025-10-13'
|
||||||
</script>
|
</script>
|
||||||
@@ -36,9 +36,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
import { getCurrentAgreement } from '@/config/api/system.js'
|
import { URL } from '@/config/url.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -50,6 +50,13 @@
|
|||||||
remark: ''
|
remark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 将语言代码转换为后端接受的格式
|
||||||
|
const convertLanguageCode = (lang) => {
|
||||||
|
// zh-CN -> zh-CN (保持不变)
|
||||||
|
// en-US -> en_US (转换下划线)
|
||||||
|
return lang.replace(/-/g, '_')
|
||||||
|
}
|
||||||
|
|
||||||
// 加载协议内容
|
// 加载协议内容
|
||||||
const loadAgreement = async () => {
|
const loadAgreement = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -57,22 +64,35 @@
|
|||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('加载隐私政策')
|
// 获取当前语言设置
|
||||||
|
const currentLang = uni.getStorageSync('language') || 'zh-CN'
|
||||||
|
const languageCode = convertLanguageCode(currentLang)
|
||||||
|
|
||||||
|
console.log('加载隐私政策,语言:', currentLang, '转换后:', languageCode)
|
||||||
|
|
||||||
// 调用接口获取协议内容
|
// 调用接口获取协议内容
|
||||||
const res = await getCurrentAgreement({
|
const res = await uni.request({
|
||||||
agreementCode: 'PRIVACY_POLICY',
|
url: `${URL}/device/agreementConfig/current`,
|
||||||
appPlatform: 'wechat',
|
method: 'GET',
|
||||||
appType: 'user'
|
header: {
|
||||||
|
'Content-Language': languageCode,
|
||||||
|
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
agreementCode: 'PRIVACY_POLICY',
|
||||||
|
appPlatform: 'wechat',
|
||||||
|
appType: 'user'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('隐私政策响应:', res)
|
console.log('隐私政策响应:', res)
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
|
||||||
agreementData.value = {
|
agreementData.value = {
|
||||||
title: res.data.title || t('legal.privacy'),
|
title: res.data.data.title || $t('legal.privacy'),
|
||||||
content: res.data.content || '',
|
content: res.data.data.content || '',
|
||||||
remark: res.data.remark || ''
|
remark: res.data.data.remark || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
@@ -80,12 +100,12 @@
|
|||||||
title: agreementData.value.title
|
title: agreementData.value.title
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || t('common.loadFailed'))
|
throw new Error(res.data.msg || $t('common.loadFailed'))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载隐私政策失败:', err)
|
console.error('加载隐私政策失败:', err)
|
||||||
error.value = true
|
error.value = true
|
||||||
errorMessage.value = err.message || t('common.loadFailed')
|
errorMessage.value = err.message || $t('common.loadFailed')
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -8,32 +8,16 @@
|
|||||||
<view class="title">{{ $t('auth.loginTitle') }}</view>
|
<view class="title">{{ $t('auth.loginTitle') }}</view>
|
||||||
<view class="subtitle">{{ $t('auth.loginDesc') }}</view>
|
<view class="subtitle">{{ $t('auth.loginDesc') }}</view>
|
||||||
|
|
||||||
<!-- 微信小程序:一键手机号快捷登录 -->
|
<!-- 支付宝一键手机号快捷登录(推荐) -->
|
||||||
<!-- #ifdef MP-WEIXIN -->
|
|
||||||
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
|
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
|
||||||
{{ $t('auth.getPhoneNumber') }}
|
{{ $t('auth.getPhoneNumber') }}
|
||||||
</button>
|
</button>
|
||||||
<button v-else class="btn primary" open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber">
|
<button v-else class="btn primary" open-type="getAuthorize" scope="phoneNumber" @getAuthorize="onGetPhoneNumber">
|
||||||
{{ $t('auth.getPhoneNumber') }}
|
{{ $t('auth.getPhoneNumber') }}
|
||||||
</button>
|
</button>
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- 支付宝小程序:授权码快捷登录(不支持 open-type=getPhoneNumber) -->
|
<!-- 仅支付宝登录(不授权手机号时使用) -->
|
||||||
<!-- #ifdef MP-ALIPAY -->
|
<!-- <button class="btn outline" @click="onAlipayLogin">仅支付宝登录</button> -->
|
||||||
<button v-if="!isAgreed" class="btn primary" @click="handleLoginClick">
|
|
||||||
{{ $t('auth.loginBtn') }}
|
|
||||||
</button>
|
|
||||||
<button v-else class="btn primary" @click="onAlipayLogin">
|
|
||||||
{{ $t('auth.loginBtn') }}
|
|
||||||
</button>
|
|
||||||
<!-- #endif -->
|
|
||||||
|
|
||||||
<!-- H5:不显示小程序快捷登录按钮 -->
|
|
||||||
|
|
||||||
<!-- 手机号验证码登录 -->
|
|
||||||
<button class="btn outline" @click="goToPhoneLogin" v-if="isHTML5">
|
|
||||||
{{ $t('auth.phoneLogin') }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<view class="agreement-box">
|
<view class="agreement-box">
|
||||||
<checkbox-group @change="onAgreementChange">
|
<checkbox-group @change="onAgreementChange">
|
||||||
@@ -41,9 +25,9 @@
|
|||||||
<checkbox value="agreed" :checked="isAgreed" color="#07c160" class="agreement-checkbox" />
|
<checkbox value="agreed" :checked="isAgreed" color="#07c160" class="agreement-checkbox" />
|
||||||
<text class="agreement-text">
|
<text class="agreement-text">
|
||||||
{{ $t('auth.agreeToTerms') }}
|
{{ $t('auth.agreeToTerms') }}
|
||||||
<text class="link" @tap.stop="go('/subPackages/other/legal/agreement')">{{ $t('user.userAgreement') }}</text>
|
<text class="link" @tap.stop="go('/pages/legal/agreement')">{{ $t('user.userAgreement') }}</text>
|
||||||
{{ $t('common.and') }}
|
{{ $t('common.and') }}
|
||||||
<text class="link" @tap.stop="go('/subPackages/other/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
|
<text class="link" @tap.stop="go('/pages/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
|
||||||
</text>
|
</text>
|
||||||
</label>
|
</label>
|
||||||
</checkbox-group>
|
</checkbox-group>
|
||||||
@@ -54,26 +38,25 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { wxLogin, alipayLogin, getUserPhoneNumber, getUserInfo } from '@/util/index.js'
|
import { alipayLogin, getUserPhoneNumber, getUserInfo } from '../../util/index.js'
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('auth.loginTitle')
|
title: $t('auth.loginTitle')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const isHTML5 = ref(false) // 是否是HTML5模式
|
|
||||||
|
|
||||||
const redirect = ref('/pages/index/index')
|
const redirect = ref('/pages/index/index')
|
||||||
const isAgreed = ref(false) // 是否同意协议
|
const isAgreed = ref(false) // 是否同意协议
|
||||||
|
|
||||||
// 勾选协议变化
|
// 勾选协议变化
|
||||||
const onAgreementChange = (e) => {
|
const onAgreementChange = (e) => {
|
||||||
isAgreed.value = e.detail.value.includes('agreed')
|
isAgreed.value = e.detail.value.includes('agreed')
|
||||||
|
console.log('协议勾选状态:', isAgreed.value, e.detail.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 未勾选协议时点击登录按钮
|
// 未勾选协议时点击登录按钮
|
||||||
@@ -95,9 +78,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 未勾选,弹窗提示
|
// 未勾选,弹窗提示
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('common.tips'),
|
title: $t('common.tips'),
|
||||||
content: t('auth.pleaseAgreeToTerms'),
|
content: $t('auth.pleaseAgreeToTerms'),
|
||||||
|
confirmText: $t('common.confirm'),
|
||||||
|
cancelText: $t('common.cancel'),
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
// 用户点击同意,自动勾选
|
// 用户点击同意,自动勾选
|
||||||
@@ -105,7 +90,7 @@
|
|||||||
resolve()
|
resolve()
|
||||||
} else {
|
} else {
|
||||||
// 用户点击取消
|
// 用户点击取消
|
||||||
reject(new Error(t('auth.pleaseAgreeToTerms')))
|
reject(new Error('需要同意协议才能登录'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -128,51 +113,41 @@
|
|||||||
uni.reLaunch({ url: target })
|
uni.reLaunch({ url: target })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 微信快捷登录入口(备用,目前主流程使用一键手机号登录)
|
|
||||||
// const onWeChatLogin = async () => {
|
|
||||||
// try {
|
|
||||||
// await checkAgreement()
|
|
||||||
// await wxLogin()
|
|
||||||
// uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
|
||||||
// await navigateAfterLogin()
|
|
||||||
// } catch (error) {
|
|
||||||
// if (error.message !== t('auth.pleaseAgreeToTerms')) {
|
|
||||||
// uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 支付宝快捷登录入口(支付宝小程序)
|
|
||||||
const onAlipayLogin = async () => {
|
const onAlipayLogin = async () => {
|
||||||
try {
|
try {
|
||||||
|
// 先检查是否同意协议
|
||||||
await checkAgreement()
|
await checkAgreement()
|
||||||
await alipayLogin()
|
|
||||||
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
await alipayLogin()
|
||||||
|
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
|
||||||
await navigateAfterLogin()
|
await navigateAfterLogin()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.message !== t('auth.pleaseAgreeToTerms')) {
|
if (error.message !== '需要同意协议才能登录') {
|
||||||
uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
|
uni.showToast({ title: error.message || '登录失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注意:手机号一致性校验不在登录页做;扫码/租借前校验(支付宝 my.getPhoneNumber)
|
|
||||||
|
|
||||||
const onGetPhoneNumber = async (e) => {
|
const onGetPhoneNumber = async (e) => {
|
||||||
if (!e || e.detail.errMsg !== 'getPhoneNumber:ok') {
|
// 支付宝获取手机号的回调处理
|
||||||
uni.showToast({ title: t('auth.phoneCancelled'), icon: 'none' })
|
if (!e || !e.detail || !e.detail.response) {
|
||||||
|
uni.showToast({ title: $t('auth.phoneCancelled'), icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log(e);
|
|
||||||
try {
|
try {
|
||||||
// 先完成微信登录(/app/user/quickLogin,后端建立/查询 WECHAT_MINI 用户)
|
// 先支付宝登录,获取 token
|
||||||
await wxLogin(e.detail.code)
|
await alipayLogin()
|
||||||
// 再用微信返回的临时 code 换取手机号(后端按文档更新 phone 字段)
|
// 再用支付宝返回的授权码换取手机号
|
||||||
// await getUserPhoneNumber(e.detail.code)
|
// 支付宝的授权码在 e.detail.response 中
|
||||||
uni.showToast({ title: t('auth.loginSuccess'), icon: 'success' })
|
const authCode = e.detail.response?.response?.code || e.detail.response?.code
|
||||||
|
if (authCode) {
|
||||||
|
await getUserPhoneNumber(authCode)
|
||||||
|
}
|
||||||
|
uni.showToast({ title: $t('auth.loginSuccess'), icon: 'success' })
|
||||||
await navigateAfterLogin()
|
await navigateAfterLogin()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.showToast({ title: error.message || t('auth.loginFailed'), icon: 'none' })
|
uni.showToast({ title: error.message || $t('auth.loginFailed'), icon: 'none' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,29 +155,19 @@
|
|||||||
if (opts && opts.redirect) {
|
if (opts && opts.redirect) {
|
||||||
try {
|
try {
|
||||||
redirect.value = decodeURIComponent(opts.redirect)
|
redirect.value = decodeURIComponent(opts.redirect)
|
||||||
} catch (err) {
|
} catch (_) {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// #ifdef H5
|
|
||||||
isHTML5.value = true
|
|
||||||
// #endif
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const go = (url) => {
|
const go = (url) => {
|
||||||
uni.navigateTo({ url })
|
uni.navigateTo({ url })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转到手机号登录页面
|
|
||||||
const goToPhoneLogin = () => {
|
|
||||||
uni.navigateTo({ url: '/subPackages/user/login/phone' })
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.login-container {
|
.login-container {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(180deg, #C8F4D9 0%, #FFFFFF 100%);
|
background: #f8f8f8;
|
||||||
padding: 80rpx 40rpx 40rpx;
|
padding: 80rpx 40rpx 40rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="my-page">
|
<view class="my-page">
|
||||||
<view class="user-card" @click="navigateTo('/subPackages/user/userProfile/index')">
|
<view class="user-card" @click="navigateTo('/pages/userProfile/index')">
|
||||||
<view class="avatar-box">
|
<view class="avatar-box">
|
||||||
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill" lazy-load="true">
|
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
|
||||||
</image>
|
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill"></image>
|
||||||
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill" lazy-load="true"></image>
|
|
||||||
</view>
|
|
||||||
<view class="user-text">
|
|
||||||
<view class="nickname">{{ userInfo.nickName || $t('user.clickToLogin') }}</view>
|
|
||||||
<view class="subtext">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('user.loginPrompt') }}</view>
|
|
||||||
</view>
|
|
||||||
<uv-icon type="right" size="16" color="#999"></uv-icon>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="user-text">
|
||||||
|
<view class="nickname">{{ userInfo.nickName || $t('user.clickToLogin') }}</view>
|
||||||
|
<view class="subtext">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('user.loginPrompt') }}</view>
|
||||||
|
</view>
|
||||||
|
<uv-icon name="arrow-right" size="16" color="#999"></uv-icon>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
<!-- <view class="assets-card">
|
<!-- <view class="assets-card">
|
||||||
@@ -24,71 +23,54 @@
|
|||||||
</view>
|
</view>
|
||||||
</view> -->
|
</view> -->
|
||||||
|
|
||||||
<view class="section">
|
<view class="section">
|
||||||
<!-- 广告轮播 -->
|
<!-- 广告轮播 -->
|
||||||
<view class="banner-card" v-if="bannerImages.length > 0">
|
<view class="banner-card" v-if="bannerImages.length > 0">
|
||||||
<swiper class="banner-swiper" :indicator-dots="bannerImages.length > 1"
|
<swiper class="banner-swiper" :indicator-dots="bannerImages.length > 1"
|
||||||
:autoplay="bannerImages.length > 1" :circular="true" :interval="3000">
|
:autoplay="bannerImages.length > 1" :circular="true" :interval="3000">
|
||||||
<swiper-item v-for="(image, index) in bannerImages" :key="index">
|
<swiper-item v-for="(image, index) in bannerImages" :key="index">
|
||||||
<image class="banner-image" :src="image" mode="aspectFill" @click="handleBannerClick(index)"
|
<image class="banner-image" :src="image" mode="aspectFill"
|
||||||
lazy-load="true">
|
@click="handleBannerClick(index)"></image>
|
||||||
</image>
|
</swiper-item>
|
||||||
</swiper-item>
|
</swiper>
|
||||||
</swiper>
|
</view>
|
||||||
</view>
|
<!-- 默认图片(当没有广告时显示) -->
|
||||||
<!-- 默认图片(当没有广告时显示) -->
|
<view class="banner-card" v-else @click="navigateTo('/pages/join/index')">
|
||||||
<!-- <view class="banner-card" v-else @click="navigateTo('/pages/join/index')">
|
<image class="banner-image" src="/static/userCenter_swiper.png" mode="aspectFill"></image>
|
||||||
<image class="banner-image" src="/static/userCenter_swiper.png" mode="aspectFill" lazy-load="true"></image>
|
</view>
|
||||||
</view> -->
|
|
||||||
<!-- <view class="section-title">常用服务</view> -->
|
<!-- <view class="section-title">常用服务</view> -->
|
||||||
<view class="list">
|
<view class="list">
|
||||||
<view class="list-item" @click="handleQuickReturn">
|
<view class="list-item" @click="handleQuickReturn">
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<image class="icon" src="/static/express_return.png" mode="aspectFit" lazy-load="true"></image>
|
<image class="icon" src="/static/express_return.png" mode="aspectFit"></image>
|
||||||
<text class="title">{{ $t('user.quickReturn') }}<text
|
<text class="title">{{ $t('user.quickReturn') }}<text style="font-size: 18rpx;">{{ $t('user.quickReturnDesc') }}</text></text>
|
||||||
style="font-size: 18rpx;">{{ $t('user.quickReturnDesc') }}</text></text>
|
|
||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="list-item" @click="navigateTo('/pages/expressReturn/index')" v-if="showMenuItem">
|
<view class="list-item" @click="navigateTo('/pages/expressReturn/index')" v-if="showMenuItem">
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<image class="icon" src="/static/express.png" mode="aspectFit" lazy-load="true"></image>
|
<image class="icon" src="/static/express.png" mode="aspectFit"></image>
|
||||||
<text class="title">{{ $t('user.expressReturn') }}</text>
|
<text class="title">{{ $t('user.expressReturn') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="list-item" @click="navigateTo('/subPackages/order/index')">
|
<view class="list-item" @click="navigateTo('/pages/order/index')">
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<image class="icon" src="/static/orderList.png" mode="aspectFit" lazy-load="true"></image>
|
<image class="icon" src="/static/orderList.png" mode="aspectFit"></image>
|
||||||
<text class="title">{{ $t('user.myOrders') }}</text>
|
<text class="title">{{ $t('user.myOrders') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="list-item" @click="navigateTo('/subPackages/business/my-card')">
|
<view class="list-item" @click="navigateTo('/pages/help/index')">
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<image class="icon" src="/static/my_member.png" mode="aspectFit" lazy-load="true"></image>
|
<image class="icon" src="/static/customer-service.png" mode="aspectFit"></image>
|
||||||
<text class="title">{{ $t('user.myCards') }}</text>
|
|
||||||
</view>
|
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
|
||||||
</view>
|
|
||||||
<view class="list-item" @click="navigateTo('/subPackages/business/my-coupon')">
|
|
||||||
<view class="left">
|
|
||||||
<image class="icon" src="/static/my_coupon.png" mode="aspectFit" lazy-load="true"></image>
|
|
||||||
<text class="title">{{ $t('user.myCoupons') }}</text>
|
|
||||||
</view>
|
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
|
||||||
</view>
|
|
||||||
<view class="list-item" @click="navigateTo('/subPackages/service/help/index')">
|
|
||||||
<view class="left">
|
|
||||||
<image class="icon" src="/static/customer-service.png" mode="aspectFit" lazy-load="true">
|
|
||||||
</image>
|
|
||||||
<text class="title">{{ $t('user.customerService') }}</text>
|
<text class="title">{{ $t('user.customerService') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="list-item" @click="navigateTo('/subPackages/service/feedback/index')">
|
<view class="list-item" @click="navigateTo('/pages/feedback/index')">
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<image class="icon" src="/static/suggess.png" mode="aspectFit" lazy-load="true"></image>
|
<image class="icon" src="/static/suggess.png" mode="aspectFit"></image>
|
||||||
<text class="title">{{ $t('user.feedback') }}</text>
|
<text class="title">{{ $t('user.feedback') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
@@ -100,18 +82,16 @@
|
|||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view> -->
|
</view> -->
|
||||||
<!-- #ifndef MP-ALIPAY -->
|
<view class="list-item" @click="navigateTo('/pages/join/index')">
|
||||||
<view class="list-item" @click="navigateTo('/subPackages/other/join/index')">
|
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<image class="icon" src="/static/peopleInWork.png" mode="aspectFit" lazy-load="true"></image>
|
<image class="icon" src="/static/peopleInWork.png" mode="aspectFit"></image>
|
||||||
<text class="title">{{ $t('user.cooperation') }}</text>
|
<text class="title">{{ $t('user.cooperation') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<!-- #endif -->
|
<view class="list-item" @click="navigateTo('/pages/setting/index')">
|
||||||
<view class="list-item" @click="navigateTo('/subPackages/user/setting/index')">
|
|
||||||
<view class="left">
|
<view class="left">
|
||||||
<image class="icon" src="/static/setting.png" mode="aspectFit" lazy-load="true"></image>
|
<image class="icon" src="/static/setting.png" mode="aspectFit"></image>
|
||||||
<text class="title">{{ $t('user.settings') }}</text>
|
<text class="title">{{ $t('user.settings') }}</text>
|
||||||
</view>
|
</view>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
@@ -121,11 +101,9 @@
|
|||||||
|
|
||||||
<view class="footer-agreements">
|
<view class="footer-agreements">
|
||||||
<view class="link-box">
|
<view class="link-box">
|
||||||
<text class="link"
|
<text class="link" @click="navigateTo('/pages/legal/agreement')">{{ $t('user.userAgreement') }}</text>
|
||||||
@click="navigateTo('/subPackages/other/legal/agreement')">{{ $t('user.userAgreement') }}</text>
|
|
||||||
<text class="sep">|</text>
|
<text class="sep">|</text>
|
||||||
<text class="link"
|
<text class="link" @click="navigateTo('/pages/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
|
||||||
@click="navigateTo('/subPackages/other/legal/privacy')">{{ $t('user.privacyPolicy') }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="version">{{ $t('user.version') }}{{ appVersion }}</view>
|
<view class="version">{{ $t('user.version') }}{{ appVersion }}</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -135,10 +113,10 @@
|
|||||||
<u-popup ref="authPopup" mode="center" border-radius="15" width="600rpx" @open="onPopupOpen" @close="onPopupClose">
|
<u-popup ref="authPopup" mode="center" border-radius="15" width="600rpx" @open="onPopupOpen" @close="onPopupClose">
|
||||||
<view class="auth-popup">
|
<view class="auth-popup">
|
||||||
<view class="auth-title">授权登录</view>
|
<view class="auth-title">授权登录</view>
|
||||||
<view class="auth-desc">获取您的微信头像、昵称等公开信息</view>
|
<view class="auth-desc">获取您的支付宝头像、昵称等公开信息</view>
|
||||||
<view class="auth-buttons">
|
<view class="auth-buttons">
|
||||||
<button class="cancel-btn" @click="closeAuthPopup">{{ $t('common.cancel') }}</button>
|
<button class="cancel-btn" @click="closeAuthPopup">取消</button>
|
||||||
<button class="confirm-btn" @click="getUserProfile">{{ $t('common.confirm') }}</button>
|
<button class="confirm-btn" @click="getUserProfile">确定</button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</u-popup>
|
||||||
@@ -157,26 +135,19 @@
|
|||||||
} from '@dcloudio/uni-app';
|
} from '@dcloudio/uni-app';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
wxLogin,
|
alipayLogin,
|
||||||
getUserInfo
|
getUserInfo
|
||||||
} from '@/util/index.js';
|
} from '../../util/index.js';
|
||||||
|
import {
|
||||||
|
uploadUserAvatar
|
||||||
|
} from '../../config/api/user.js'
|
||||||
import {
|
import {
|
||||||
uploadUserAvatar
|
URL
|
||||||
} from '@/config/api/user.js'
|
} from '../../config/url.js'
|
||||||
import {
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
getCurrentAdvertisement
|
|
||||||
} from '@/config/api/system.js'
|
|
||||||
import {
|
|
||||||
getInUseOrder
|
|
||||||
} from '@/config/api/order.js'
|
|
||||||
import {
|
|
||||||
useI18n
|
|
||||||
} from '@/utils/i18n.js'
|
|
||||||
// 设置页执行退出登录,此页不再直接调用
|
// 设置页执行退出登录,此页不再直接调用
|
||||||
|
|
||||||
const {
|
const { t: $t } = useI18n()
|
||||||
t
|
|
||||||
} = useI18n()
|
|
||||||
|
|
||||||
// 响应式状态
|
// 响应式状态
|
||||||
const userInfo = ref({});
|
const userInfo = ref({});
|
||||||
@@ -188,35 +159,47 @@
|
|||||||
|
|
||||||
const showMenuItem = ref(false)
|
const showMenuItem = ref(false)
|
||||||
const bannerImages = ref([]) // 广告图片列表
|
const bannerImages = ref([]) // 广告图片列表
|
||||||
const bannerImageList = ref([]) // 完整的广告配置列表(包含链接信息)
|
|
||||||
|
// 将语言代码转换为后端接受的格式
|
||||||
|
const convertLanguageCode = (lang) => {
|
||||||
|
// zh-CN -> zh_CN (转换下划线)
|
||||||
|
// en-US -> en_US (转换下划线)
|
||||||
|
return lang.replace(/-/g, '_')
|
||||||
|
}
|
||||||
|
|
||||||
// 获取广告图片
|
// 获取广告图片
|
||||||
const getBannerImages = async () => {
|
const getBannerImages = async () => {
|
||||||
try {
|
try {
|
||||||
let appPlatform;
|
// 获取当前语言设置
|
||||||
// #ifdef MP-WEIXIN
|
const currentLang = uni.getStorageSync('language') || 'zh-CN'
|
||||||
appPlatform = 'wechat'
|
const languageCode = convertLanguageCode(currentLang)
|
||||||
// #endif
|
|
||||||
// #ifdef MP-ALIPAY
|
console.log('加载个人中心广告,语言:', currentLang, '转换后:', languageCode)
|
||||||
appPlatform = 'ali'
|
|
||||||
// #endif
|
|
||||||
// 调用接口获取广告内容
|
// 调用接口获取广告内容
|
||||||
const res = await getCurrentAdvertisement({
|
const res = await uni.request({
|
||||||
appPlatform: appPlatform, // 微信平台
|
url: `${URL}/device/advertisementConfig/current`,
|
||||||
appType: 'user', // 用户端
|
method: 'GET',
|
||||||
pictureLocation: 'userProfile_banner'
|
header: {
|
||||||
|
'Content-Language': languageCode
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
appPlatform: 'alipay', // 支付宝平台
|
||||||
|
appType: 'user' // 用户端
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
console.log('个人中心广告响应:', res)
|
||||||
// 使用 imageList 字段(包含图片和链接信息)
|
|
||||||
const imageList = res.data.imageList || []
|
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
|
||||||
if (imageList.length > 0) {
|
// 使用 files 字段(图片列表)
|
||||||
bannerImageList.value = imageList
|
const files = res.data.data.files || []
|
||||||
// 提取图片URL用于展示
|
if (files.length > 0) {
|
||||||
bannerImages.value = imageList.map(item => item.imageUrl)
|
bannerImages.value = files
|
||||||
|
console.log('个人中心广告加载成功,图片数量:', files.length)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('获取个人中心广告失败:', res?.msg || '未知错误')
|
console.warn('获取个人中心广告失败:', res.data?.msg || '未知错误')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取个人中心广告失败:', error)
|
console.error('获取个人中心广告失败:', error)
|
||||||
@@ -225,54 +208,17 @@
|
|||||||
|
|
||||||
// 处理广告点击
|
// 处理广告点击
|
||||||
const handleBannerClick = (index) => {
|
const handleBannerClick = (index) => {
|
||||||
if (!bannerImageList.value || !bannerImageList.value[index]) {
|
console.log('点击广告:', index)
|
||||||
return
|
// 可以根据需要添加跳转逻辑
|
||||||
}
|
// 例如:navigateTo('/pages/join/index')
|
||||||
|
|
||||||
const config = bannerImageList.value[index]
|
|
||||||
|
|
||||||
// 根据链接类型进行跳转
|
|
||||||
if (config.linkType === 'miniapp' && config.appId) {
|
|
||||||
// 跳转到外部小程序
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
uni.navigateToMiniProgram({
|
|
||||||
appId: config.appId,
|
|
||||||
path: config.linkUrl || '',
|
|
||||||
success: () => {},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('跳转小程序失败:', err)
|
|
||||||
uni.showToast({
|
|
||||||
title: t('common.loadFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
// #ifndef MP-WEIXIN
|
|
||||||
uni.showToast({
|
|
||||||
title: t('auth.pleaseUseInWechat'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
} else if (config.linkType === 'external' && config.linkUrl) {
|
|
||||||
// 跳转到外部链接(H5页面)
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `subPackages/other/webview/index?url=${encodeURIComponent(config.linkUrl)}`
|
|
||||||
})
|
|
||||||
} else if (config.linkType === 'internal' && config.linkUrl) {
|
|
||||||
// 跳转到内部页面
|
|
||||||
uni.navigateTo({
|
|
||||||
url: config.linkUrl
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面加载时初始化
|
// 页面加载时初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('user.personalCenter')
|
title: $t('user.personalCenter')
|
||||||
})
|
})
|
||||||
// getInfo();
|
getInfo();
|
||||||
initVersion();
|
initVersion();
|
||||||
getBannerImages(); // 加载广告
|
getBannerImages(); // 加载广告
|
||||||
});
|
});
|
||||||
@@ -287,6 +233,7 @@
|
|||||||
const getInfo = async () => {
|
const getInfo = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await getUserInfo();
|
const res = await getUserInfo();
|
||||||
|
console.log('User info response:', res);
|
||||||
|
|
||||||
if (res.code == 401 || res.code == 40101) {
|
if (res.code == 401 || res.code == 40101) {
|
||||||
redirectToLogin()
|
redirectToLogin()
|
||||||
@@ -310,8 +257,9 @@
|
|||||||
deposit.value = res.data.balanceAmount || '0.00';
|
deposit.value = res.data.balanceAmount || '0.00';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('获取用户信息失败:', error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.getUserInfoFailed'),
|
title: $t('user.getUserInfoFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -319,11 +267,12 @@
|
|||||||
|
|
||||||
// 初始化应用版本号(多端兼容,取可用信息)
|
// 初始化应用版本号(多端兼容,取可用信息)
|
||||||
const initVersion = () => {
|
const initVersion = () => {
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-ALIPAY
|
||||||
try {
|
try {
|
||||||
const info = wx.getAccountInfoSync && wx.getAccountInfoSync();
|
// 支付宝小程序获取版本号
|
||||||
if (info && info.miniProgram && info.miniProgram.version) {
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
appVersion.value = info.miniProgram.version;
|
if (systemInfo && systemInfo.version) {
|
||||||
|
appVersion.value = systemInfo.version;
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
// #endif
|
// #endif
|
||||||
@@ -346,11 +295,11 @@
|
|||||||
`${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
|
`${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
|
||||||
const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
|
const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: `/subPackages/user/login/index?redirect=${redirect}`
|
url: `/pages/login/index?redirect=${redirect}`
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: '/subPackages/user/login/index'
|
url: '/pages/login/index'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,29 +315,43 @@
|
|||||||
const handleQuickReturn = async () => {
|
const handleQuickReturn = async () => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.loading')
|
title: $t('common.loading')
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取使用中的订单
|
// 获取使用中的订单
|
||||||
const res = await getInUseOrder();
|
const res = await uni.request({
|
||||||
|
url: `${URL}/app/order/inUse`,
|
||||||
|
method: 'GET',
|
||||||
|
header: {
|
||||||
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
if (res.statusCode === 401 || res.data?.code === 401 || res.data?.code === 40101) {
|
||||||
const inUseOrder = res.data;
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.statusCode === 200 && res.data.code === 200 && res.data.data) {
|
||||||
|
const inUseOrder = res.data.data;
|
||||||
// 跳转到统一订单详情页面
|
// 跳转到统一订单详情页面
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${inUseOrder.deviceNo}`
|
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${inUseOrder.deviceNo}`
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.noOrder'),
|
title: $t('order.noOrder'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
uni.hideLoading();
|
||||||
|
console.error('获取使用中订单失败:', error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.getOrderFailed'),
|
title: $t('order.getOrderFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -406,12 +369,13 @@
|
|||||||
redirectToLogin()
|
redirectToLogin()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-ALIPAY
|
||||||
getUserProfile()
|
// 支付宝小程序通过页面跳转处理用户资料
|
||||||
|
navigateTo('/pages/userProfile/index')
|
||||||
// #endif
|
// #endif
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-ALIPAY
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('auth.pleaseUseInWechat'),
|
title: $t('auth.pleaseUseInAlipay'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
@@ -428,13 +392,13 @@
|
|||||||
const avatarLocalPath = e?.detail?.avatarUrl
|
const avatarLocalPath = e?.detail?.avatarUrl
|
||||||
if (!avatarLocalPath) {
|
if (!avatarLocalPath) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.noAvatar'),
|
title: '未选择头像',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.uploading'),
|
title: $t('common.uploading'),
|
||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
const uploadRes = await uploadUserAvatar(avatarLocalPath)
|
const uploadRes = await uploadUserAvatar(avatarLocalPath)
|
||||||
@@ -447,13 +411,14 @@
|
|||||||
uni.setStorageSync('userInfo', userInfo.value)
|
uni.setStorageSync('userInfo', userInfo.value)
|
||||||
}
|
}
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.avatarUpdated'),
|
title: '头像已更新',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
await getInfo()
|
await getInfo()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('选择/上传头像失败:', err)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.avatarUploadFailed'),
|
title: '头像更新失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -471,77 +436,49 @@
|
|||||||
|
|
||||||
// 弹窗打开事件处理
|
// 弹窗打开事件处理
|
||||||
const onPopupOpen = () => {
|
const onPopupOpen = () => {
|
||||||
|
console.log('授权弹窗已打开');
|
||||||
isPopupVisible.value = true;
|
isPopupVisible.value = true;
|
||||||
// 这里可以添加弹窗打开后的逻辑
|
// 这里可以添加弹窗打开后的逻辑
|
||||||
};
|
};
|
||||||
|
|
||||||
// 弹窗关闭事件处理
|
// 弹窗关闭事件处理
|
||||||
const onPopupClose = () => {
|
const onPopupClose = () => {
|
||||||
|
console.log('授权弹窗已关闭');
|
||||||
isPopupVisible.value = false;
|
isPopupVisible.value = false;
|
||||||
// 这里可以添加弹窗关闭后的逻辑
|
// 这里可以添加弹窗关闭后的逻辑
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取微信用户个人信息
|
// 获取支付宝用户个人信息(已废弃,使用页面跳转方式)
|
||||||
const getUserProfile = () => {
|
const getUserProfile = () => {
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-ALIPAY
|
||||||
uni.showLoading({
|
// 支付宝小程序通过页面跳转处理用户资料
|
||||||
title: t('common.getting'),
|
navigateTo('/pages/userProfile/index')
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
wx.getUserProfile({
|
|
||||||
desc: '用于完善会员资料',
|
|
||||||
success: (res) => {
|
|
||||||
console.log('获取用户信息成功:', res);
|
|
||||||
updateUserInfo(res.userInfo);
|
|
||||||
uploadAvatarAndRefresh(res.userInfo);
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('获取用户信息失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: t('user.getUserInfoFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
complete: () => {
|
|
||||||
uni.hideLoading();
|
|
||||||
closeAuthPopup();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-ALIPAY
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('auth.pleaseUseInWechat'),
|
title: $t('auth.pleaseUseInAlipay'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
closeAuthPopup();
|
closeAuthPopup();
|
||||||
// #endif
|
// #endif
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新用户信息
|
// 更新用户信息(支付宝小程序通过页面跳转处理)
|
||||||
const updateUserInfo = async (wxUserInfo) => {
|
const updateUserInfo = async (alipayUserInfo) => {
|
||||||
try {
|
try {
|
||||||
// 更新本地用户信息
|
// 更新本地用户信息
|
||||||
const updatedInfo = {
|
const updatedInfo = {
|
||||||
...userInfo.value,
|
...userInfo.value,
|
||||||
nickName: wxUserInfo.nickName,
|
nickName: alipayUserInfo.nickName,
|
||||||
avatar: wxUserInfo.avatarUrl
|
avatar: alipayUserInfo.avatarUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
userInfo.value = updatedInfo;
|
userInfo.value = updatedInfo;
|
||||||
uni.setStorageSync('userInfo', updatedInfo);
|
uni.setStorageSync('userInfo', updatedInfo);
|
||||||
|
|
||||||
// 这里可以添加调用后端API更新用户信息的代码
|
|
||||||
// const updateRes = await updateUserInfoApi({
|
|
||||||
// openId: openId.value,
|
|
||||||
// nickName: wxUserInfo.nickName,
|
|
||||||
// avatarUrl: wxUserInfo.avatarUrl,
|
|
||||||
// gender: wxUserInfo.gender
|
|
||||||
// });
|
|
||||||
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.updateSuccess'),
|
title: $t('user.updateSuccess'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -550,24 +487,24 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('更新用户信息失败:', error);
|
console.error('更新用户信息失败:', error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.updateFailed'),
|
title: $t('user.updateFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 下载并上传头像,更新用户信息
|
// 下载并上传头像,更新用户信息
|
||||||
const uploadAvatarAndRefresh = async (wxUserInfo) => {
|
const uploadAvatarAndRefresh = async (alipayUserInfo) => {
|
||||||
try {
|
try {
|
||||||
const avatarUrl = wxUserInfo?.avatarUrl
|
const avatarUrl = alipayUserInfo?.avatarUrl
|
||||||
if (!avatarUrl) {
|
if (!avatarUrl) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.noAvatarUrl'),
|
title: '未获取到头像地址',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 下载微信头像为本地临时文件
|
// 下载支付宝头像为本地临时文件
|
||||||
const tempFilePath = await new Promise((resolve, reject) => {
|
const tempFilePath = await new Promise((resolve, reject) => {
|
||||||
uni.downloadFile({
|
uni.downloadFile({
|
||||||
url: avatarUrl,
|
url: avatarUrl,
|
||||||
@@ -576,7 +513,7 @@
|
|||||||
resolve(res.tempFilePath)
|
resolve(res.tempFilePath)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reject(new Error(t('user.avatarDownloadFailed')))
|
reject(new Error('头像下载失败'))
|
||||||
},
|
},
|
||||||
fail: reject
|
fail: reject
|
||||||
})
|
})
|
||||||
@@ -593,13 +530,14 @@
|
|||||||
uni.setStorageSync('userInfo', userInfo.value)
|
uni.setStorageSync('userInfo', userInfo.value)
|
||||||
}
|
}
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.avatarUpdated'),
|
title: '头像已更新',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
await getInfo()
|
await getInfo()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('头像上传失败:', error)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('user.avatarUploadFailed'),
|
title: '头像上传失败',
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -618,7 +556,7 @@
|
|||||||
// 关于我们
|
// 关于我们
|
||||||
const handleAboutUs = () => {
|
const handleAboutUs = () => {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('help.functionDeveloping'),
|
title: $t('help.functionDeveloping'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -626,7 +564,7 @@
|
|||||||
// 隐私政策
|
// 隐私政策
|
||||||
const handlePrivacyPolicy = () => {
|
const handlePrivacyPolicy = () => {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('help.functionDeveloping'),
|
title: $t('help.functionDeveloping'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<view class="order-list">
|
<view class="order-list">
|
||||||
<view class="empty-state" v-if="orderList.length === 0">
|
<view class="empty-state" v-if="orderList.length === 0">
|
||||||
<view class="empty-icon">
|
<view class="empty-icon">
|
||||||
<image src="/static/orderList.png" mode="aspectFill" class="empty-icon" lazy-load="true"></image>
|
<image src="/static/orderList.png" mode="aspectFill" class="empty-icon"></image>
|
||||||
</view>
|
</view>
|
||||||
<text class="empty-text">{{ $t('order.noOrderRecord') }}</text>
|
<text class="empty-text">{{ $t('order.noOrderRecord') }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -35,8 +35,7 @@
|
|||||||
import {
|
import {
|
||||||
ref,
|
ref,
|
||||||
reactive,
|
reactive,
|
||||||
onMounted,
|
onMounted
|
||||||
onUnmounted
|
|
||||||
} from 'vue';
|
} from 'vue';
|
||||||
import OrderItemCard from '../../components/OrderItemCard.vue';
|
import OrderItemCard from '../../components/OrderItemCard.vue';
|
||||||
import {
|
import {
|
||||||
@@ -45,16 +44,28 @@
|
|||||||
import {
|
import {
|
||||||
getOrderList,
|
getOrderList,
|
||||||
queryById,
|
queryById,
|
||||||
getOrderByOrderNo,
|
|
||||||
getOrderByOrderNoScorePayStatus,
|
getOrderByOrderNoScorePayStatus,
|
||||||
cancelOrder
|
cancelOrder
|
||||||
} from '../../config/api/order.js';
|
} from '../../config/api/order.js';
|
||||||
import {
|
import {
|
||||||
getDeviceInfo
|
confirmPaymentAndRent
|
||||||
} from '../../config/api/device.js';
|
} from '../../config/api/device.js';
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
import {
|
||||||
|
updateUserBalance
|
||||||
|
} from '../../config/api/user.js';
|
||||||
|
import {
|
||||||
|
URL
|
||||||
|
} from '../../config/url.js';
|
||||||
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
|
// 设置页面标题
|
||||||
|
onMounted(() => {
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: $t('order.myOrders')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// 初始化状态
|
// 初始化状态
|
||||||
const currentTab = ref(0);
|
const currentTab = ref(0);
|
||||||
@@ -63,62 +74,62 @@
|
|||||||
// 订单状态映射
|
// 订单状态映射
|
||||||
const orderStatusMap = reactive({
|
const orderStatusMap = reactive({
|
||||||
'0': {
|
'0': {
|
||||||
get text() { return t('order.waitingForPayment') },
|
get text() { return $t('order.waitingForPayment') },
|
||||||
class: 'status-waiting'
|
class: 'status-waiting'
|
||||||
},
|
},
|
||||||
'1': {
|
'1': {
|
||||||
get text() { return t('order.inUse') },
|
get text() { return $t('order.inUse') },
|
||||||
class: 'status-using'
|
class: 'status-using'
|
||||||
},
|
},
|
||||||
'2': {
|
'2': {
|
||||||
get text() { return t('order.finished') },
|
get text() { return $t('order.finished') },
|
||||||
class: 'status-finished'
|
class: 'status-finished'
|
||||||
},
|
},
|
||||||
'3': {
|
'3': {
|
||||||
get text() { return t('order.cancelled') },
|
get text() { return $t('order.cancelled') },
|
||||||
class: 'status-cancelled'
|
class: 'status-cancelled'
|
||||||
},
|
},
|
||||||
'waiting_for_payment': {
|
'waiting_for_payment': {
|
||||||
get text() { return t('order.waitingForPayment') },
|
get text() { return $t('order.waitingForPayment') },
|
||||||
class: 'status-waiting'
|
class: 'status-waiting'
|
||||||
},
|
},
|
||||||
'in_used': {
|
'in_used': {
|
||||||
get text() { return t('order.inUse') },
|
get text() { return $t('order.inUse') },
|
||||||
class: 'status-using'
|
class: 'status-using'
|
||||||
},
|
},
|
||||||
'used_done': {
|
'used_done': {
|
||||||
get text() { return t('order.finished') },
|
get text() { return $t('order.finished') },
|
||||||
class: 'status-finished'
|
class: 'status-finished'
|
||||||
},
|
},
|
||||||
'order_cancelled': {
|
'order_cancelled': {
|
||||||
get text() { return t('order.cancelled') },
|
get text() { return $t('order.cancelled') },
|
||||||
class: 'status-cancelled'
|
class: 'status-cancelled'
|
||||||
},
|
},
|
||||||
'express_return': {
|
'express_return': {
|
||||||
get text() { return t('express.title') },
|
get text() { return $t('express.title') },
|
||||||
class: 'status-express-return'
|
class: 'status-express-return'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 订单状态标签
|
// 订单状态标签
|
||||||
const orderStatusTabs = reactive([{
|
const orderStatusTabs = reactive([{
|
||||||
get text() { return t('common.all') },
|
get text() { return $t('common.all') },
|
||||||
status: []
|
status: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
get text() { return t('order.waitingForPayment') },
|
get text() { return $t('order.waitingForPayment') },
|
||||||
status: ['waiting_for_payment']
|
status: ['waiting_for_payment']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
get text() { return t('order.inUse') },
|
get text() { return $t('order.inUse') },
|
||||||
status: ['in_used']
|
status: ['in_used']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
get text() { return t('order.finished') },
|
get text() { return $t('order.finished') },
|
||||||
status: ['used_done']
|
status: ['used_done']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
get text() { return t('order.cancelled') },
|
get text() { return $t('order.cancelled') },
|
||||||
status: ['order_cancelled']
|
status: ['order_cancelled']
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
@@ -139,9 +150,7 @@
|
|||||||
|
|
||||||
// 格式化订单数据
|
// 格式化订单数据
|
||||||
const formattedOrder = {
|
const formattedOrder = {
|
||||||
orderNo: orderData.orderNo || orderData.orderId,
|
orderNo: orderData.orderId,
|
||||||
orderId: orderData.orderId,
|
|
||||||
orderStatus: orderData.orderStatus,
|
|
||||||
status: orderData.orderStatus,
|
status: orderData.orderStatus,
|
||||||
deviceId: orderData.deviceNo,
|
deviceId: orderData.deviceNo,
|
||||||
payWay: orderData.payWay,
|
payWay: orderData.payWay,
|
||||||
@@ -149,8 +158,7 @@
|
|||||||
endTime: orderData.endTime || '',
|
endTime: orderData.endTime || '',
|
||||||
positionName: orderData.positionName || orderData.positionLocation || '',
|
positionName: orderData.positionName || orderData.positionLocation || '',
|
||||||
deviceName: orderData.deviceName || '',
|
deviceName: orderData.deviceName || '',
|
||||||
amount: orderData.payAmount || orderData.actualDeviceAmount || orderData.currentFee || orderData.residueAmount || '0.00',
|
amount: orderData.payAmount || orderData.actualDeviceAmount || orderData.currentFee || orderData.residueAmount || '0.00'
|
||||||
pauseTime: orderData.pauseTime != null ? orderData.pauseTime : orderData.pause_time
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 将订单添加到列表开头
|
// 将订单添加到列表开头
|
||||||
@@ -207,57 +215,33 @@
|
|||||||
endTime: item.endTime || '',
|
endTime: item.endTime || '',
|
||||||
positionName: item.positionName || item.positionLocation || '',
|
positionName: item.positionName || item.positionLocation || '',
|
||||||
deviceName: item.deviceName || '',
|
deviceName: item.deviceName || '',
|
||||||
amount: item.payAmount || item.actualDeviceAmount || item.currentFee || item.residueAmount || '0.00',
|
amount: item.payAmount || item.actualDeviceAmount || item.currentFee || item.residueAmount || '0.00'
|
||||||
pauseTime: item.pauseTime != null ? item.pauseTime : item.pause_time
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取订单列表失败:', error);
|
console.error('获取订单列表失败:', error);
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.getOrderListFailed'),
|
title: $t('order.getOrderListFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理订单完成事件
|
|
||||||
const handleOrderCompleted = (orderData) => {
|
|
||||||
console.log('订单列表页收到订单完成事件:', orderData)
|
|
||||||
// 刷新订单列表,根据当前选中的标签刷新对应状态的订单
|
|
||||||
const statusList = orderStatusTabs[currentTab.value].status[0]
|
|
||||||
loadOrderList(statusList)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置页面标题并监听订单完成事件
|
|
||||||
onMounted(() => {
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('order.myOrders')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听订单完成事件
|
|
||||||
uni.$on('orderCompleted', handleOrderCompleted)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 页面卸载时移除事件监听
|
|
||||||
onUnmounted(() => {
|
|
||||||
uni.$off('orderCompleted', handleOrderCompleted)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 同步订单状态
|
// 同步订单状态
|
||||||
const getOrderStatus = async (order) => {
|
const getOrderStatus = async (order) => {
|
||||||
try {
|
try {
|
||||||
const res = await getOrderByOrderNoScorePayStatus(order.orderNo);
|
const res = await getOrderByOrderNoScorePayStatus(order.orderNo);
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.syncSuccess'),
|
title: $t('order.syncSuccess'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
await loadOrderList(orderStatusTabs[currentTab.value].status);
|
await loadOrderList(orderStatusTabs[currentTab.value].status);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.syncFailed'),
|
title: $t('order.syncFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -280,92 +264,75 @@
|
|||||||
navigateToOrderDetail(order);
|
navigateToOrderDetail(order);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// 立即支付
|
||||||
* 待支付订单:与设备租借押金流程一致,进入支付详情页(微信/支付宝/H5-Antom/DANA 等在支付页内选择并完成)
|
|
||||||
*/
|
|
||||||
const handlePayment = async (order) => {
|
const handlePayment = async (order) => {
|
||||||
const orderNo = order.orderNo
|
|
||||||
const orderId = order.orderId || order.orderNo
|
|
||||||
if (!orderId && !orderNo) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.orderInfoMissing'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.loading')
|
title: $t('common.processing')
|
||||||
})
|
});
|
||||||
|
|
||||||
let od = null
|
// 调用后端创建支付宝支付订单接口
|
||||||
if (orderNo) {
|
const res = await uni.request({
|
||||||
const res = await getOrderByOrderNo(orderNo)
|
url: `${URL || 'http://127.0.0.1:8080'}/app/alipay-payment/create/${order.orderNo}`,
|
||||||
if (res && res.code === 200 && res.data) {
|
method: 'GET',
|
||||||
od = res.data
|
header: {
|
||||||
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
if (!od && orderId) {
|
|
||||||
const res = await queryById(orderId)
|
|
||||||
if (res && res.code === 200 && res.data) {
|
|
||||||
od = res.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const idForPay = String(od?.orderId ?? orderId ?? orderNo ?? '')
|
if (res.statusCode === 200 && res.data.code === 200) {
|
||||||
const qsParts = [`orderId=${encodeURIComponent(idForPay)}`]
|
const payParams = res.data.data;
|
||||||
|
|
||||||
if (od) {
|
// 调用支付宝支付
|
||||||
const deposit = parseFloat(od.depositAmount)
|
await uni.requestPayment({
|
||||||
const packagePrice = parseFloat(od.unitPrice)
|
provider: 'alipay',
|
||||||
if (Number.isFinite(packagePrice)) {
|
...payParams,
|
||||||
qsParts.push(`packagePrice=${packagePrice}`)
|
success: async () => {
|
||||||
}
|
uni.showToast({
|
||||||
if (Number.isFinite(deposit)) {
|
title: $t('payment.paymentSuccess'),
|
||||||
const totalAmount = deposit.toFixed(2)
|
icon: 'success'
|
||||||
qsParts.push(`totalAmount=${encodeURIComponent(totalAmount)}`)
|
});
|
||||||
qsParts.push(`depositAmount=${encodeURIComponent(String(deposit))}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const deviceNo = od.deviceNo || order.deviceId
|
// 更新用户余额
|
||||||
if (deviceNo) {
|
try {
|
||||||
try {
|
await updateUserBalance(order.orderId || order.orderNo);
|
||||||
const devRes = await getDeviceInfo(deviceNo)
|
} catch (error) {
|
||||||
const feeCfg = devRes?.data?.device?.feeConfig
|
console.warn('更新用户余额失败:', error);
|
||||||
if (feeCfg) {
|
|
||||||
qsParts.push(`feeConfig=${encodeURIComponent(feeCfg)}`)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.warn('获取设备 feeConfig 失败,支付页将仅依赖订单信息:', e)
|
// 刷新订单列表
|
||||||
|
await loadOrderList(orderStatusTabs[currentTab.value].status);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('支付失败:', err);
|
||||||
|
throw new Error($t('payment.paymentFailedRetry'));
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error(res.data.msg || '创建支付订单失败');
|
||||||
}
|
}
|
||||||
|
|
||||||
uni.hideLoading()
|
uni.hideLoading();
|
||||||
uni.navigateTo({
|
|
||||||
url: `/subPackages/order/payment?${qsParts.join('&')}`
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.hideLoading()
|
uni.hideLoading();
|
||||||
console.error('跳转支付页失败:', error)
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: error?.message || t('payment.paymentFailed'),
|
title: error.message || $t('payment.paymentFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
const handleCancelOrder = async (order) => {
|
const handleCancelOrder = async (order) => {
|
||||||
try {
|
try {
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('order.confirmCancel'),
|
title: $t('order.confirmCancel'),
|
||||||
content: t('order.confirmCancelContent'),
|
content: $t('order.confirmCancelContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.processing')
|
title: $t('common.processing')
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await cancelOrder({
|
const result = await cancelOrder({
|
||||||
@@ -375,14 +342,14 @@
|
|||||||
if (result) {
|
if (result) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('order.cancelSuccess'),
|
title: $t('order.cancelSuccess'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 刷新订单列表
|
// 刷新订单列表
|
||||||
await loadOrderList();
|
await loadOrderList();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.msg || t('order.cancelFailed'));
|
throw new Error(result.msg || $t('order.cancelFailed'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,7 +357,7 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
uni.hideLoading();
|
uni.hideLoading();
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: error.message || t('order.cancelFailed'),
|
title: error.message || $t('order.cancelFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,588 @@
|
|||||||
|
<template>
|
||||||
|
<view class="payment-container">
|
||||||
|
<!-- 订单状态 -->
|
||||||
|
<view class="status-card">
|
||||||
|
<view class="status-icon" :class="orderStatus.class"></view>
|
||||||
|
<view class="status-text">{{ orderStatus.text }}</view>
|
||||||
|
<view class="status-desc">{{ orderStatus.desc }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单信息 -->
|
||||||
|
<view class="order-card">
|
||||||
|
<view class="card-title">{{ $t('payment.orderInfo') }}</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('order.orderNo') }}</text>
|
||||||
|
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('order.deviceNo') }}</text>
|
||||||
|
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('payment.createTime') }}</text>
|
||||||
|
<text class="value">{{ orderInfo.createTime || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('payment.contactPhone') }}</text>
|
||||||
|
<text class="value">{{ orderInfo.phone || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 费用信息 -->
|
||||||
|
<view class="price-card">
|
||||||
|
<view class="card-title">{{ $t('payment.feeInfo') }}</view>
|
||||||
|
<view class="price-item">
|
||||||
|
<text class="label">{{ $t('payment.deposit') }}</text>
|
||||||
|
<text class="value">¥{{ orderInfo.deposit || '99.00' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="price-item">
|
||||||
|
<text class="label">{{ $t('payment.package') }}</text>
|
||||||
|
<text class="value">{{ packageInfo.price }}{{ $t('unit.yuan') }}/{{ packageInfo.time }}{{ $t('time.hour') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="price-item total">
|
||||||
|
<text class="label">{{ $t('payment.total') }}</text>
|
||||||
|
<text class="value">¥{{ totalAmount }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<view class="bottom-bar">
|
||||||
|
<view class="total-amount">
|
||||||
|
<text>{{ $t('payment.total') }}:</text>
|
||||||
|
<text class="amount">¥{{ totalAmount }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="pay-btn" @click="handlePayment">{{ $t('payment.payNow') }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { queryById, updateOrderPackage } from '@/config/api/order.js'
|
||||||
|
import { getDeviceInfo } from '@/config/api/device.js'
|
||||||
|
import { updateUserBalance } from '@/config/api/user.js'
|
||||||
|
import {
|
||||||
|
URL
|
||||||
|
}from"@/config/url.js"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
orderId: null,
|
||||||
|
deviceNo: null,
|
||||||
|
orderInfo: {},
|
||||||
|
packageInfo: {
|
||||||
|
time: '',
|
||||||
|
price: '0.00'
|
||||||
|
},
|
||||||
|
deviceInfo: null,
|
||||||
|
passedTotalAmount: null,
|
||||||
|
passedDepositAmount: null,
|
||||||
|
orderStatus: {
|
||||||
|
get text() { return this.$t('payment.waitingForPayment') },
|
||||||
|
get desc() { return this.$t('payment.pleasePayIn15Min') },
|
||||||
|
class: 'waiting'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
totalAmount() {
|
||||||
|
if (this.passedTotalAmount !== null) {
|
||||||
|
return parseFloat(this.passedTotalAmount).toFixed(2);
|
||||||
|
}
|
||||||
|
const deposit = parseFloat(this.orderInfo.deposit || this.passedDepositAmount || 99)
|
||||||
|
const packagePrice = parseFloat(this.packageInfo.price || 0)
|
||||||
|
return (deposit + packagePrice).toFixed(2)
|
||||||
|
},
|
||||||
|
// 计算每小时的价格
|
||||||
|
hourlyRate() {
|
||||||
|
const price = parseFloat(this.packageInfo.price || 0);
|
||||||
|
let time = parseFloat(this.packageInfo.time || 1);
|
||||||
|
|
||||||
|
// 如果时间单位不是小时(例如分钟),需要转换
|
||||||
|
if (this.packageInfo.time && this.packageInfo.time.includes('分钟')) {
|
||||||
|
time = time / 60; // 将分钟转换为小时
|
||||||
|
} else if (this.packageInfo.time && this.packageInfo.time.includes('次')) {
|
||||||
|
// 按次计费时,暂时显示单次价格
|
||||||
|
return price.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 避免除以零
|
||||||
|
if (time <= 0) time = 1;
|
||||||
|
|
||||||
|
// 计算每小时价格
|
||||||
|
return (price / time).toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
// 设置页面标题
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: this.$t('payment.orderPayment')
|
||||||
|
})
|
||||||
|
|
||||||
|
if (options && options.orderId) {
|
||||||
|
this.orderId = options.orderId
|
||||||
|
|
||||||
|
if (options.totalAmount) {
|
||||||
|
this.passedTotalAmount = options.depositAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.depositAmount) {
|
||||||
|
this.passedDepositAmount = options.depositAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果URL中包含了feeConfig参数,保存它
|
||||||
|
if (options.feeConfig) {
|
||||||
|
try {
|
||||||
|
console.log('从URL获取到feeConfig:', options.feeConfig)
|
||||||
|
const feeConfigStr = decodeURIComponent(options.feeConfig)
|
||||||
|
// 创建一个临时的deviceInfo对象保存feeConfig
|
||||||
|
this.deviceInfo = { feeConfig: feeConfigStr }
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析URL中的feeConfig失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loadOrderInfo()
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('order.orderNotExist'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/index/index'
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 加载订单信息
|
||||||
|
async loadOrderInfo() {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: this.$t('common.loading')
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await queryById(this.orderId)
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
const orderData = res.data
|
||||||
|
|
||||||
|
// 处理创建时间,确保显示的是格式化后的时间
|
||||||
|
let formattedTime;
|
||||||
|
try {
|
||||||
|
// 如果orderData.createTime存在并且是有效的日期字符串/时间戳,则格式化它
|
||||||
|
if (orderData.createTime) {
|
||||||
|
formattedTime = this.formatTime(new Date(orderData.createTime));
|
||||||
|
} else {
|
||||||
|
// 如果createTime不存在,使用当前时间作为创建时间
|
||||||
|
formattedTime = this.formatTime(new Date());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('时间格式化错误:', e);
|
||||||
|
formattedTime = this.formatTime(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.orderInfo = {
|
||||||
|
orderNo: orderData.orderNo || orderData.orderId,
|
||||||
|
deviceNo: orderData.deviceNo,
|
||||||
|
createTime: formattedTime,
|
||||||
|
phone: orderData.phone,
|
||||||
|
deposit: this.passedDepositAmount || orderData.depositAmount || '99.00',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接从订单数据中获取套餐信息
|
||||||
|
if (orderData.packageTime && orderData.packagePrice) {
|
||||||
|
// 将分钟转换为小时
|
||||||
|
const timeInHours = (parseFloat(orderData.packageTime) / 60).toFixed(1);
|
||||||
|
this.packageInfo = {
|
||||||
|
time: timeInHours.toString(),
|
||||||
|
price: orderData.packagePrice.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备信息(但不再用于设置套餐信息)
|
||||||
|
this.deviceNo = orderData.deviceNo;
|
||||||
|
await this.loadDeviceInfo();
|
||||||
|
} else {
|
||||||
|
throw new Error('获取订单信息失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '获取订单信息失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加载设备信息
|
||||||
|
async loadDeviceInfo() {
|
||||||
|
if (!this.deviceNo) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getDeviceInfo(this.deviceNo);
|
||||||
|
if (res.code === 200 && res.data) {
|
||||||
|
this.deviceInfo = res.data.device;
|
||||||
|
|
||||||
|
// 设置存款金额
|
||||||
|
if (this.deviceInfo && this.deviceInfo.depositAmount) {
|
||||||
|
this.orderInfo.deposit = this.deviceInfo.depositAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取设备信息失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 处理支付
|
||||||
|
async handlePayment() {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: this.$t('common.processing')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 调用后端创建支付宝支付订单接口
|
||||||
|
const res = await uni.request({
|
||||||
|
url: `${URL || 'http://127.0.0.1:8080'}/app/alipay-payment/create/${this.orderInfo.orderNo}`,
|
||||||
|
method: 'GET',
|
||||||
|
header: {
|
||||||
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.statusCode === 200 && res.data.code === 200) {
|
||||||
|
const payParams = res.data.data
|
||||||
|
|
||||||
|
// 调用支付宝支付
|
||||||
|
await uni.requestPayment({
|
||||||
|
provider: 'alipay',
|
||||||
|
...payParams,
|
||||||
|
success: async () => {
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('payment.paymentSuccess'),
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新用户余额
|
||||||
|
try {
|
||||||
|
await updateUserBalance(this.orderId);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('更新用户余额失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付成功后直接跳转到订单页面,不再轮询
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: `/pages/order/index?orderId=${this.orderId}`
|
||||||
|
});
|
||||||
|
}, 1500);
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('支付失败:', err)
|
||||||
|
throw new Error(this.$t('payment.paymentFailedRetry'))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error(res.data.msg || '创建支付订单失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || '支付失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送租借指令
|
||||||
|
async sendRentCommand() {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: this.$t('common.processing')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 调用发送租借指令的接口
|
||||||
|
const res = await this.sendRentRequest()
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('device.rentSuccess'),
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
|
||||||
|
// 跳转到订单列表页面
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: `/pages/order/index?orderId=${this.orderId}`
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
} else {
|
||||||
|
throw new Error(res.msg || this.$t('device.rentFailed'))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || this.$t('device.rentFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 发送租借请求
|
||||||
|
sendRentRequest() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.request({
|
||||||
|
url: `${URL || 'http://127.0.0.1:8080'}/app/device/sendRentCommand`,
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
orderId: this.orderId
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
},
|
||||||
|
success(res) {
|
||||||
|
if (res.statusCode === 200) {
|
||||||
|
resolve(res.data)
|
||||||
|
} else {
|
||||||
|
reject(new Error('请求失败'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
formatTime(date) {
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||||
|
const day = date.getDate().toString().padStart(2, '0')
|
||||||
|
const hour = date.getHours().toString().padStart(2, '0')
|
||||||
|
const minute = date.getMinutes().toString().padStart(2, '0')
|
||||||
|
|
||||||
|
return `${year}-${month}-${day} ${hour}:${minute}`
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查订单状态(单次查询,不轮询)
|
||||||
|
async checkOrderStatus() {
|
||||||
|
try {
|
||||||
|
const res = await uni.request({
|
||||||
|
url: `${URL || 'http://127.0.0.1:8080'}/app/alipay-payment/status/${this.orderInfo.orderNo}`,
|
||||||
|
method: 'GET',
|
||||||
|
header: {
|
||||||
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.statusCode === 200 && res.data.code === 200) {
|
||||||
|
return res.data.data;
|
||||||
|
} else {
|
||||||
|
throw new Error('查询订单状态失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('查询订单状态错误:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.payment-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8f8f8;
|
||||||
|
padding: 30rpx;
|
||||||
|
padding-bottom: 180rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.status-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f5f5f5;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
|
||||||
|
&.waiting {
|
||||||
|
background: #FFF9C4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
background: #E8F5E9;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.failed {
|
||||||
|
background: #FFEBEE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-desc {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card, .price-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 8rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
background: #1976D2;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item, .price-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.total {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
padding-top: 30rpx;
|
||||||
|
border-top: 1px solid #f5f5f5;
|
||||||
|
|
||||||
|
.label, .value {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: #FF5722;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechat-tip {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.method-icon {
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
|
||||||
|
&.wechat {
|
||||||
|
background: url('../../static/images/wechat.svg') no-repeat center/contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-bar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
.total-amount {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #FF5722;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-btn {
|
||||||
|
background: #1976D2;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 20rpx 60rpx;
|
||||||
|
border-radius: 100rpx;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -85,7 +85,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { queryById } from '@/config/api/order.js'
|
import { queryById } from '@/config/api/order.js'
|
||||||
import { withdrawDeposit } from '@/config/api/user.js'
|
|
||||||
import {
|
import {
|
||||||
URL
|
URL
|
||||||
}from"@/config/url.js"
|
}from"@/config/url.js"
|
||||||
@@ -235,9 +234,17 @@ export default {
|
|||||||
try {
|
try {
|
||||||
uni.showLoading({ title: this.$t('common.processing') });
|
uni.showLoading({ title: this.$t('common.processing') });
|
||||||
|
|
||||||
const res = await withdrawDeposit(this.orderInfo.orderNo)
|
const res = await uni.request({
|
||||||
|
url: `${URL || 'http://127.0.0.1:8080'}/app/withdraw/add/${this.orderInfo.orderNo}`,
|
||||||
|
method: 'GET',
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (res && res.code === 200) {
|
if (res.statusCode === 200 && res.data.code === 200) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: this.$t('order.refundSuccess'),
|
title: this.$t('order.refundSuccess'),
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
@@ -252,7 +259,7 @@ export default {
|
|||||||
this.loadOrderInfo();
|
this.loadOrderInfo();
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(res?.msg || this.$t('order.refundFailed'));
|
throw new Error(res.data.msg || this.$t('order.refundFailed'));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('退款申请错误:', error);
|
console.error('退款申请错误:', error);
|
||||||
@@ -1,399 +1,346 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="success-container">
|
<view class="success-container">
|
||||||
<!-- 支付成功状态和订单信息 -->
|
<!-- 支付成功状态 -->
|
||||||
<view class="status-order-card">
|
<view class="status-card">
|
||||||
<!-- 支付成功状态 -->
|
<view class="status-icon success"></view>
|
||||||
<view class="status-section">
|
<view class="status-text">{{ $t('success.paymentSuccess') }}</view>
|
||||||
<view class="status-icon success"></view>
|
<view class="status-desc">{{ $t('success.paymentSuccessDesc') }}</view>
|
||||||
<view class="status-text">{{ $t('success.paymentSuccess') }}</view>
|
</view>
|
||||||
<view class="status-desc">{{ $t('success.paymentSuccessDesc') }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 分割线 -->
|
<!-- 订单信息 -->
|
||||||
<view class="section-divider"></view>
|
<view class="order-card">
|
||||||
|
<view class="card-title">{{ $t('success.orderInfo') }}</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('order.orderNo') }}</text>
|
||||||
|
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('order.deviceNo') }}</text>
|
||||||
|
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('success.paymentAmount') }}</text>
|
||||||
|
<text class="value">¥{{ orderInfo.amount || '0.00' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-item">
|
||||||
|
<text class="label">{{ $t('success.paymentTime') }}</text>
|
||||||
|
<text class="value">{{ orderInfo.payTime || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 订单信息 -->
|
<!-- 设备状态 -->
|
||||||
<view class="order-section">
|
<view class="device-status">
|
||||||
<view class="card-title">{{ $t('success.orderInfo') }}</view>
|
<view class="status-message">{{ deviceMessage }}</view>
|
||||||
<view class="info-item">
|
<view class="loading-animation" v-if="isLoading">
|
||||||
<text class="label">{{ $t('order.orderNo') }}</text>
|
<view class="loading-circle"></view>
|
||||||
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('order.deviceNo') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('success.paymentAmount') }}</text>
|
|
||||||
<text class="value">¥{{ orderInfo.amount || '0.00' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('success.paymentTime') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.payTime || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 设备状态 -->
|
<!-- 操作按钮 -->
|
||||||
<view class="device-status">
|
<view class="button-group">
|
||||||
<view class="status-message">{{ deviceMessage }}</view>
|
<button class="primary-btn" @click="goToHome">{{ $t('success.backToHome') }}</button>
|
||||||
<view class="loading-animation" v-if="isLoading">
|
<button class="secondary-btn" @click="goToOrderList">{{ $t('success.viewOrder') }}</button>
|
||||||
<view class="loading-circle"></view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<view class="button-group">
|
|
||||||
<view class="secondary-btn" @click="goToHome">{{ $t('success.backToHome') }}</view>
|
|
||||||
<view class="primary-btn" @click="goToOrderList">{{ $t('success.viewOrder') }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script>
|
||||||
import {
|
import { queryById } from '@/config/api/order.js'
|
||||||
ref,
|
import { confirmPaymentAndRent } from '@/config/api/device.js'
|
||||||
getCurrentInstance
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
onLoad
|
|
||||||
} from '@dcloudio/uni-app'
|
|
||||||
import {
|
|
||||||
queryById,
|
|
||||||
getOrderByOrderNo
|
|
||||||
} from '@/config/api/order.js'
|
|
||||||
|
|
||||||
// 获取当前实例以访问 $t 方法
|
export default {
|
||||||
const {
|
data() {
|
||||||
proxy
|
return {
|
||||||
} = getCurrentInstance()
|
orderId: '',
|
||||||
|
orderInfo: {},
|
||||||
|
isLoading: true,
|
||||||
|
deviceMessage: '',
|
||||||
|
hasTriggeredDevice: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
// 设置页面标题
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: this.$t('success.paymentSuccess')
|
||||||
|
})
|
||||||
|
|
||||||
// 响应式数据
|
this.deviceMessage = this.$t('success.preparingDevice')
|
||||||
const orderId = ref('')
|
|
||||||
const orderInfo = ref({})
|
|
||||||
const isLoading = ref(true)
|
|
||||||
const deviceMessage = ref('')
|
|
||||||
const hasTriggeredDevice = ref(false)
|
|
||||||
|
|
||||||
// 页面加载
|
if (options && options.orderId) {
|
||||||
onLoad((options) => {
|
this.orderId = options.orderId
|
||||||
// 设置页面标题
|
this.loadOrderInfo()
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: proxy.$t('success.paymentSuccess')
|
|
||||||
})
|
|
||||||
|
|
||||||
deviceMessage.value = proxy.$t('success.preparingDevice')
|
// 添加页面显示监听,防止页面切换后重复触发弹出
|
||||||
|
uni.$once('orderSuccess:' + this.orderId, () => {
|
||||||
|
console.log('已经触发过弹出逻辑,不再重复触发')
|
||||||
|
this.hasTriggeredDevice = true
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('order.orderNotExist'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
this.goToHome()
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadOrderInfo() {
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: this.$t('common.loading')
|
||||||
|
})
|
||||||
|
|
||||||
// #ifdef H5
|
const res = await queryById(this.orderId)
|
||||||
if (uni.getStorageSync('pendingPaymentNo')) {
|
if (res.code === 200 && res.data) {
|
||||||
orderId.value = options.orderId
|
const orderData = res.data
|
||||||
loadOrderInfo()
|
this.orderInfo = {
|
||||||
|
orderNo: orderData.orderNo || orderData.orderId,
|
||||||
|
deviceNo: orderData.deviceNo,
|
||||||
|
amount: orderData.payAmount || orderData.amount,
|
||||||
|
payTime: orderData.payTime || this.formatTime(new Date())
|
||||||
|
}
|
||||||
|
|
||||||
// 添加页面显示监听,防止页面切换后重复触发弹出
|
// 检查订单状态
|
||||||
uni.$once('orderSuccess:' + orderId.value, () => {
|
if (orderData.orderStatus === 'IN_USED') {
|
||||||
console.log('已经触发过弹出逻辑,不再重复触发')
|
// 如果已经是使用中状态,可能说明开锁已经完成
|
||||||
hasTriggeredDevice.value = true
|
this.deviceMessage = '设备已弹出,请取走您的风扇'
|
||||||
})
|
this.isLoading = false
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: proxy.$t('order.orderNotExist'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
goToHome()
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
// #ifndef H5
|
|
||||||
if (options && options.orderId) {
|
|
||||||
orderId.value = options.orderId
|
|
||||||
loadOrderInfo()
|
|
||||||
|
|
||||||
// 添加页面显示监听,防止页面切换后重复触发弹出
|
// 如果是第一次加载页面且设备已弹出,记录状态,避免重复弹出
|
||||||
uni.$once('orderSuccess:' + orderId.value, () => {
|
if (!this.hasTriggeredDevice) {
|
||||||
console.log('已经触发过弹出逻辑,不再重复触发')
|
uni.$emit('orderSuccess:' + this.orderId)
|
||||||
hasTriggeredDevice.value = true
|
this.hasTriggeredDevice = true
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
// 正常触发弹出逻辑
|
||||||
title: proxy.$t('order.orderNotExist'),
|
this.triggerDeviceEject()
|
||||||
icon: 'none'
|
}
|
||||||
})
|
} else {
|
||||||
setTimeout(() => {
|
throw new Error('获取订单信息失败')
|
||||||
goToHome()
|
}
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载订单信息
|
uni.hideLoading()
|
||||||
const loadOrderInfo = async () => {
|
} catch (error) {
|
||||||
try {
|
uni.hideLoading()
|
||||||
uni.showLoading({
|
uni.showToast({
|
||||||
title: proxy.$t('common.loading')
|
title: error.message || this.$t('order.getOrderFailed'),
|
||||||
})
|
icon: 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
const res = await queryById(orderId.value)
|
// 触发弹出风扇
|
||||||
if (res.code === 200 && res.data) {
|
async triggerDeviceEject() {
|
||||||
const orderData = res.data
|
if (this.hasTriggeredDevice) {
|
||||||
orderInfo.value = {
|
console.log('已经触发过弹出风扇,不重复触发')
|
||||||
orderNo: orderData.orderNo || orderData.orderId,
|
return
|
||||||
deviceNo: orderData.deviceNo,
|
}
|
||||||
amount: orderData.payAmount || orderData.amount,
|
|
||||||
payTime: orderData.payTime || formatTime(new Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查订单状态
|
this.hasTriggeredDevice = true
|
||||||
if (orderData.orderStatus === 'IN_USED') {
|
uni.$emit('orderSuccess:' + this.orderId)
|
||||||
// 如果已经是使用中状态,可能说明开锁已经完成
|
this.isLoading = true
|
||||||
deviceMessage.value = '设备已弹出,请取走您的风扇'
|
this.deviceMessage = this.$t('success.preparingDevice')
|
||||||
isLoading.value = false
|
|
||||||
|
|
||||||
// 如果是第一次加载页面且设备已弹出,记录状态,避免重复弹出
|
try {
|
||||||
if (!hasTriggeredDevice.value) {
|
console.log(`准备触发弹出风扇,orderId: ${this.orderId}`)
|
||||||
uni.$emit('orderSuccess:' + orderId.value)
|
|
||||||
hasTriggeredDevice.value = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 在此页面不再触发设备弹出操作,仅更新展示文案和加载状态
|
|
||||||
deviceMessage.value = proxy.$t('success.paymentSuccessDesc')
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('获取订单信息失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.hideLoading()
|
// 调用确认支付并弹出的方法
|
||||||
} catch (error) {
|
const result = await confirmPaymentAndRent(this.orderId)
|
||||||
uni.hideLoading()
|
console.log('确认支付并弹出风扇结果:', JSON.stringify(result))
|
||||||
uni.showToast({
|
|
||||||
title: error.message || proxy.$t('order.getOrderFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化时间
|
if (result && result.code === 200) {
|
||||||
const formatTime = (date) => {
|
this.deviceMessage = this.$t('success.deviceReady')
|
||||||
const year = date.getFullYear()
|
uni.showToast({
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
title: this.$t('success.deviceReady'),
|
||||||
const day = date.getDate().toString().padStart(2, '0')
|
icon: 'success'
|
||||||
const hour = date.getHours().toString().padStart(2, '0')
|
})
|
||||||
const minute = date.getMinutes().toString().padStart(2, '0')
|
} else {
|
||||||
const second = date.getSeconds().toString().padStart(2, '0')
|
throw new Error((result && result.msg) || this.$t('success.deviceFailed'))
|
||||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
}
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error('弹出风扇错误:', error)
|
||||||
|
this.deviceMessage = this.$t('success.deviceFailed')
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || this.$t('success.deviceFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.isLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// 返回首页
|
formatTime(date) {
|
||||||
const goToHome = () => {
|
const year = date.getFullYear()
|
||||||
uni.reLaunch({
|
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||||
url: '/pages/index/index'
|
const day = date.getDate().toString().padStart(2, '0')
|
||||||
})
|
const hour = date.getHours().toString().padStart(2, '0')
|
||||||
}
|
const minute = date.getMinutes().toString().padStart(2, '0')
|
||||||
|
const second = date.getSeconds().toString().padStart(2, '0')
|
||||||
// 查看订单列表
|
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
||||||
const goToOrderList = () => {
|
},
|
||||||
uni.redirectTo({
|
goToHome() {
|
||||||
url: '/pages/order/index'
|
uni.switchTab({
|
||||||
})
|
url: '/pages/index/index'
|
||||||
}
|
})
|
||||||
|
},
|
||||||
|
goToOrderList() {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/order/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.success-container {
|
.success-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-bottom: 180rpx;
|
background-color: #f5f5f5;
|
||||||
background-color: #f5f5f5;
|
min-height: 100vh;
|
||||||
min-height: 100vh;
|
}
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-order-card {
|
.status-card {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
margin-bottom: 20px;
|
padding: 30px;
|
||||||
overflow: hidden;
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
.status-section {
|
.status-icon {
|
||||||
padding: 30px;
|
width: 60px;
|
||||||
text-align: center;
|
height: 60px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
background-color: #07c160;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.status-icon {
|
&::after {
|
||||||
width: 60px;
|
content: '';
|
||||||
height: 60px;
|
position: absolute;
|
||||||
margin: 0 auto 16px;
|
left: 50%;
|
||||||
background-color: #07c160;
|
top: 50%;
|
||||||
border-radius: 50%;
|
transform: translate(-50%, -50%);
|
||||||
position: relative;
|
width: 30px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
border-top: none;
|
||||||
|
border-right: none;
|
||||||
|
transform-origin: center;
|
||||||
|
transform: translate(-50%, -70%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
.status-text {
|
||||||
content: '';
|
font-size: 24px;
|
||||||
position: absolute;
|
font-weight: bold;
|
||||||
left: 50%;
|
color: #07c160;
|
||||||
top: 50%;
|
margin-bottom: 8px;
|
||||||
transform: translate(-50%, -50%);
|
}
|
||||||
width: 30px;
|
|
||||||
height: 20px;
|
|
||||||
border: 3px solid #fff;
|
|
||||||
border-top: none;
|
|
||||||
border-right: none;
|
|
||||||
transform-origin: center;
|
|
||||||
transform: translate(-50%, -70%) rotate(-45deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-text {
|
.status-desc {
|
||||||
font-size: 24px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
color: #666;
|
||||||
color: #07c160;
|
}
|
||||||
margin-bottom: 8px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.status-desc {
|
.order-card {
|
||||||
font-size: 14px;
|
background-color: #fff;
|
||||||
color: #666;
|
border-radius: 12px;
|
||||||
}
|
padding: 20px;
|
||||||
}
|
margin-bottom: 20px;
|
||||||
|
|
||||||
.section-divider {
|
.card-title {
|
||||||
height: 1px;
|
font-size: 16px;
|
||||||
background-color: #f0f0f0;
|
font-weight: bold;
|
||||||
margin: 0 20px;
|
margin-bottom: 16px;
|
||||||
}
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
.order-section {
|
.info-item {
|
||||||
padding: 20px;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
.card-title {
|
.label {
|
||||||
font-size: 16px;
|
color: #666;
|
||||||
font-weight: bold;
|
font-size: 14px;
|
||||||
margin-bottom: 16px;
|
}
|
||||||
color: #333;
|
|
||||||
padding-left: 12px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::before {
|
.value {
|
||||||
content: '';
|
color: #333;
|
||||||
position: absolute;
|
font-size: 14px;
|
||||||
left: 0;
|
}
|
||||||
top: 50%;
|
}
|
||||||
transform: translateY(-50%);
|
}
|
||||||
width: 4px;
|
|
||||||
height: 16px;
|
|
||||||
background-color: #07c160;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
.device-status {
|
||||||
display: flex;
|
background-color: #fff;
|
||||||
justify-content: space-between;
|
border-radius: 12px;
|
||||||
align-items: center;
|
padding: 20px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
&:last-child {
|
.status-message {
|
||||||
margin-bottom: 0;
|
font-size: 16px;
|
||||||
}
|
color: #333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.label {
|
.loading-animation {
|
||||||
color: #666;
|
display: flex;
|
||||||
font-size: 14px;
|
justify-content: center;
|
||||||
}
|
align-items: center;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
.value {
|
.loading-circle {
|
||||||
color: #333;
|
width: 30px;
|
||||||
font-size: 14px;
|
height: 30px;
|
||||||
font-weight: 500;
|
border-radius: 50%;
|
||||||
}
|
border: 3px solid #f0f0f0;
|
||||||
}
|
border-top-color: #07c160;
|
||||||
}
|
animation: spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.device-status {
|
@keyframes spin {
|
||||||
background-color: #fff;
|
0% { transform: rotate(0deg); }
|
||||||
border-radius: 12px;
|
100% { transform: rotate(360deg); }
|
||||||
padding: 20px;
|
}
|
||||||
margin-bottom: 20px;
|
}
|
||||||
text-align: center;
|
}
|
||||||
|
|
||||||
.status-message {
|
.button-group {
|
||||||
font-size: 16px;
|
margin-top: 30px;
|
||||||
color: #333;
|
display: flex;
|
||||||
margin-bottom: 12px;
|
// flex-direction: column;
|
||||||
}
|
gap: 16px;
|
||||||
|
|
||||||
.loading-animation {
|
.primary-btn {
|
||||||
display: flex;
|
background-color: #07c160;
|
||||||
justify-content: center;
|
color: #fff;
|
||||||
align-items: center;
|
border: none;
|
||||||
height: 40px;
|
border-radius: 24px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
.loading-circle {
|
&:active {
|
||||||
width: 30px;
|
opacity: 0.8;
|
||||||
height: 30px;
|
}
|
||||||
border-radius: 50%;
|
}
|
||||||
border: 3px solid #f0f0f0;
|
|
||||||
border-top-color: #07c160;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
.secondary-btn {
|
||||||
0% {
|
background-color: #fff;
|
||||||
transform: rotate(0deg);
|
color: #07c160;
|
||||||
}
|
border: 1px solid #07c160;
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
100% {
|
&:active {
|
||||||
transform: rotate(360deg);
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 20rpx 30rpx;
|
|
||||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
|
||||||
z-index: 10;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20rpx;
|
|
||||||
|
|
||||||
.primary-btn {
|
|
||||||
background-color: #07c160;
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 32rpx;
|
|
||||||
padding: 0 32rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
line-height: 64rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-btn {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #07c160;
|
|
||||||
border: 2rpx solid #07c160;
|
|
||||||
border-radius: 32rpx;
|
|
||||||
padding: 0 32rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
line-height: 64rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
@@ -71,10 +71,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
getNearbyDevices,
|
getNearbyDevices,
|
||||||
transformDeviceData
|
transformDeviceData
|
||||||
} from '@/config/api/device.js'
|
} from '../../config/api/device.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '../../utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
const positionInfo = ref({})
|
const positionInfo = ref({})
|
||||||
const positionId = ref('')
|
const positionId = ref('')
|
||||||
@@ -112,7 +112,7 @@ const { t } = useI18n()
|
|||||||
const loadPositionDetail = async () => {
|
const loadPositionDetail = async () => {
|
||||||
try {
|
try {
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: t('common.loading')
|
title: $t('common.loading')
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取用户位置用于查询附近设备
|
// 获取用户位置用于查询附近设备
|
||||||
@@ -147,7 +147,7 @@ const { t } = useI18n()
|
|||||||
positionInfo.value = position
|
positionInfo.value = position
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('location.notExist'),
|
title: $t('location.notExist'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,7 @@ const { t } = useI18n()
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('加载设备详情失败:', e)
|
console.error('加载设备详情失败:', e)
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('common.loadFailed'),
|
title: $t('common.loadFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -166,7 +166,7 @@ const { t } = useI18n()
|
|||||||
const navigateToPosition = () => {
|
const navigateToPosition = () => {
|
||||||
if (!positionInfo.value.latitude || !positionInfo.value.longitude) {
|
if (!positionInfo.value.latitude || !positionInfo.value.longitude) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('location.coordinateError'),
|
title: $t('location.coordinateError'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -181,7 +181,7 @@ const { t } = useI18n()
|
|||||||
longitude < -180 || longitude > 180 ||
|
longitude < -180 || longitude > 180 ||
|
||||||
(latitude === 0 && longitude === 0)) {
|
(latitude === 0 && longitude === 0)) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('location.coordinateError'),
|
title: $t('location.coordinateError'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -15,24 +15,24 @@
|
|||||||
|
|
||||||
<!-- 支付方式标识 -->
|
<!-- 支付方式标识 -->
|
||||||
<view class="device-right">
|
<view class="device-right">
|
||||||
<!-- 微信支付分标识 -->
|
<!-- 支付宝信用免押标识 -->
|
||||||
<view class="payment-badge wx-score" v-if="orderInfo.payWay == 'wx_score_pay'">
|
<view class="payment-badge alipay-score" v-if="orderInfo.payWay == 'alipay_score_pay'">
|
||||||
<image src="/static/images/wxpayflag.png" mode="aspectFit" class="badge-icon"></image>
|
<image src="/static/images/alipay.svg" mode="aspectFit" class="badge-icon"></image>
|
||||||
<view class="badge-text">
|
<view class="badge-text">
|
||||||
<text>{{ $t('order.wxPayScore') }}</text>
|
<text>{{ $t('order.alipayScore') }}</text>
|
||||||
<text class="divider">|</text>
|
<text class="divider">|</text>
|
||||||
<text class="highlight">{{ $t('order.depositFree') }}</text>
|
<text class="highlight">{{ $t('order.depositFree') }}</text>
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 会员订单标识 -->
|
|
||||||
<view class="payment-badge member" v-else-if="orderInfo.payWay == 'wx_member_pay'">
|
|
||||||
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
|
|
||||||
</view>
|
|
||||||
<!-- 微信支付(押金)标识 -->
|
|
||||||
<view class="payment-badge deposit" v-else-if="orderInfo.payWay == 'wx_pay'">
|
|
||||||
<text class="badge-text">{{ $t('order.depositPay') }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- 会员订单标识 -->
|
||||||
|
<view class="payment-badge member" v-else-if="orderInfo.payWay == 'alipay_member_pay'">
|
||||||
|
<text class="badge-text">{{ $t('order.memberOrder') }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 支付宝支付(押金)标识 -->
|
||||||
|
<view class="payment-badge deposit" v-else-if="orderInfo.payWay == 'alipay_pay'">
|
||||||
|
<text class="badge-text">{{ $t('order.depositPay') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="time-info">
|
<view class="time-info">
|
||||||
@@ -143,8 +143,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
queryById,
|
queryById,
|
||||||
cancelOrder,
|
cancelOrder
|
||||||
getInUseOrder
|
|
||||||
} from '@/config/api/order.js'
|
} from '@/config/api/order.js'
|
||||||
import {
|
import {
|
||||||
getSystemConfig
|
getSystemConfig
|
||||||
@@ -152,7 +151,6 @@
|
|||||||
import {
|
import {
|
||||||
URL
|
URL
|
||||||
} from "@/config/url.js"
|
} from "@/config/url.js"
|
||||||
import { showModalI18n } from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -324,7 +322,7 @@
|
|||||||
this.countdownTimer = null
|
this.countdownTimer = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 解析开始时间字符串为时间戳(毫秒),兼容常见格式及 iOS/微信环境
|
// 解析开始时间字符串为时间戳(毫秒),兼容常见格式及 iOS/支付宝环境
|
||||||
parseStartTimeToMs(timeStr) {
|
parseStartTimeToMs(timeStr) {
|
||||||
if (!timeStr) return NaN
|
if (!timeStr) return NaN
|
||||||
if (typeof timeStr === 'number') {
|
if (typeof timeStr === 'number') {
|
||||||
@@ -412,7 +410,7 @@
|
|||||||
this.clearExpressCountdown()
|
this.clearExpressCountdown()
|
||||||
|
|
||||||
// 显示归还成功弹窗
|
// 显示归还成功弹窗
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: this.$t('order.returnSuccess'),
|
title: this.$t('order.returnSuccess'),
|
||||||
content: this.$t('order.returnSuccessMessage'),
|
content: this.$t('order.returnSuccessMessage'),
|
||||||
confirmText: this.$t('order.viewDetails'),
|
confirmText: this.$t('order.viewDetails'),
|
||||||
@@ -712,12 +710,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 这里调用API查询该设备的使用中订单
|
// 这里调用API查询该设备的使用中订单
|
||||||
const inUseRes = await getInUseOrder()
|
const inUseRes = await uni.request({
|
||||||
|
url: `${URL || 'http://127.0.0.1:8080'}/app/order/inUse`,
|
||||||
|
method: 'GET',
|
||||||
|
header: {
|
||||||
|
'Authorization': "Bearer " + uni.getStorageSync('token'),
|
||||||
|
'Clientid': uni.getStorageSync('client_id')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
console.log('通过设备号查询订单结果:', JSON.stringify(inUseRes))
|
console.log('通过设备号查询订单结果:', JSON.stringify(inUseRes))
|
||||||
|
|
||||||
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
|
if (inUseRes.statusCode === 200 && inUseRes.data.code === 200 && inUseRes.data.data) {
|
||||||
const inUseOrder = inUseRes.data
|
const inUseOrder = inUseRes.data.data
|
||||||
console.log('使用中的订单:', inUseOrder)
|
console.log('使用中的订单:', inUseOrder)
|
||||||
|
|
||||||
// 更新订单ID
|
// 更新订单ID
|
||||||
@@ -794,7 +799,7 @@
|
|||||||
|
|
||||||
// 取消订单
|
// 取消订单
|
||||||
handleCancelOrder() {
|
handleCancelOrder() {
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: this.$t('order.confirmCancel'),
|
title: this.$t('order.confirmCancel'),
|
||||||
content: this.$t('order.confirmCancelContent'),
|
content: this.$t('order.confirmCancelContent'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
@@ -1,754 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="scan-page">
|
|
||||||
<!-- 扫码区域容器 -->
|
|
||||||
<view class="scan-window">
|
|
||||||
<!-- html5-qrcode 扫描器容器 -->
|
|
||||||
<view id="qr-reader" class="qr-reader"></view>
|
|
||||||
|
|
||||||
<!-- 扫描装饰 -->
|
|
||||||
<view class="scan-mask">
|
|
||||||
<view class="scan-frame">
|
|
||||||
<view class="scan-line" v-if="scanning"></view>
|
|
||||||
<view class="corner top-left"></view>
|
|
||||||
<view class="corner top-right"></view>
|
|
||||||
<view class="corner bottom-left"></view>
|
|
||||||
<view class="corner bottom-right"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="scan-tip">{{ tipText }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
|
||||||
<view class="bottom-actions">
|
|
||||||
<view class="action-item" @click.stop="chooseImage">
|
|
||||||
<!-- <view class="action-icon">📷</view> -->
|
|
||||||
<uv-icon name="photo" size="24" color="#fff"></uv-icon>
|
|
||||||
<text>{{ $t('scan.album') }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="action-item" @click.stop="toggleInput">
|
|
||||||
<!-- <view class="action-icon">✏️</view> -->
|
|
||||||
<uv-icon name="edit-pen" size="24" color="#fff"></uv-icon>
|
|
||||||
<text>{{ $t('scan.manualInput') }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
|
|
||||||
<view class="action-item" @click.stop="goBack">
|
|
||||||
<!-- <view class="action-icon">←</view> -->
|
|
||||||
<uv-icon name="arrow-left" size="24" color="#fff"></uv-icon>
|
|
||||||
<text>{{ $t('common.back') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 手动输入弹窗 -->
|
|
||||||
<uv-popup ref="inputPopup" mode="center" round="16" :closeOnClickOverlay="true">
|
|
||||||
<view class="input-dialog">
|
|
||||||
<view class="dialog-title">{{ $t('scan.manualInputTitle') }}</view>
|
|
||||||
<input
|
|
||||||
v-model="manualDeviceNo"
|
|
||||||
:placeholder="$t('scan.deviceNoPlaceholder')"
|
|
||||||
class="device-input"
|
|
||||||
type="text"
|
|
||||||
/>
|
|
||||||
<view class="dialog-btns">
|
|
||||||
<button class="cancel-btn" @click="closeInput">{{ $t('common.cancel') }}</button>
|
|
||||||
<button class="confirm-btn" @click="confirmManualInput">{{ $t('common.confirm') }}</button>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</uv-popup>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
|
||||||
import { getQueryString } from '../../util/index.js';
|
|
||||||
import { Html5Qrcode, Html5QrcodeSupportedFormats } from 'html5-qrcode';
|
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js';
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const inputPopup = ref(null);
|
|
||||||
const manualDeviceNo = ref('');
|
|
||||||
const tipText = ref(t('scan.initializing'));
|
|
||||||
const scanning = ref(false);
|
|
||||||
const hasFlash = ref(false);
|
|
||||||
const flashOn = ref(false);
|
|
||||||
|
|
||||||
let html5QrCode = null;
|
|
||||||
let isProcessing = false; // 防止重复处理
|
|
||||||
|
|
||||||
// 统一扫描配置:增大识别区域,提升低清晰度二维码识别成功率
|
|
||||||
const getScanConfig = () => ({
|
|
||||||
fps: 12,
|
|
||||||
qrbox: (viewfinderWidth, viewfinderHeight) => {
|
|
||||||
const minEdge = Math.min(viewfinderWidth, viewfinderHeight);
|
|
||||||
const size = Math.max(220, Math.floor(minEdge * 0.72));
|
|
||||||
return { width: size, height: size };
|
|
||||||
},
|
|
||||||
disableFlip: false,
|
|
||||||
formatsToSupport: [Html5QrcodeSupportedFormats.QR_CODE],
|
|
||||||
experimentalFeatures: {
|
|
||||||
useBarCodeDetectorIfSupported: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化扫码
|
|
||||||
const initScan = async () => {
|
|
||||||
try {
|
|
||||||
tipText.value = t('scan.initializing');
|
|
||||||
console.log('=== 开始初始化扫码 ===');
|
|
||||||
|
|
||||||
// 检查浏览器支持
|
|
||||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
||||||
throw new Error(t('scan.browserNotSupportCamera'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待 DOM 渲染
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
|
|
||||||
// 检查容器元素
|
|
||||||
const readerElement = document.getElementById('qr-reader');
|
|
||||||
if (!readerElement) {
|
|
||||||
throw new Error(t('scan.containerNotFound'));
|
|
||||||
}
|
|
||||||
console.log('✓ 扫码容器元素已找到');
|
|
||||||
|
|
||||||
// 创建 Html5Qrcode 实例
|
|
||||||
html5QrCode = new Html5Qrcode('qr-reader', {
|
|
||||||
verbose: false // 关闭详细日志
|
|
||||||
});
|
|
||||||
console.log('✓ Html5Qrcode 实例创建成功');
|
|
||||||
|
|
||||||
// 启动扫描
|
|
||||||
await startScanning();
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('❌ 初始化失败:', err);
|
|
||||||
handleInitError(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 开始扫描
|
|
||||||
const startScanning = async () => {
|
|
||||||
try {
|
|
||||||
if (!html5QrCode) {
|
|
||||||
throw new Error(t('scan.initFailed'));
|
|
||||||
}
|
|
||||||
|
|
||||||
tipText.value = t('scan.startingCamera');
|
|
||||||
console.log('=== 开始启动扫描 ===');
|
|
||||||
console.log('html5QrCode 实例:', html5QrCode);
|
|
||||||
console.log('html5QrCode.start 方法:', typeof html5QrCode.start);
|
|
||||||
|
|
||||||
// 配置扫描参数
|
|
||||||
const config = getScanConfig();
|
|
||||||
|
|
||||||
console.log('扫描配置:', config);
|
|
||||||
console.log('准备调用 html5QrCode.start()...');
|
|
||||||
|
|
||||||
// 启动扫描 - 使用 { facingMode: "environment" } 优先后置摄像头
|
|
||||||
const startResult = await html5QrCode.start(
|
|
||||||
{ facingMode: "environment" },
|
|
||||||
config,
|
|
||||||
onScanSuccess,
|
|
||||||
onScanFailure
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('start() 调用结果:', startResult);
|
|
||||||
|
|
||||||
scanning.value = true;
|
|
||||||
tipText.value = t('scan.alignQRCode');
|
|
||||||
console.log('✅ 扫描已成功启动');
|
|
||||||
|
|
||||||
// 延迟隐藏默认UI
|
|
||||||
setTimeout(() => {
|
|
||||||
hideDefaultUI();
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('❌ 启动扫描失败:', err);
|
|
||||||
console.error('错误详情:', {
|
|
||||||
name: err.name,
|
|
||||||
message: err.message,
|
|
||||||
stack: err.stack
|
|
||||||
});
|
|
||||||
|
|
||||||
// 尝试使用前置摄像头
|
|
||||||
console.log('尝试使用前置摄像头...');
|
|
||||||
try {
|
|
||||||
const config = getScanConfig();
|
|
||||||
|
|
||||||
await html5QrCode.start(
|
|
||||||
{ facingMode: "user" },
|
|
||||||
config,
|
|
||||||
onScanSuccess,
|
|
||||||
onScanFailure
|
|
||||||
);
|
|
||||||
|
|
||||||
scanning.value = true;
|
|
||||||
tipText.value = t('scan.alignQRCode');
|
|
||||||
console.log('✅ 使用前置摄像头启动成功');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
hideDefaultUI();
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
} catch (err2) {
|
|
||||||
console.error('❌ 前置摄像头也失败:', err2);
|
|
||||||
|
|
||||||
// 最后尝试:不指定摄像头
|
|
||||||
console.log('最后尝试:使用默认摄像头...');
|
|
||||||
try {
|
|
||||||
const config = getScanConfig();
|
|
||||||
|
|
||||||
// 获取摄像头列表
|
|
||||||
const cameras = await Html5Qrcode.getCameras();
|
|
||||||
console.log('可用摄像头:', cameras);
|
|
||||||
|
|
||||||
if (cameras && cameras.length > 0) {
|
|
||||||
const cameraId = cameras[0].id;
|
|
||||||
console.log('使用摄像头ID:', cameraId);
|
|
||||||
|
|
||||||
await html5QrCode.start(
|
|
||||||
cameraId,
|
|
||||||
config,
|
|
||||||
onScanSuccess,
|
|
||||||
onScanFailure
|
|
||||||
);
|
|
||||||
|
|
||||||
scanning.value = true;
|
|
||||||
tipText.value = t('scan.alignQRCode');
|
|
||||||
console.log('✅ 使用默认摄像头启动成功');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
hideDefaultUI();
|
|
||||||
}, 200);
|
|
||||||
} else {
|
|
||||||
throw new Error(t('scan.noCameraFound'));
|
|
||||||
}
|
|
||||||
} catch (err3) {
|
|
||||||
console.error('❌ 所有方式都失败:', err3);
|
|
||||||
throw err3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 隐藏默认UI
|
|
||||||
const hideDefaultUI = () => {
|
|
||||||
try {
|
|
||||||
const shaded = document.getElementById('qr-shaded-region');
|
|
||||||
if (shaded) {
|
|
||||||
shaded.style.border = 'none';
|
|
||||||
console.log('✓ 已隐藏默认边框');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('隐藏默认UI失败:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 扫描成功回调
|
|
||||||
const onScanSuccess = (decodedText, decodedResult) => {
|
|
||||||
// 防止重复处理
|
|
||||||
if (isProcessing) {
|
|
||||||
console.log('正在处理中,忽略重复扫码');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scanning.value) {
|
|
||||||
console.log('扫描已停止,忽略结果');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
isProcessing = true;
|
|
||||||
|
|
||||||
console.log('========== 扫码成功 ==========');
|
|
||||||
console.log('解码文本:', decodedText);
|
|
||||||
console.log('解码结果:', decodedResult);
|
|
||||||
console.log('==============================');
|
|
||||||
|
|
||||||
// 验证结果
|
|
||||||
if (!decodedText || decodedText.trim() === '') {
|
|
||||||
console.error('扫码结果为空');
|
|
||||||
isProcessing = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalResult = decodedText.trim();
|
|
||||||
|
|
||||||
// 停止扫描
|
|
||||||
stopScan().then(() => {
|
|
||||||
// 震动反馈
|
|
||||||
if (navigator.vibrate) {
|
|
||||||
navigator.vibrate(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过全局事件通知首页
|
|
||||||
uni.$emit('h5ScanSuccess', {
|
|
||||||
result: finalResult,
|
|
||||||
scanType: 'QR_CODE',
|
|
||||||
path: finalResult
|
|
||||||
});
|
|
||||||
|
|
||||||
// 不要立即返回,等待首页处理完成
|
|
||||||
// 首页会在处理完成后自动关闭扫码页
|
|
||||||
console.log('扫码结果已发送,等待首页处理...');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 扫描失败回调
|
|
||||||
const onScanFailure = (error) => {
|
|
||||||
// 正常情况,不需要处理
|
|
||||||
};
|
|
||||||
|
|
||||||
// 停止扫描
|
|
||||||
const stopScan = async () => {
|
|
||||||
if (!html5QrCode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('=== 停止扫描 ===');
|
|
||||||
scanning.value = false;
|
|
||||||
flashOn.value = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const isScanning = html5QrCode.isScanning;
|
|
||||||
if (isScanning) {
|
|
||||||
await html5QrCode.stop();
|
|
||||||
console.log('✓ Html5Qrcode 已停止');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('停止扫描失败:', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理初始化错误
|
|
||||||
const handleInitError = (err) => {
|
|
||||||
console.error('处理初始化错误:', err);
|
|
||||||
|
|
||||||
let errMsg = t('scan.initFailed');
|
|
||||||
let errDetail = '';
|
|
||||||
|
|
||||||
if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
|
||||||
errMsg = t('scan.cameraPermissionDenied');
|
|
||||||
errDetail = t('scan.cameraPermissionHint');
|
|
||||||
}
|
|
||||||
else if (err.name === 'NotFoundError' || err.name === 'DevicesNotFoundError') {
|
|
||||||
errMsg = t('scan.noCameraFound');
|
|
||||||
errDetail = t('scan.ensureCameraExists');
|
|
||||||
}
|
|
||||||
else if (err.name === 'NotReadableError' || err.name === 'TrackStartError') {
|
|
||||||
errMsg = t('scan.cameraInUse');
|
|
||||||
errDetail = t('scan.closeOtherCameraApps');
|
|
||||||
}
|
|
||||||
else if (err.name === 'NotSupportedError') {
|
|
||||||
errMsg = t('scan.browserNotSupported');
|
|
||||||
errDetail = t('scan.useModernBrowser');
|
|
||||||
}
|
|
||||||
// else if (location.protocol !== 'https:' && location.hostname !== 'localhost' && location.hostname !== '127.0.0.1') {
|
|
||||||
// errMsg = '需要 HTTPS 环境';
|
|
||||||
// errDetail = '摄像头功能需要在安全环境下使用';
|
|
||||||
// }
|
|
||||||
else {
|
|
||||||
errMsg = err.message || t('scan.cameraStartFailed');
|
|
||||||
errDetail = t('scan.tryRefreshOrAlternative');
|
|
||||||
}
|
|
||||||
|
|
||||||
tipText.value = errMsg;
|
|
||||||
|
|
||||||
const fallbackContent = `${errDetail}\n\n${t('scan.errorFallbackHint')}\n1. ${t('scan.errorFallbackAlbum')}\n2. ${t('scan.errorFallbackManual')}`;
|
|
||||||
|
|
||||||
// 显示错误提示,提供备选方案
|
|
||||||
showModalI18n({
|
|
||||||
title: errMsg,
|
|
||||||
content: fallbackContent,
|
|
||||||
showCancel: true,
|
|
||||||
cancelText: t('common.back'),
|
|
||||||
confirmText: t('scan.manualInput'),
|
|
||||||
success: (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
toggleInput();
|
|
||||||
} else if (res.cancel) {
|
|
||||||
goBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 从相册选择图片识别
|
|
||||||
const chooseImage = async () => {
|
|
||||||
// #ifdef H5
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.type = 'file';
|
|
||||||
input.accept = 'image/*';
|
|
||||||
input.onchange = async (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({ title: t('scan.recognizing') });
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 先停止摄像头扫描
|
|
||||||
const wasScanning = scanning.value;
|
|
||||||
if (wasScanning) {
|
|
||||||
await stopScan();
|
|
||||||
console.log('已停止摄像头扫描,准备识别图片');
|
|
||||||
// 等待停止完成
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!html5QrCode) {
|
|
||||||
html5QrCode = new Html5Qrcode('qr-reader', { verbose: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 html5-qrcode 扫描文件
|
|
||||||
const result = await html5QrCode.scanFile(file, true);
|
|
||||||
console.log(result);
|
|
||||||
uni.hideLoading();
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
console.log('图片识别成功:', result);
|
|
||||||
|
|
||||||
// 震动反馈
|
|
||||||
if (navigator.vibrate) {
|
|
||||||
navigator.vibrate(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过全局事件通知首页
|
|
||||||
uni.$emit('h5ScanSuccess', {
|
|
||||||
result: result,
|
|
||||||
scanType: 'QR_CODE',
|
|
||||||
path: result
|
|
||||||
});
|
|
||||||
|
|
||||||
// 不要立即返回,等待首页处理完成
|
|
||||||
console.log('图片识别结果已发送,等待首页处理...');
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: t('scan.qrNotFound'), icon: 'none' });
|
|
||||||
// 识别失败,重新启动摄像头扫描
|
|
||||||
if (wasScanning) {
|
|
||||||
setTimeout(async () => {
|
|
||||||
await startScanning();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('图片识别失败:', err);
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({ title: t('scan.recognizeFailed'), icon: 'none' });
|
|
||||||
// 识别失败,重新启动摄像头扫描
|
|
||||||
setTimeout(async () => {
|
|
||||||
try {
|
|
||||||
await startScanning();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('重新启动扫描失败:', e);
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
input.click();
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifndef H5
|
|
||||||
uni.chooseImage({
|
|
||||||
count: 1,
|
|
||||||
sourceType: ['album'],
|
|
||||||
success: (res) => {
|
|
||||||
uni.showToast({ title: t('scan.h5Only'), icon: 'none' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// 打开手动输入弹窗
|
|
||||||
const toggleInput = () => {
|
|
||||||
if (inputPopup.value) {
|
|
||||||
inputPopup.value.open();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 关闭手动输入弹窗
|
|
||||||
const closeInput = () => {
|
|
||||||
if (inputPopup.value) {
|
|
||||||
inputPopup.value.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 确认手动输入
|
|
||||||
const confirmManualInput = () => {
|
|
||||||
const deviceNo = manualDeviceNo.value.trim();
|
|
||||||
|
|
||||||
if (!deviceNo) {
|
|
||||||
uni.showToast({ title: t('scan.deviceNoRequired'), icon: 'none' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
closeInput();
|
|
||||||
stopScan();
|
|
||||||
|
|
||||||
// 处理可能包含 URL 的情况
|
|
||||||
let finalDeviceNo = deviceNo;
|
|
||||||
if (deviceNo.includes('deviceNo=')) {
|
|
||||||
finalDeviceNo = getQueryString(deviceNo, 'deviceNo') || deviceNo;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 通过全局事件通知首页
|
|
||||||
uni.$emit('h5ScanSuccess', {
|
|
||||||
result: finalDeviceNo,
|
|
||||||
scanType: 'MANUAL'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 不要立即返回,等待首页处理完成
|
|
||||||
console.log('手动输入结果已发送,等待首页处理...');
|
|
||||||
};
|
|
||||||
|
|
||||||
// 返回
|
|
||||||
const goBack = () => {
|
|
||||||
stopScan();
|
|
||||||
uni.navigateBack();
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('扫码页面已挂载');
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('scan.title')
|
|
||||||
});
|
|
||||||
|
|
||||||
// 延迟初始化,确保 DOM 已渲染
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('开始初始化扫码');
|
|
||||||
initScan();
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
console.log('扫码页面卸载,清理资源');
|
|
||||||
isProcessing = false;
|
|
||||||
|
|
||||||
if (html5QrCode) {
|
|
||||||
stopScan().then(() => {
|
|
||||||
html5QrCode.clear().catch(err => {
|
|
||||||
console.warn('清理 Html5Qrcode 实例失败:', err);
|
|
||||||
});
|
|
||||||
html5QrCode = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.scan-page {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background: #000;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-window {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.qr-reader {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
// 覆盖 html5-qrcode 默认样式
|
|
||||||
:deep(#qr-shaded-region) {
|
|
||||||
border: none !important; // 隐藏默认边框
|
|
||||||
background: transparent !important; // 透明背景
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(video) {
|
|
||||||
width: 100% !important;
|
|
||||||
height: 100% !important;
|
|
||||||
object-fit: cover !important;
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(canvas) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 隐藏 html5-qrcode 的所有内置 UI 元素
|
|
||||||
:deep(#qr-shaded-region > div) {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-mask {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 10;
|
|
||||||
background: rgba(0, 0, 0, 0.5); // 添加半透明遮罩
|
|
||||||
|
|
||||||
.scan-frame {
|
|
||||||
width: 500rpx;
|
|
||||||
height: 500rpx;
|
|
||||||
position: relative;
|
|
||||||
background: transparent; // 扫描区域透明
|
|
||||||
box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.5); // 使用 box-shadow 创建外部遮罩
|
|
||||||
|
|
||||||
.scan-line {
|
|
||||||
width: 100%;
|
|
||||||
height: 4rpx;
|
|
||||||
background: linear-gradient(to right, transparent, #3EAB64, transparent);
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
animation: scanMove 3s linear infinite;
|
|
||||||
box-shadow: 0 0 10rpx #3EAB64;
|
|
||||||
}
|
|
||||||
|
|
||||||
.corner {
|
|
||||||
position: absolute;
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border: 6rpx solid #3EAB64;
|
|
||||||
box-shadow: 0 0 10rpx rgba(62, 171, 100, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-left { top: -2rpx; left: -2rpx; border-right: none; border-bottom: none; }
|
|
||||||
.top-right { top: -2rpx; right: -2rpx; border-left: none; border-bottom: none; }
|
|
||||||
.bottom-left { bottom: -2rpx; left: -2rpx; border-right: none; border-top: none; }
|
|
||||||
.bottom-right { bottom: -2rpx; right: -2rpx; border-left: none; border-top: none; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scanMove {
|
|
||||||
0% { top: 0; opacity: 0; }
|
|
||||||
10% { opacity: 1; }
|
|
||||||
90% { opacity: 1; }
|
|
||||||
100% { top: 100%; opacity: 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-tip {
|
|
||||||
position: absolute;
|
|
||||||
top: 15%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
text-align: center;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
padding: 0 40rpx;
|
|
||||||
line-height: 1.6;
|
|
||||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.8);
|
|
||||||
z-index: 11;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-actions {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 80rpx;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
padding: 0 60rpx;
|
|
||||||
z-index: 20;
|
|
||||||
|
|
||||||
.action-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
padding: 12rpx 16rpx;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
border-radius: 16rpx;
|
|
||||||
backdrop-filter: blur(10rpx);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
min-width: 100rpx;
|
|
||||||
border: 1rpx solid rgba(255, 255, 255, 0.1);
|
|
||||||
|
|
||||||
.action-icon {
|
|
||||||
font-size: 40rpx;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
text {
|
|
||||||
color: #fff;
|
|
||||||
font-size: 22rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
background: rgba(62, 171, 100, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-dialog {
|
|
||||||
width: 600rpx;
|
|
||||||
background: #fff;
|
|
||||||
padding: 40rpx;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
|
|
||||||
.dialog-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-input {
|
|
||||||
height: 88rpx;
|
|
||||||
background: #F8F9FA;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
padding: 0 24rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
border: 1rpx solid #eee;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: #3EAB64;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-btns {
|
|
||||||
display: flex;
|
|
||||||
gap: 20rpx;
|
|
||||||
|
|
||||||
button {
|
|
||||||
flex: 1;
|
|
||||||
height: 80rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 28rpx;
|
|
||||||
border-radius: 40rpx;
|
|
||||||
border: none;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-btn {
|
|
||||||
background: #3EAB64;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
@relocate="init" @markerTap="goToPositionDetail" />
|
@relocate="init" @markerTap="goToPositionDetail" />
|
||||||
<!-- 定位按钮 -->
|
<!-- 定位按钮 -->
|
||||||
<view class="relocate-btn" @click="init">
|
<view class="relocate-btn" @click="init">
|
||||||
<image src="/static/location.png" class="relocate-icon" mode="aspectFit" lazy-load="true"></image>
|
<image src="/static/location.png" class="relocate-icon" mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="list-wrap">
|
<view class="list-wrap">
|
||||||
@@ -22,52 +22,51 @@
|
|||||||
:class="{ available: isRentable(item), invalid: !isValidCoordinate(item.latitude, item.longitude) }"
|
:class="{ available: isRentable(item), invalid: !isValidCoordinate(item.latitude, item.longitude) }"
|
||||||
v-for="(item, index) in filteredPositions" :key="item.positionId || index"
|
v-for="(item, index) in filteredPositions" :key="item.positionId || index"
|
||||||
@click="goToPositionDetail(item)">
|
@click="goToPositionDetail(item)">
|
||||||
<!-- 第一行:三列布局 -->
|
<view class="thumb">
|
||||||
<view class="card-row-first">
|
<image v-if="item.deviceImg" :src="item.deviceImg" class="thumb-img" mode="aspectFill">
|
||||||
<!-- 第一列:缩略图 -->
|
</image>
|
||||||
<view class="thumb">
|
<image v-else src="/static/device-info.png" class="thumb-img" mode="aspectFit"></image>
|
||||||
<image v-if="item.deviceImg" :src="item.deviceImg" class="thumb-img" mode="aspectFill">
|
|
||||||
</image>
|
|
||||||
<image v-else src="/static/device-info.png" class="thumb-img" mode="aspectFit"></image>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 第二列:信息 -->
|
|
||||||
<view class="info">
|
|
||||||
<view class="row top">
|
|
||||||
<view class="name">{{ item.name }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="row sub" v-if="item.location">
|
|
||||||
<text class="addr">{{ item.location }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="row meta" v-if="item.workTime && item.workTime !== '0'">
|
|
||||||
<text class="time">{{ $t('location.businessHours') }}{{ item.workTime }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="row meta" v-if="!isValidCoordinate(item.latitude, item.longitude)">
|
|
||||||
<text class="time" style="color: #ff6b6b;">{{ $t('location.coordinateError') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 第三列:操作 -->
|
|
||||||
<view class="actions">
|
|
||||||
<view class="nav" :class="{ disabled: !isValidCoordinate(item.latitude, item.longitude) }"
|
|
||||||
@click.stop="navigateToPosition(item)">
|
|
||||||
<image src="/static/luxian.png" class="action-icon" mode="aspectFit"></image>
|
|
||||||
</view>
|
|
||||||
<view class="distance"
|
|
||||||
v-if="item.distance && isValidCoordinate(item.latitude, item.longitude)">
|
|
||||||
{{ item.distance }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="info">
|
||||||
|
<view class="row top">
|
||||||
|
<view class="name">{{ item.name }}</view>
|
||||||
|
|
||||||
<!-- 第二行:标签 -->
|
</view>
|
||||||
<view class="card-row-second">
|
<view class="row sub" v-if="item.location">
|
||||||
|
<text class="addr">{{ item.location }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="row meta" v-if="item.workTime && item.workTime !== '0'">
|
||||||
|
<text class="time">{{ $t('location.businessHours') }}{{ item.workTime }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="row meta" v-if="!isValidCoordinate(item.latitude, item.longitude)">
|
||||||
|
<text class="time" style="color: #ff6b6b;">{{ $t('location.coordinateError') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="row meta"
|
||||||
|
v-if="item.availablePowerBankCount !== undefined && item.availablePowerBankCount !== null">
|
||||||
|
<text class="time">可租借:{{ item.availablePowerBankCount }} 台</text>
|
||||||
|
</view>
|
||||||
|
<view class="row meta"
|
||||||
|
v-if="item.availableEmptyGridCount !== undefined && item.availableEmptyGridCount !== null">
|
||||||
|
<text class="time">可归还:{{ item.availableEmptyGridCount }} 个空位</text>
|
||||||
|
</view>
|
||||||
|
<view class="row meta remark-info" v-if="item.remark">
|
||||||
|
<text class="time">💰 {{ item.remark }}</text>
|
||||||
|
</view>
|
||||||
<view class="tags">
|
<view class="tags">
|
||||||
<view class="tag rent" v-if="isRentable(item)">{{ $t('location.rent') }}</view>
|
<view class="tag rent" v-if="isRentable(item)">{{ $t('location.rent') }}</view>
|
||||||
<view class="tag return" v-if="isReturnable(item)">{{ $t('location.return') }}</view>
|
<view class="tag return" v-if="isReturnable(item)">{{ $t('location.return') }}</view>
|
||||||
<view class="tag coupon" v-if="item.supportCouponOrMember">{{ $t('location.supportCouponOrMember') }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="actions">
|
||||||
|
|
||||||
|
<view class="nav" :class="{ disabled: !isValidCoordinate(item.latitude, item.longitude) }"
|
||||||
|
@click.stop="navigateToPosition(item)">
|
||||||
|
<image src="/static/luxian.png" class="action-icon" mode="aspectFit"></image>
|
||||||
|
</view>
|
||||||
|
<view class="distance"
|
||||||
|
v-if="item.distance && isValidCoordinate(item.latitude, item.longitude)">
|
||||||
|
{{ item.distance }}</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="empty-state" v-if="!isLoading && (!positionList || positionList.length === 0)">
|
<view class="empty-state" v-if="!isLoading && (!positionList || positionList.length === 0)">
|
||||||
@@ -101,13 +100,13 @@
|
|||||||
} from '@/utils/i18n.js'
|
} from '@/utils/i18n.js'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
t
|
t: $t
|
||||||
} = useI18n()
|
} = useI18n()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('search.title')
|
title: $t('search.title')
|
||||||
})
|
})
|
||||||
// uni.showLoading({
|
// uni.showLoading({
|
||||||
// title:'11111',
|
// title:'11111',
|
||||||
@@ -134,13 +133,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatDistance = (meters) => {
|
const formatDistance = (meters) => {
|
||||||
// 兼容支付宝小程序等环境:保证始终对 Number 调用 toFixed
|
if (meters < 1000) return `${Math.round(meters)}m`
|
||||||
let m = meters
|
return `${(meters / 1000).toFixed(1)}km`
|
||||||
if (typeof m === 'bigint') m = Number(m)
|
|
||||||
m = Number(m)
|
|
||||||
if (!Number.isFinite(m) || m < 0) return ''
|
|
||||||
if (m < 1000) return `${Math.round(m)}m`
|
|
||||||
return `${(m / 1000).toFixed(1)}km`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setTab = (name) => {
|
const setTab = (name) => {
|
||||||
@@ -222,8 +216,8 @@
|
|||||||
return {
|
return {
|
||||||
...transformed,
|
...transformed,
|
||||||
canRent: activeTab.value === 'rent' ? true : (device.availablePowerBankCount > 0),
|
canRent: activeTab.value === 'rent' ? true : (device.availablePowerBankCount > 0),
|
||||||
canReturn: activeTab.value === 'return' ? true : (device.availableEmptyGridCount > 0),
|
canReturn: activeTab.value === 'return' ? true : (device.availableEmptyGridCount >
|
||||||
supportCouponOrMember: device.supportCouponOrMember || false
|
0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -262,7 +256,7 @@ const init = async () => {
|
|||||||
positionList.value = []
|
positionList.value = []
|
||||||
filteredPositions.value = []
|
filteredPositions.value = []
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('home.getLocationFailed'),
|
title: $t('home.getLocationFailed'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@@ -284,7 +278,7 @@ const init = async () => {
|
|||||||
const navigateToPosition = (position) => {
|
const navigateToPosition = (position) => {
|
||||||
if (!isValidCoordinate(position.latitude, position.longitude)) {
|
if (!isValidCoordinate(position.latitude, position.longitude)) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('search.invalidCoordinate'),
|
title: $t('search.invalidCoordinate'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -302,13 +296,13 @@ const init = async () => {
|
|||||||
const goToPositionDetail = (position) => {
|
const goToPositionDetail = (position) => {
|
||||||
if (!position.positionId) {
|
if (!position.positionId) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('search.positionInfoError'),
|
title: $t('search.positionInfoError'),
|
||||||
icon: 'none'
|
icon: 'none'
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/subPackages/business/position/detail?positionId=${position.positionId}`
|
url: `/pages/position/detail?positionId=${position.positionId}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -398,8 +392,9 @@ const init = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: 120rpx 1fr 72rpx;
|
||||||
|
align-items: center;
|
||||||
gap: 16rpx;
|
gap: 16rpx;
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
@@ -418,21 +413,6 @@ const init = async () => {
|
|||||||
background: #FFF9F9;
|
background: #FFF9F9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第一行:三列布局
|
|
||||||
.card-row-first {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 120rpx 1fr 72rpx;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第二行:标签
|
|
||||||
.card-row-second {
|
|
||||||
width: 100%;
|
|
||||||
padding-top: 8rpx;
|
|
||||||
// border-top: 1rpx solid #F0F0F0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
width: 120rpx;
|
width: 120rpx;
|
||||||
height: 120rpx;
|
height: 120rpx;
|
||||||
@@ -462,7 +442,7 @@ const init = async () => {
|
|||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #2A2A2A;
|
color: #2A2A2A;
|
||||||
max-width: 100%;
|
max-width: 70%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -494,34 +474,28 @@ const init = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
gap: 10rpx;
|
||||||
gap: 10rpx;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
padding: 6rpx 14rpx;
|
padding: 6rpx 14rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag.rent {
|
.tag.rent {
|
||||||
background: #E8F5E8;
|
background: #E8F5E8;
|
||||||
color: #3EAB64;
|
color: #3EAB64;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag.return {
|
.tag.return {
|
||||||
background: #E8F2FF;
|
background: #E8F2FF;
|
||||||
color: #3578e5;
|
color: #3578e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag.coupon {
|
|
||||||
background: #FFF9F0;
|
|
||||||
color: #D4A574;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
@@ -529,7 +503,6 @@ const init = async () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
wxLogin,
|
alipayLogin,
|
||||||
} from '@/util/index'
|
} from '../../../util/index'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getMyIndexInfo
|
getMyIndexInfo
|
||||||
} from "@/config/api/user.js";
|
} from "../../../config/api/user.js";
|
||||||
import {
|
import {
|
||||||
queryHasOrder,
|
queryHasOrder,
|
||||||
checkOrdersByStatus
|
checkOrdersByStatus
|
||||||
} from "@/config/api/order.js";
|
} from "../../../config/api/order.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
// 如果是使用中的订单,跳转到归还页面
|
// 如果是使用中的订单,跳转到归还页面
|
||||||
console.log('检测到使用中订单,跳转归还页:', latestOrder.orderId);
|
console.log('检测到使用中订单,跳转归还页:', latestOrder.orderId);
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/subPackages/service/return/index?orderId=${latestOrder.orderId}`
|
url: `/pages/device/return?orderId=${latestOrder.orderId}`
|
||||||
});
|
});
|
||||||
} else if (latestOrder.orderStatus === 'waiting_for_payment') {
|
} else if (latestOrder.orderStatus === 'waiting_for_payment') {
|
||||||
// 如果是待支付订单,跳转到支付页面,并传递必要信息
|
// 如果是待支付订单,跳转到支付页面,并传递必要信息
|
||||||
@@ -81,13 +81,13 @@
|
|||||||
const totalAmount = (parseFloat(depositAmount) + parseFloat(packagePrice)).toFixed(2);
|
const totalAmount = (parseFloat(depositAmount) + parseFloat(packagePrice)).toFixed(2);
|
||||||
|
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/subPackages/order/payment?orderId=${latestOrder.orderId}&packageTimeHours=${packageTimeHours}&packagePrice=${packagePrice}&hourlyRate=${hourlyRate}&totalAmount=${totalAmount}&depositAmount=${depositAmount}`
|
url: `/pages/order/payment?orderId=${latestOrder.orderId}&packageTimeHours=${packageTimeHours}&packagePrice=${packagePrice}&hourlyRate=${hourlyRate}&totalAmount=${totalAmount}&depositAmount=${depositAmount}`
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// 其他状态(理论上不应该到这里,除非statusesToCheck配置错误),默认到详情页
|
// 其他状态(理论上不应该到这里,除非statusesToCheck配置错误),默认到详情页
|
||||||
console.log('检测到其他状态订单,跳转详情页:', latestOrder.orderId);
|
console.log('检测到其他状态订单,跳转详情页:', latestOrder.orderId);
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: `/pages/order/detail?deviceNo=${deviceNo}`
|
url: `/pages/device/detail?deviceNo=${deviceNo}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -122,11 +122,8 @@
|
|||||||
url: `/pages/device/detail?deviceNo=${option.deviceNo}`
|
url: `/pages/device/detail?deviceNo=${option.deviceNo}`
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// uni.switchTab({
|
|
||||||
// url:'/pages/index/index'
|
|
||||||
// })
|
|
||||||
// 如果连deviceNo都没有,则返回首页
|
// 如果连deviceNo都没有,则返回首页
|
||||||
uni.reLaunch({ url: '/pages/index/index' });
|
uni.switchTab({ url: '/pages/index/index' });
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -10,16 +10,12 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="group">
|
<view class="group">
|
||||||
<view class="item" @click="navigateTo('/subPackages/other/legal/agreement')">
|
<view class="item" @click="navigateTo('/pages/legal/agreement')">
|
||||||
<text class="label">{{ $t('user.settinguserAgreement') }}</text>
|
<text class="label">{{ $t('user.userAgreement') }}</text>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
<view class="item" @click="navigateTo('/subPackages/other/legal/privacy')">
|
<view class="item" @click="navigateTo('/pages/legal/privacy')">
|
||||||
<text class="label">{{ $t('user.settinguserprivacyPolicy') }}</text>
|
<text class="label">{{ $t('user.privacyPolicy') }}</text>
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
|
||||||
</view>
|
|
||||||
<view class="item" @click="navigateTo('/subPackages/other/legal/terms')">
|
|
||||||
<text class="label">{{ $t('legal.termsAndConditions') }}</text>
|
|
||||||
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
<uv-icon name="arrow-right" size="16" color="#c8c8c8"></uv-icon>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -35,9 +31,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, getCurrentInstance } from 'vue'
|
import { ref, computed, onMounted, getCurrentInstance } from 'vue'
|
||||||
import { userLogout } from '@/config/api/user.js'
|
import { userLogout } from '@/config/api/user.js'
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
// 获取全局 i18n 实例
|
// 获取全局 i18n 实例
|
||||||
const instance = getCurrentInstance()
|
const instance = getCurrentInstance()
|
||||||
@@ -46,7 +42,7 @@ const globalI18n = instance?.appContext?.config?.globalProperties?.$i18n
|
|||||||
// 设置页面标题
|
// 设置页面标题
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('settings.title')
|
title: $t('settings.title')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -55,13 +51,7 @@ const currentLanguage = ref(uni.getStorageSync('language') || 'zh-CN')
|
|||||||
|
|
||||||
// 当前语言文本显示
|
// 当前语言文本显示
|
||||||
const currentLanguageText = computed(() => {
|
const currentLanguageText = computed(() => {
|
||||||
if (currentLanguage.value === 'zh-CN') {
|
return currentLanguage.value === 'zh-CN' ? '简体中文' : 'English'
|
||||||
return t('settings.chinese')
|
|
||||||
} else if (currentLanguage.value === 'id-ID') {
|
|
||||||
return t('settings.indonesian')
|
|
||||||
} else {
|
|
||||||
return t('settings.english')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const navigateTo = (url) => {
|
const navigateTo = (url) => {
|
||||||
@@ -70,40 +60,58 @@ const navigateTo = (url) => {
|
|||||||
|
|
||||||
// 显示语言选择器
|
// 显示语言选择器
|
||||||
const showLanguageSelector = () => {
|
const showLanguageSelector = () => {
|
||||||
const languages = [
|
|
||||||
{ code: 'zh-CN', label: t('settings.chinese') },
|
|
||||||
{ code: 'en-US', label: t('settings.english') },
|
|
||||||
{ code: 'id-ID', label: t('settings.indonesian') }
|
|
||||||
]
|
|
||||||
|
|
||||||
uni.showActionSheet({
|
uni.showActionSheet({
|
||||||
itemList: languages.map(lang => lang.label),
|
itemList: ['简体中文', 'English'],
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
const selectedLang = languages[res.tapIndex].code
|
const lang = res.tapIndex === 0 ? 'zh-CN' : 'en-US'
|
||||||
if (selectedLang !== currentLanguage.value) {
|
if (lang !== currentLanguage.value) {
|
||||||
|
console.log('========================================')
|
||||||
|
console.log('=== 用户选择切换语言 ===')
|
||||||
|
console.log('旧语言:', currentLanguage.value)
|
||||||
|
console.log('新语言:', lang)
|
||||||
|
|
||||||
// 1. 保存到缓存
|
// 1. 保存到缓存
|
||||||
uni.setStorageSync('language', selectedLang)
|
uni.setStorageSync('language', lang)
|
||||||
|
console.log('✓ 语言已保存到缓存')
|
||||||
|
|
||||||
// 2. 立即更新 i18n 实例(重要!)
|
// 2. 立即更新 i18n 实例(重要!)
|
||||||
if (globalI18n) {
|
if (globalI18n) {
|
||||||
globalI18n.locale = selectedLang
|
console.log('✓ 正在更新 globalI18n.locale...')
|
||||||
|
console.log(' 更新前:', globalI18n.locale)
|
||||||
|
globalI18n.locale = lang
|
||||||
|
console.log(' 更新后:', globalI18n.locale)
|
||||||
|
console.log('✓ 测试翻译:', globalI18n.t('common.loading'))
|
||||||
|
} else {
|
||||||
|
console.warn('✗ globalI18n 不存在!')
|
||||||
|
console.warn(' instance:', !!instance)
|
||||||
|
console.warn(' appContext:', !!instance?.appContext)
|
||||||
|
console.warn(' globalProperties:', !!instance?.appContext?.config?.globalProperties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 更新当前语言状态
|
// 3. 更新当前语言状态
|
||||||
currentLanguage.value = selectedLang
|
currentLanguage.value = lang
|
||||||
|
|
||||||
|
console.log('========================================')
|
||||||
|
|
||||||
// 4. 提示用户
|
// 4. 提示用户
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('settings.languageSwitched'),
|
title: lang === 'zh-CN' ? '语言已切换,正在刷新...' : 'Language switched, refreshing...',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 800
|
duration: 800
|
||||||
})
|
})
|
||||||
|
|
||||||
// 5. 延迟后重新加载应用(确保 i18n 更新已生效)
|
// 5. 延迟后重新加载应用(确保 i18n 更新已生效)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
console.log('=== 准备 reLaunch 到首页 ===')
|
||||||
// 使用 reLaunch 完全重启应用
|
// 使用 reLaunch 完全重启应用
|
||||||
uni.reLaunch({
|
uni.reLaunch({
|
||||||
url: '/pages/index/index'
|
url: '/pages/index/index',
|
||||||
|
success: () => {
|
||||||
|
console.log('✓ 页面已重新加载')
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.error('✗ 页面重载失败:', err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}, 800)
|
}, 800)
|
||||||
}
|
}
|
||||||
@@ -112,18 +120,18 @@ const showLanguageSelector = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
showModalI18n({
|
uni.showModal({
|
||||||
title: t('common.tips'),
|
title: $t('common.tips'),
|
||||||
content: t('user.confirmLogout'),
|
content: $t('user.confirmLogout'),
|
||||||
success: async (res) => {
|
success: async (res) => {
|
||||||
if (res.confirm) {
|
if (res.confirm) {
|
||||||
const response = await userLogout();
|
const response = await userLogout();
|
||||||
if (response.code == 200) {
|
if (response.code == 200) {
|
||||||
uni.showToast({ title: t('user.logoutSuccess'), icon: 'none' })
|
uni.showToast({ title: $t('user.logoutSuccess'), icon: 'none' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.removeStorageSync('token')
|
uni.removeStorageSync('token')
|
||||||
uni.removeStorageSync('userInfo')
|
uni.removeStorageSync('userInfo')
|
||||||
uni.reLaunch({ url: '/subPackages/user/login/index' })
|
uni.reLaunch({ url: '/pages/login/index' })
|
||||||
}, 1200)
|
}, 1200)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,240 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 用户信息卡片 -->
|
||||||
|
<view class="user-card">
|
||||||
|
<view class="avatar">
|
||||||
|
<image :src="userInfo.avatar || '/static/images/default-avatar.png'" mode="aspectFill"></image>
|
||||||
|
</view>
|
||||||
|
<view class="user-info">
|
||||||
|
<text class="nickname">{{ userInfo.nickName || $t('user.notLoggedIn') }}</text>
|
||||||
|
<text class="phone">{{ userInfo.phone || $t('user.phoneNotBound') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 余额卡片 -->
|
||||||
|
<view class="balance-card">
|
||||||
|
<view class="balance-title">{{ $t('userProfile.balance') }}</view>
|
||||||
|
<view class="balance-amount">¥{{ userInfo.balanceAmount || '0.00' }}</view>
|
||||||
|
<view class="balance-desc">{{ $t('user.balanceDesc') }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 功能菜单 -->
|
||||||
|
<view class="menu-list">
|
||||||
|
<view class="menu-item" @click="navigateTo('/pages/order/index')">
|
||||||
|
<text class="menu-icon">📋</text>
|
||||||
|
<text class="menu-text">{{ $t('user.myOrders') }}</text>
|
||||||
|
<text class="menu-arrow">></text>
|
||||||
|
</view>
|
||||||
|
<view class="menu-item" @click="navigateTo('/pages/feedback/index')">
|
||||||
|
<text class="menu-icon">💬</text>
|
||||||
|
<text class="menu-text">{{ $t('user.feedback') }}</text>
|
||||||
|
<text class="menu-arrow">></text>
|
||||||
|
</view>
|
||||||
|
<view class="menu-item" @click="navigateTo('/pages/help/index')">
|
||||||
|
<text class="menu-icon">ℹ️</text>
|
||||||
|
<text class="menu-text">{{ $t('help.title') }}</text>
|
||||||
|
<text class="menu-arrow">></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 退出登录按钮 -->
|
||||||
|
<view class="logout-btn" @click="handleLogout" v-if="isLogin">
|
||||||
|
<text>{{ $t('user.logout') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getUserInfo } from '@/api/user'
|
||||||
|
import { URL } from '@/config/url'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
userInfo: {},
|
||||||
|
isLogin: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
// 设置页面标题
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: this.$t('user.personalCenter')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.loadUserInfo()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadUserInfo() {
|
||||||
|
try {
|
||||||
|
const res = await getUserInfo()
|
||||||
|
if (res.code === 401 || res.code === 40101) {
|
||||||
|
// 无提示跳转至登录
|
||||||
|
try {
|
||||||
|
const pages = getCurrentPages()
|
||||||
|
const current = pages && pages.length ? pages[pages.length - 1] : null
|
||||||
|
const route = current && current.route ? ('/' + current.route) : '/pages/index/index'
|
||||||
|
const query = current && current.options ? Object.keys(current.options).map(k => `${k}=${encodeURIComponent(current.options[k])}`).join('&') : ''
|
||||||
|
const redirect = encodeURIComponent(query ? `${route}?${query}` : route)
|
||||||
|
uni.reLaunch({ url: `/pages/login/index?redirect=${redirect}` })
|
||||||
|
} catch (e) {
|
||||||
|
uni.reLaunch({ url: '/pages/login/index' })
|
||||||
|
}
|
||||||
|
} else if (res.code === 200) {
|
||||||
|
this.userInfo = res.data
|
||||||
|
this.isLogin = true
|
||||||
|
} else {
|
||||||
|
this.isLogin = false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载用户信息失败:', error)
|
||||||
|
this.isLogin = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigateTo(url) {
|
||||||
|
uni.navigateTo({ url })
|
||||||
|
},
|
||||||
|
handleLogout() {
|
||||||
|
uni.showModal({
|
||||||
|
title: this.$t('common.tips'),
|
||||||
|
content: this.$t('user.confirmLogout'),
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.removeStorageSync('token')
|
||||||
|
uni.removeStorageSync('userInfo')
|
||||||
|
this.isLogin = false
|
||||||
|
uni.showToast({
|
||||||
|
title: this.$t('user.logoutSuccess'),
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/login/index'
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
padding: 20rpx;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 60rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nickname {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-amount {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-list {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30rpx;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
font-size: 36rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-arrow {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
margin-top: 40rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-size: 28rpx;
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,413 @@
|
|||||||
|
<template>
|
||||||
|
<view class="profile-page">
|
||||||
|
<view class="avatar-section">
|
||||||
|
<view class="avatar-container">
|
||||||
|
<image class="avatar" v-if="userInfo.avatar" :src="userInfo.avatar" mode="aspectFill"></image>
|
||||||
|
<image v-else class="avatar" src="@/static/head.png" mode="aspectFill"></image>
|
||||||
|
<!-- 覆盖在头像上的支付宝选择头像授权按钮,仅小程序生效 -->
|
||||||
|
<!-- #ifdef MP-ALIPAY -->
|
||||||
|
<button class="avatar-choose-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar"></button>
|
||||||
|
<!-- #endif -->
|
||||||
|
</view>
|
||||||
|
<view class="avatar-tip">{{ $t('userProfile.clickToChange') }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-section">
|
||||||
|
<!-- 昵称编辑区域 -->
|
||||||
|
<view class="form-item nickname-item" :class="{ editing: isEditingNickname }">
|
||||||
|
<view class="label">{{ $t('userProfile.nickname') }}</view>
|
||||||
|
<view class="value" v-if="!isEditingNickname" @click="startEditNickname">
|
||||||
|
<text class="value-text">{{ userInfo.nickName || $t('userProfile.notSet') }}</text>
|
||||||
|
<uv-icon name="edit-pen" size="16" color="#999999"></uv-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 昵称编辑输入框(展开状态) -->
|
||||||
|
<view class="nickname-edit-area" v-if="isEditingNickname">
|
||||||
|
<input
|
||||||
|
class="nickname-input"
|
||||||
|
v-model="newNickname"
|
||||||
|
:placeholder="$t('userProfile.enterNickname')"
|
||||||
|
maxlength="20"
|
||||||
|
:focus="true"
|
||||||
|
/>
|
||||||
|
<view class="edit-buttons">
|
||||||
|
<button class="cancel-btn" @click="cancelEditNickname">{{ $t('common.cancel') }}</button>
|
||||||
|
<button class="save-btn" @click="saveNickname">{{ $t('common.save') }}</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<view class="label">{{ $t('userProfile.phone') }}</view>
|
||||||
|
<view class="value">
|
||||||
|
<text class="value-text">{{ userInfo.phone ? maskPhone(userInfo.phone) : $t('userProfile.notBound') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- <view class="form-item" v-if="userInfo.balanceAmount !== undefined">
|
||||||
|
<view class="label">{{ $t('userProfile.balance') }}</view>
|
||||||
|
<view class="value">
|
||||||
|
<text class="value-text amount">¥{{ userInfo.balanceAmount || '0.00' }}</text>
|
||||||
|
</view>
|
||||||
|
</view> -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import { getMyIndexInfo, uploadUserAvatar, updateUserInfo } from '../../config/api/user.js';
|
||||||
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
|
// 响应式状态
|
||||||
|
const userInfo = ref({
|
||||||
|
nickName: '',
|
||||||
|
phone: '',
|
||||||
|
avatar: '',
|
||||||
|
balanceAmount: '0.00'
|
||||||
|
});
|
||||||
|
|
||||||
|
const newNickname = ref('');
|
||||||
|
const isEditingNickname = ref(false);
|
||||||
|
|
||||||
|
// 页面加载时初始化
|
||||||
|
onMounted(() => {
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: $t('userProfile.title')
|
||||||
|
})
|
||||||
|
loadUserInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
const loadUserInfo = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getMyIndexInfo();
|
||||||
|
console.log('User info response:', res);
|
||||||
|
|
||||||
|
if (res.code == 401 || res.code == 40101) {
|
||||||
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
} else if (res.code == 200) {
|
||||||
|
userInfo.value = {
|
||||||
|
nickName: res.data.nickname,
|
||||||
|
phone: res.data.phone,
|
||||||
|
avatar: res.data.iconUrl,
|
||||||
|
balanceAmount: res.data.balanceAmount || '0.00',
|
||||||
|
isAdmin: res.data.isAdmin
|
||||||
|
};
|
||||||
|
uni.setStorageSync('userInfo', userInfo.value);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户信息失败:', error);
|
||||||
|
uni.showToast({
|
||||||
|
title: $t('user.getUserInfoFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 跳转登录
|
||||||
|
const redirectToLogin = () => {
|
||||||
|
try {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
const current = pages && pages.length ? pages[pages.length - 1] : null;
|
||||||
|
const route = current && current.route ? ('/' + current.route) : '/pages/index/index';
|
||||||
|
const query = current && current.options ? Object.keys(current.options).map(k =>
|
||||||
|
`${k}=${encodeURIComponent(current.options[k])}`).join('&') : '';
|
||||||
|
const redirect = encodeURIComponent(query ? `${route}?${query}` : route);
|
||||||
|
uni.reLaunch({
|
||||||
|
url: `/pages/login/index?redirect=${redirect}`
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/index'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 小程序原生选择头像回调
|
||||||
|
const onChooseAvatar = async (e) => {
|
||||||
|
try {
|
||||||
|
const token = uni.getStorageSync('token');
|
||||||
|
if (!token) {
|
||||||
|
redirectToLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const avatarLocalPath = e?.detail?.avatarUrl;
|
||||||
|
if (!avatarLocalPath) {
|
||||||
|
uni.showToast({
|
||||||
|
title: $t('user.noAvatar'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showLoading({
|
||||||
|
title: $t('userProfile.uploading'),
|
||||||
|
mask: true
|
||||||
|
});
|
||||||
|
const uploadRes = await uploadUserAvatar(avatarLocalPath);
|
||||||
|
const serverAvatar = uploadRes?.data?.url || uploadRes?.url || uploadRes?.data || '';
|
||||||
|
if (serverAvatar) {
|
||||||
|
userInfo.value = {
|
||||||
|
...userInfo.value,
|
||||||
|
avatar: serverAvatar
|
||||||
|
};
|
||||||
|
uni.setStorageSync('userInfo', userInfo.value);
|
||||||
|
}
|
||||||
|
uni.showToast({
|
||||||
|
title: $t('user.avatarUpdated'),
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
await loadUserInfo();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('选择/上传头像失败:', err);
|
||||||
|
uni.showToast({
|
||||||
|
title: $t('user.avatarUploadFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始编辑昵称
|
||||||
|
const startEditNickname = () => {
|
||||||
|
newNickname.value = userInfo.value.nickName || '';
|
||||||
|
isEditingNickname.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消编辑昵称
|
||||||
|
const cancelEditNickname = () => {
|
||||||
|
isEditingNickname.value = false;
|
||||||
|
newNickname.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存昵称
|
||||||
|
const saveNickname = async () => {
|
||||||
|
if (!newNickname.value || !newNickname.value.trim()) {
|
||||||
|
uni.showToast({
|
||||||
|
title: $t('userProfile.nicknameRequired'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
uni.showLoading({
|
||||||
|
title: $t('userProfile.saving'),
|
||||||
|
mask: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// 先获取最新的用户信息,确保数据是最新的
|
||||||
|
const latestUserInfo = await getMyIndexInfo();
|
||||||
|
|
||||||
|
if (latestUserInfo.code !== 200) {
|
||||||
|
throw new Error('获取用户信息失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用最新的服务器数据,只修改昵称字段
|
||||||
|
const updateData = {
|
||||||
|
nickname: newNickname.value.trim(),
|
||||||
|
phone: latestUserInfo.data.phone,
|
||||||
|
iconUrl: latestUserInfo.data.iconUrl,
|
||||||
|
// 保留其他可能的字段
|
||||||
|
...latestUserInfo.data
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确保昵称使用新值
|
||||||
|
updateData.nickname = newNickname.value.trim();
|
||||||
|
|
||||||
|
// 调用后端接口更新用户信息
|
||||||
|
const res = await updateUserInfo(updateData);
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
// 更新成功后重新获取用户信息,确保数据同步
|
||||||
|
await loadUserInfo();
|
||||||
|
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: $t('userProfile.nicknameUpdated'),
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
isEditingNickname.value = false;
|
||||||
|
} else {
|
||||||
|
throw new Error(res.message || $t('userProfile.updateFailed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('修改昵称失败:', error);
|
||||||
|
uni.hideLoading();
|
||||||
|
uni.showToast({
|
||||||
|
title: error.message || $t('userProfile.updateFailed'),
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 手机号掩码函数
|
||||||
|
function maskPhone(phone) {
|
||||||
|
if (!phone) return '';
|
||||||
|
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.profile-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-section {
|
||||||
|
background: linear-gradient(180deg, #D1FFE1 0%, #ffffff 100%);
|
||||||
|
padding: 60rpx 0 40rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-container {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: 80rpx;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 仅小程序端存在,此按钮覆盖在头像上捕获点击以触发选择头像 */
|
||||||
|
/* #ifdef MP-ALIPAY */
|
||||||
|
.avatar-choose-btn {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0;
|
||||||
|
border-radius: 80rpx;
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
.avatar-tip {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin: 20rpx 30rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 32rpx 30rpx;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item.nickname-item.editing {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666666;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value-text.amount {
|
||||||
|
color: #e2231a;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 昵称编辑区域样式 */
|
||||||
|
.nickname-edit-area {
|
||||||
|
padding: 0 30rpx 30rpx 30rpx;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
animation: slideDown 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nickname-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
border: 1rpx solid #e0e0e0;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn,
|
||||||
|
.save-btn {
|
||||||
|
padding: 0 40rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
line-height: 64rpx;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
border: none;
|
||||||
|
min-width: 120rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-btn {
|
||||||
|
background: linear-gradient(135deg, #42d392 0%, #28c76f 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
import { getOrderByOrderNoScorePayStatus, cancelOrder } from '@/config/api/order.js'
|
import { getOrderByOrderNoScorePayStatus, cancelOrder } from '@/config/api/order.js'
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
import { useI18n } from '@/utils/i18n.js'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t: $t } = useI18n()
|
||||||
|
|
||||||
const progress = ref(0)
|
const progress = ref(0)
|
||||||
const leftRotateDeg = ref(0)
|
const leftRotateDeg = ref(0)
|
||||||
@@ -91,9 +91,9 @@
|
|||||||
await cancelOrder({ orderId: orderNo.value })
|
await cancelOrder({ orderId: orderNo.value })
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
uni.showToast({ title: t('waiting.rentFailed'), icon: 'none' })
|
uni.showToast({ title: $t('waiting.rentFailed'), icon: 'none' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.reLaunch({ url: '/pages/index/index' })
|
uni.switchTab({ url: '/pages/index/index' })
|
||||||
}, 800)
|
}, 800)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,16 +129,16 @@
|
|||||||
// 超时保护:例如 60 秒
|
// 超时保护:例如 60 秒
|
||||||
timeoutTimer = setTimeout(() => {
|
timeoutTimer = setTimeout(() => {
|
||||||
stopAllTimers()
|
stopAllTimers()
|
||||||
uni.showToast({ title: t('waiting.timeout'), icon: 'none' })
|
uni.showToast({ title: $t('waiting.timeout'), icon: 'none' })
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.reLaunch({ url: '/pages/index/index' })
|
uni.switchTab({ url: '/pages/index/index' })
|
||||||
}, 800)
|
}, 800)
|
||||||
}, 60000)
|
}, 60000)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad((query) => {
|
onLoad((query) => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('waiting.title')
|
title: $t('waiting.title')
|
||||||
})
|
})
|
||||||
if (query) {
|
if (query) {
|
||||||
if (query.orderNo) {
|
if (query.orderNo) {
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ importers:
|
|||||||
axios-miniprogram-adapter:
|
axios-miniprogram-adapter:
|
||||||
specifier: 0.3.4
|
specifier: 0.3.4
|
||||||
version: 0.3.4
|
version: 0.3.4
|
||||||
html5-qrcode:
|
|
||||||
specifier: ^2.3.8
|
|
||||||
version: 2.3.8
|
|
||||||
uniapp-axios-adapter:
|
uniapp-axios-adapter:
|
||||||
specifier: ^0.3.2
|
specifier: ^0.3.2
|
||||||
version: 0.3.2(axios@1.10.0)
|
version: 0.3.2(axios@1.10.0)
|
||||||
@@ -86,6 +83,9 @@ packages:
|
|||||||
'@jridgewell/source-map@0.3.6':
|
'@jridgewell/source-map@0.3.6':
|
||||||
resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
|
resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.0':
|
||||||
|
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5':
|
'@jridgewell/sourcemap-codec@1.5.5':
|
||||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
@@ -491,9 +491,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
html5-qrcode@2.3.8:
|
|
||||||
resolution: {integrity: sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==}
|
|
||||||
|
|
||||||
immutable@5.1.3:
|
immutable@5.1.3:
|
||||||
resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==}
|
resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==}
|
||||||
|
|
||||||
@@ -746,7 +743,7 @@ snapshots:
|
|||||||
'@jridgewell/gen-mapping@0.3.8':
|
'@jridgewell/gen-mapping@0.3.8':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/set-array': 1.2.1
|
'@jridgewell/set-array': 1.2.1
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
'@jridgewell/resolve-uri@3.1.2': {}
|
'@jridgewell/resolve-uri@3.1.2': {}
|
||||||
@@ -758,12 +755,14 @@ snapshots:
|
|||||||
'@jridgewell/gen-mapping': 0.3.8
|
'@jridgewell/gen-mapping': 0.3.8
|
||||||
'@jridgewell/trace-mapping': 0.3.25
|
'@jridgewell/trace-mapping': 0.3.25
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
'@jridgewell/trace-mapping@0.3.25':
|
'@jridgewell/trace-mapping@0.3.25':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.0
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.1':
|
'@parcel/watcher-android-arm64@2.5.1':
|
||||||
optional: true
|
optional: true
|
||||||
@@ -1178,8 +1177,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
html5-qrcode@2.3.8: {}
|
|
||||||
|
|
||||||
immutable@5.1.3: {}
|
immutable@5.1.3: {}
|
||||||
|
|
||||||
is-extglob@2.1.1:
|
is-extglob@2.1.1:
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 847 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 348 B |
|
After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 676 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 868 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,783 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="order-detail-container">
|
|
||||||
<!-- 状态标题 -->
|
|
||||||
<!-- <view class="status-header">
|
|
||||||
<view class="status-icon">
|
|
||||||
<uv-icon name="checkbox-mark" size="40" color="#07c160"></uv-icon>
|
|
||||||
</view>
|
|
||||||
<text class="status-text">{{ statusText }}</text>
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<!-- 产品信息卡片 -->
|
|
||||||
<view class="product-card">
|
|
||||||
<image
|
|
||||||
:src="orderDetail.pictureUrl || orderDetail.productImage || '/static/default-product.png'"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="product-image"
|
|
||||||
></image>
|
|
||||||
<view class="product-info">
|
|
||||||
<view class="product-name">{{ orderDetail.productName || orderDetail.deviceName || $t('goods.defaultProductNameFull') }}</view>
|
|
||||||
<view class="product-style">款式:{{ orderDetail.optionName || orderDetail.style || '标准' }}</view>
|
|
||||||
<view class="product-price">¥{{ orderDetail.price || orderDetail.totalAmount }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 订单信息 -->
|
|
||||||
<view class="info-section">
|
|
||||||
<view class="section-title">订单信息</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">订单号</text>
|
|
||||||
<text class="value">{{ orderDetail.orderNo || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item" v-if="orderDetail.outTradeNo">
|
|
||||||
<text class="label">交易单号</text>
|
|
||||||
<text class="value">{{ orderDetail.outTradeNo }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">订单状态</text>
|
|
||||||
<text class="value" :style="{color: statusColor}">{{ statusText }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">创建时间</text>
|
|
||||||
<text class="value">{{ orderDetail.createTime || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">支付方式</text>
|
|
||||||
<text class="value">{{ paymentMethodText }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">支付时间</text>
|
|
||||||
<text class="value">{{ orderDetail.payTime || '未支付' }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">收货人</text>
|
|
||||||
<text class="value">{{ receiverInfo }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">收货地址</text>
|
|
||||||
<text class="value address">{{ orderDetail.receiverAddress || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item" v-if="orderDetail.expressageNo">
|
|
||||||
<text class="label">快递单号</text>
|
|
||||||
<text class="value">{{ orderDetail.expressageNo }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item" v-if="orderDetail.remark">
|
|
||||||
<text class="label">备注</text>
|
|
||||||
<text class="value address">{{ orderDetail.remark }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 费用信息 -->
|
|
||||||
<view class="info-section">
|
|
||||||
<view class="section-title">费用信息</view>
|
|
||||||
|
|
||||||
<view class="info-item" v-if="orderDetail.quantity">
|
|
||||||
<text class="label">数量</text>
|
|
||||||
<text class="value">x{{ orderDetail.quantity }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">单价</text>
|
|
||||||
<text class="value">¥ {{ orderDetail.price || orderDetail.totalAmount }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="info-item total">
|
|
||||||
<text class="label">合计</text>
|
|
||||||
<text class="value highlight">¥ {{ totalAmount }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部按钮 -->
|
|
||||||
<view class="bottom-actions">
|
|
||||||
<!-- 待付款状态:显示取消订单和立即支付 -->
|
|
||||||
<template v-if="orderDetail.status === 0 || orderDetail.status === '0'">
|
|
||||||
<view class="action-btn secondary" @click="onCancelOrder">
|
|
||||||
取消订单
|
|
||||||
</view>
|
|
||||||
<view class="action-btn primary" @click="onPayNow">
|
|
||||||
立即支付
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 已完成/已取消状态:显示删除订单和再次定制 -->
|
|
||||||
<template v-else-if="orderDetail.status === 3 || orderDetail.status === '3' || orderDetail.status === 4 || orderDetail.status === '4'">
|
|
||||||
<view class="action-btn secondary" @click="onDeleteOrder">
|
|
||||||
删除订单
|
|
||||||
</view>
|
|
||||||
<view class="action-btn primary" @click="onReorder(orderDetail.productId)">
|
|
||||||
再次定制
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- 其他状态:显示联系客服和再次定制 -->
|
|
||||||
<template v-else>
|
|
||||||
<view class="action-btn secondary" @click="onContactService">
|
|
||||||
联系客服
|
|
||||||
</view>
|
|
||||||
<view class="action-btn primary" @click="onReorder(orderDetail.productId)">
|
|
||||||
再次定制
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
||||||
import { onLoad } from '@dcloudio/uni-app';
|
|
||||||
import {
|
|
||||||
queryById,
|
|
||||||
getProductOrderDetail,
|
|
||||||
deleteProductOrder,
|
|
||||||
cancelProductOrder,
|
|
||||||
createWxPayment
|
|
||||||
} from '../../config/api/order.js';
|
|
||||||
// import { getSystemParamByKey } from '../../config/api/system.js';
|
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const orderDetail = ref({});
|
|
||||||
const orderId = ref('');
|
|
||||||
const countdownText = ref('');
|
|
||||||
let countdownTimer = null;
|
|
||||||
|
|
||||||
// 订单状态文本
|
|
||||||
const statusText = computed(() => {
|
|
||||||
const status = orderDetail.value.status;
|
|
||||||
if (status === 0 || status === '0') {
|
|
||||||
return '待付款';
|
|
||||||
}
|
|
||||||
if (status === 1 || status === '1') {
|
|
||||||
return '待发货';
|
|
||||||
}
|
|
||||||
if (status === 2 || status === '2') {
|
|
||||||
return '待收货';
|
|
||||||
}
|
|
||||||
if (status === 3 || status === '3') {
|
|
||||||
return '已完成';
|
|
||||||
}
|
|
||||||
if (status === 4 || status === '4') {
|
|
||||||
return '已取消';
|
|
||||||
}
|
|
||||||
if (status === 5 || status === '5') {
|
|
||||||
return '退款中';
|
|
||||||
}
|
|
||||||
return '待付款';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 订单状态颜色
|
|
||||||
const statusColor = computed(() => {
|
|
||||||
const status = orderDetail.value.status;
|
|
||||||
if (status === 0 || status === '0') {
|
|
||||||
return '#ff976a'; // 待付款 - 橙色
|
|
||||||
}
|
|
||||||
if (status === 1 || status === '1') {
|
|
||||||
return '#1989fa'; // 待发货 - 蓝色
|
|
||||||
}
|
|
||||||
if (status === 2 || status === '2') {
|
|
||||||
return '#1989fa'; // 待收货 - 蓝色
|
|
||||||
}
|
|
||||||
if (status === 3 || status === '3') {
|
|
||||||
return '#07c160'; // 已完成 - 绿色
|
|
||||||
}
|
|
||||||
if (status === 4 || status === '4') {
|
|
||||||
return '#999'; // 已取消 - 灰色
|
|
||||||
}
|
|
||||||
if (status === 5 || status === '5') {
|
|
||||||
return '#ff6b6b'; // 退款中 - 红色
|
|
||||||
}
|
|
||||||
return '#ff976a';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 支付方式文本
|
|
||||||
const paymentMethodText = computed(() => {
|
|
||||||
const payWay = orderDetail.value.payWay;
|
|
||||||
if (payWay === 'wx_score_pay') return '微信支付';
|
|
||||||
if (payWay === 'wx_global_pay') return '微信支付';
|
|
||||||
if (payWay === 'wx_member_pay') return '微信支付';
|
|
||||||
return '微信支付';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 收货人信息
|
|
||||||
const receiverInfo = computed(() => {
|
|
||||||
const name = orderDetail.value.receiverName || '-';
|
|
||||||
const phone = orderDetail.value.receiverPhone || '-';
|
|
||||||
return `${name} ${phone}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 总金额
|
|
||||||
const totalAmount = computed(() => {
|
|
||||||
return orderDetail.value.totalAmount || orderDetail.value.amount || orderDetail.value.payAmount || '99.0';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 页面加载
|
|
||||||
onLoad(async (options) => {
|
|
||||||
if (options && options.orderId) {
|
|
||||||
orderId.value = options.orderId;
|
|
||||||
await loadOrderDetail();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 根据订单状态设置页面标题
|
|
||||||
const updatePageTitle = () => {
|
|
||||||
const status = orderDetail.value.status;
|
|
||||||
let title = '订单详情';
|
|
||||||
|
|
||||||
if (status === 0 || status === '0') {
|
|
||||||
title = '待付款';
|
|
||||||
// 如果有倒计时文本,添加到标题中
|
|
||||||
if (countdownText.value) {
|
|
||||||
title = `${title} ${countdownText.value}`;
|
|
||||||
}
|
|
||||||
} else if (status === 1 || status === '1') {
|
|
||||||
title = '待发货';
|
|
||||||
} else if (status === 2 || status === '2') {
|
|
||||||
title = '待收货';
|
|
||||||
} else if (status === 3 || status === '3') {
|
|
||||||
title = '已完成';
|
|
||||||
} else if (status === 4 || status === '4') {
|
|
||||||
title = '已取消';
|
|
||||||
} else if (status === 5 || status === '5') {
|
|
||||||
title = '退款中';
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: title
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 启动倒计时
|
|
||||||
const startCountdown = () => {
|
|
||||||
// 清除之前的定时器
|
|
||||||
if (countdownTimer) {
|
|
||||||
clearInterval(countdownTimer);
|
|
||||||
countdownTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const status = orderDetail.value.status;
|
|
||||||
const expireTime = orderDetail.value.expireTime;
|
|
||||||
|
|
||||||
// 只有待付款状态且有过期时间才显示倒计时
|
|
||||||
if ((status === 0 || status === '0') && expireTime) {
|
|
||||||
// 计算倒计时
|
|
||||||
const updateCountdown = () => {
|
|
||||||
const now = new Date().getTime();
|
|
||||||
const expireTimestamp = new Date(expireTime).getTime();
|
|
||||||
const diff = expireTimestamp - now;
|
|
||||||
|
|
||||||
if (diff > 0) {
|
|
||||||
// 计算总分钟数和秒数
|
|
||||||
const totalMinutes = Math.floor(diff / (1000 * 60));
|
|
||||||
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
|
|
||||||
|
|
||||||
// 格式化为 MM:SS
|
|
||||||
const minutesStr = String(totalMinutes).padStart(2, '0');
|
|
||||||
const secondsStr = String(seconds).padStart(2, '0');
|
|
||||||
|
|
||||||
countdownText.value = `${minutesStr}:${secondsStr}`;
|
|
||||||
updatePageTitle();
|
|
||||||
} else {
|
|
||||||
// 倒计时结束
|
|
||||||
countdownText.value = '';
|
|
||||||
if (countdownTimer) {
|
|
||||||
clearInterval(countdownTimer);
|
|
||||||
countdownTimer = null;
|
|
||||||
}
|
|
||||||
// 重新加载订单详情(订单可能已自动取消)
|
|
||||||
loadOrderDetail();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 立即执行一次
|
|
||||||
updateCountdown();
|
|
||||||
// 每秒更新一次
|
|
||||||
countdownTimer = setInterval(updateCountdown, 1000);
|
|
||||||
} else {
|
|
||||||
countdownText.value = '';
|
|
||||||
updatePageTitle();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件卸载时清除定时器
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (countdownTimer) {
|
|
||||||
clearInterval(countdownTimer);
|
|
||||||
countdownTimer = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 加载订单详情
|
|
||||||
const loadOrderDetail = async () => {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '加载中...'
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await getProductOrderDetail(orderId.value);
|
|
||||||
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
const data = res.data;
|
|
||||||
orderDetail.value = {
|
|
||||||
// 基础信息
|
|
||||||
id: data.id,
|
|
||||||
orderNo: data.orderNo,
|
|
||||||
outTradeNo: data.outTradeNo,
|
|
||||||
userId: data.userId,
|
|
||||||
|
|
||||||
|
|
||||||
// 状态信息
|
|
||||||
status: data.status,
|
|
||||||
payStatus: data.payStatus,
|
|
||||||
|
|
||||||
// 金额信息
|
|
||||||
totalAmount: data.totalAmount,
|
|
||||||
payAmount: data.payAmount,
|
|
||||||
price: data.price,
|
|
||||||
|
|
||||||
// 时间信息
|
|
||||||
createTime: data.createTime,
|
|
||||||
updateTime: data.updateTime,
|
|
||||||
payTime: data.payTime,
|
|
||||||
expireTime: data.expireTime, // 订单自动取消时间
|
|
||||||
|
|
||||||
// 收货信息
|
|
||||||
receiverName: data.receiverName,
|
|
||||||
receiverPhone: data.receiverPhone,
|
|
||||||
receiverAddress: data.receiverAddress,
|
|
||||||
|
|
||||||
// 物流信息
|
|
||||||
expressageNo: data.expressageNo,
|
|
||||||
|
|
||||||
// 备注
|
|
||||||
remark: data.remark,
|
|
||||||
|
|
||||||
// 商品信息
|
|
||||||
productName: data.productName,
|
|
||||||
optionName: data.optionName,
|
|
||||||
pictureUrl: data.pictureUrl,
|
|
||||||
color: data.color,
|
|
||||||
quantity: data.quantity,
|
|
||||||
productId:data.productId,
|
|
||||||
|
|
||||||
// 兼容旧字段
|
|
||||||
orderId: data.id,
|
|
||||||
deviceId: data.deviceNo || '',
|
|
||||||
deviceName: data.deviceName || '',
|
|
||||||
style: data.optionName || data.style || data.deviceStyle || '',
|
|
||||||
payWay: data.payWay || 'wx_global_pay',
|
|
||||||
phone: data.receiverPhone,
|
|
||||||
address: data.receiverAddress,
|
|
||||||
deposit: data.deposit || '0',
|
|
||||||
package: data.package || '',
|
|
||||||
amount: data.payAmount,
|
|
||||||
productImage: data.pictureUrl || data.productImage || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
// 启动倒计时
|
|
||||||
startCountdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('加载订单详情失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: '加载失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 退款/售后
|
|
||||||
const onRefund = () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '退款/售后功能开发中',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 取消订单
|
|
||||||
const onCancelOrder = () => {
|
|
||||||
showModalI18n({
|
|
||||||
title: t('common.tips'),
|
|
||||||
content: t('order.confirmCancelContent'),
|
|
||||||
success: async (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '取消中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await cancelProductOrder(orderDetail.value.id);
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
|
|
||||||
if (result && result.code === 200) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '订单已取消',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 重新加载订单详情
|
|
||||||
await loadOrderDetail();
|
|
||||||
} else {
|
|
||||||
throw new Error(result?.msg || '取消失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('取消订单失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '取消失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除订单
|
|
||||||
const onDeleteOrder = () => {
|
|
||||||
showModalI18n({
|
|
||||||
title: t('common.tips'),
|
|
||||||
content: t('order.confirmDeleteContent'),
|
|
||||||
success: async (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '删除中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await deleteProductOrder(orderDetail.value.id);
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
|
|
||||||
if (result && result.code === 200) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '删除成功',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 返回订单列表
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.navigateBack();
|
|
||||||
}, 1500);
|
|
||||||
} else {
|
|
||||||
throw new Error(result?.msg || '删除失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('删除订单失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '删除失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 立即支付
|
|
||||||
const onPayNow = async () => {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '正在创建支付...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const res = await createWxPayment(orderDetail.value.orderNo);
|
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
|
||||||
uni.hideLoading();
|
|
||||||
|
|
||||||
const payParams = res.data;
|
|
||||||
|
|
||||||
// 调用微信支付
|
|
||||||
uni.requestPayment({
|
|
||||||
timeStamp: payParams.timeStamp,
|
|
||||||
nonceStr: payParams.nonceStr,
|
|
||||||
package: payParams.package,
|
|
||||||
signType: payParams.signType,
|
|
||||||
paySign: payParams.paySign,
|
|
||||||
success: async (payRes) => {
|
|
||||||
console.log('支付成功:', payRes);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付成功',
|
|
||||||
icon: 'success',
|
|
||||||
duration: 2000
|
|
||||||
});
|
|
||||||
|
|
||||||
// 重新加载订单详情
|
|
||||||
await loadOrderDetail();
|
|
||||||
},
|
|
||||||
fail: async (err) => {
|
|
||||||
console.error('支付失败:', err);
|
|
||||||
|
|
||||||
// 判断是用户取消还是支付失败
|
|
||||||
if (err.errMsg && err.errMsg.includes('cancel')) {
|
|
||||||
// 用户取消支付,调用取消订单接口
|
|
||||||
try {
|
|
||||||
await cancelProductOrder(orderDetail.value.id);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付已取消',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 重新加载订单详情
|
|
||||||
await loadOrderDetail();
|
|
||||||
} catch (cancelError) {
|
|
||||||
console.error('取消订单失败:', cancelError);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付已取消',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 支付失败
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付失败,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: res?.msg || '创建支付订单失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('支付异常:', error);
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '支付失败,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 联系客服
|
|
||||||
const onContactService = async () => {
|
|
||||||
|
|
||||||
|
|
||||||
const phoneNumber = uni.getStorageSync('customerPhone');
|
|
||||||
|
|
||||||
// 拨打客服电话
|
|
||||||
uni.makePhoneCall({
|
|
||||||
phoneNumber: phoneNumber,
|
|
||||||
success: () => {
|
|
||||||
console.log('拨打电话成功');
|
|
||||||
},
|
|
||||||
fail: (err) => {
|
|
||||||
console.error('拨打电话失败:', err);
|
|
||||||
uni.showToast({
|
|
||||||
title: '拨打电话失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// 再次定制
|
|
||||||
const onReorder = (order) => {
|
|
||||||
if(order){
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/subPackages/business/device-goods?productId=${order}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// console.log(order);
|
|
||||||
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.order-detail-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #f7f8fa;
|
|
||||||
padding-bottom: 120rpx;
|
|
||||||
|
|
||||||
// 状态头部
|
|
||||||
.status-header {
|
|
||||||
background: #fff;
|
|
||||||
padding: 60rpx 0 40rpx;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.status-icon {
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
font-size: 36rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 产品卡片
|
|
||||||
.product-card {
|
|
||||||
background: #fff;
|
|
||||||
margin: 20rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
display: flex;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
.product-image {
|
|
||||||
width: 120rpx;
|
|
||||||
height: 120rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
background: #f5f5f5;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-info {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.product-name {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin-bottom: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-style {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-price {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #07c160;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 信息区块
|
|
||||||
.info-section {
|
|
||||||
background: #fff;
|
|
||||||
margin: 20rpx;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
height: 50rpx;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: #999;
|
|
||||||
flex-shrink: 0;
|
|
||||||
width: 140rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
color: #333;
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
word-break: break-all;
|
|
||||||
|
|
||||||
&.address {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.total {
|
|
||||||
margin-top: 12rpx;
|
|
||||||
padding-top: 20rpx;
|
|
||||||
border-top: 1rpx dashed #e5e5e5;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value.highlight {
|
|
||||||
color: #07c160;
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部按钮
|
|
||||||
.bottom-actions {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: #fff;
|
|
||||||
padding: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 20rpx;
|
|
||||||
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
width: 180rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
border-radius: 12rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&.secondary {
|
|
||||||
background: #fff;
|
|
||||||
color: #07c160;
|
|
||||||
border: 2rpx solid #07c160;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
background: #07c160;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -1,864 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="order-container">
|
|
||||||
<!-- 状态切换 -->
|
|
||||||
<view class="status-tabs">
|
|
||||||
<view v-for="(tab, index) in orderStatusTabs" :key="index" class="tab-item"
|
|
||||||
:class="{ active: currentTab === index }" @click="switchTab(index)">
|
|
||||||
{{ tab.text }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 订单列表 -->
|
|
||||||
<view class="order-list">
|
|
||||||
<view class="empty-state" v-if="orderList.length === 0">
|
|
||||||
<view class="empty-icon">
|
|
||||||
<image src="/static/orderList.png" mode="aspectFill" class="empty-icon" lazy-load="true"></image>
|
|
||||||
</view>
|
|
||||||
<text class="empty-text">{{ $t('order.noOrderRecord') }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<DeviceOrderItemCard
|
|
||||||
v-for="(order, index) in orderList"
|
|
||||||
:key="index"
|
|
||||||
:order="order"
|
|
||||||
@click="navigateToDeviceOrderDetail"
|
|
||||||
@delete="handleDeleteOrder"
|
|
||||||
@pay="handlePayment"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
reactive,
|
|
||||||
onMounted,
|
|
||||||
onUnmounted
|
|
||||||
} from 'vue';
|
|
||||||
import DeviceOrderItemCard from '../../components/DeviceOrderItemCard.vue';
|
|
||||||
import {
|
|
||||||
onLoad,
|
|
||||||
onShow
|
|
||||||
} from '@dcloudio/uni-app';
|
|
||||||
import {
|
|
||||||
getOrderList,
|
|
||||||
getProductOrderList,
|
|
||||||
queryById,
|
|
||||||
getOrderByOrderNoScorePayStatus,
|
|
||||||
cancelOrder,
|
|
||||||
createWxPayment,
|
|
||||||
deleteProductOrder,
|
|
||||||
cancelProductOrder,
|
|
||||||
} from '../../config/api/order.js';
|
|
||||||
import{
|
|
||||||
createProductOrder
|
|
||||||
}from "@/config/api/product.js"
|
|
||||||
import {
|
|
||||||
updateUserBalance
|
|
||||||
} from '../../config/api/user.js';
|
|
||||||
import {
|
|
||||||
URL
|
|
||||||
} from '../../config/url.js';
|
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
// 初始化状态
|
|
||||||
const currentTab = ref(0);
|
|
||||||
const orderList = ref([]);
|
|
||||||
|
|
||||||
// 订单状态映射
|
|
||||||
const orderStatusMap = reactive({
|
|
||||||
'0': {
|
|
||||||
text: '待付款',
|
|
||||||
class: 'status-waiting'
|
|
||||||
},
|
|
||||||
'1': {
|
|
||||||
text: '待发货',
|
|
||||||
class: 'status-shipping'
|
|
||||||
},
|
|
||||||
'2': {
|
|
||||||
text: '待收货',
|
|
||||||
class: 'status-receiving'
|
|
||||||
},
|
|
||||||
'3': {
|
|
||||||
text: '已完成',
|
|
||||||
class: 'status-finished'
|
|
||||||
},
|
|
||||||
'4': {
|
|
||||||
text: '已取消',
|
|
||||||
class: 'status-cancelled'
|
|
||||||
},
|
|
||||||
'5': {
|
|
||||||
text: '退款中',
|
|
||||||
class: 'status-refunding'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 订单状态标签
|
|
||||||
const orderStatusTabs = reactive([
|
|
||||||
{
|
|
||||||
text: '全部',
|
|
||||||
status: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '待付款',
|
|
||||||
status: [0]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '待发货',
|
|
||||||
status: [1]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '待收货',
|
|
||||||
status: [2]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '已完成',
|
|
||||||
status: [3]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '已取消',
|
|
||||||
status: [4]
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// text: '退款中',
|
|
||||||
// status: [5]
|
|
||||||
// }
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 页面加载
|
|
||||||
onLoad(async (options) => {
|
|
||||||
// 如果有传入orderId参数,说明是从设备租借页面跳转过来的
|
|
||||||
if (options && options.orderId) {
|
|
||||||
try {
|
|
||||||
// 获取特定订单信息
|
|
||||||
const res = await queryById(options.orderId);
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
// 获取到的订单数据
|
|
||||||
const orderData = res.data;
|
|
||||||
|
|
||||||
// 使用实际的startTime字段,如果没有则尝试使用createTime
|
|
||||||
const orderStartTime = orderData.startTime || orderData.createTime || '';
|
|
||||||
|
|
||||||
// 格式化订单数据
|
|
||||||
const formattedOrder = {
|
|
||||||
orderNo: orderData.orderId,
|
|
||||||
status: orderData.orderStatus,
|
|
||||||
deviceId: orderData.deviceNo,
|
|
||||||
payWay: orderData.payWay,
|
|
||||||
startTime: orderStartTime,
|
|
||||||
endTime: orderData.endTime || '',
|
|
||||||
positionName: orderData.positionName || orderData.positionLocation || '',
|
|
||||||
deviceName: orderData.deviceName || '',
|
|
||||||
amount: orderData.payAmount || orderData.actualDeviceAmount || orderData.currentFee || orderData.residueAmount || '0.00'
|
|
||||||
};
|
|
||||||
|
|
||||||
// 将订单添加到列表开头
|
|
||||||
orderList.value = [formattedOrder, ...orderList.value];
|
|
||||||
|
|
||||||
// 根据订单状态切换到对应标签
|
|
||||||
const tabIndex = orderStatusTabs.findIndex(tab =>
|
|
||||||
tab.status.includes(orderData.orderStatus)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tabIndex !== -1) {
|
|
||||||
switchTab(tabIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取订单详情失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取订单列表
|
|
||||||
await loadOrderList();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 页面显示时刷新订单列表
|
|
||||||
onShow(async () => {
|
|
||||||
// 根据当前选中的标签刷新对应状态的订单
|
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
|
||||||
await loadOrderList(statusValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 切换标签
|
|
||||||
const switchTab = async (index) => {
|
|
||||||
currentTab.value = index;
|
|
||||||
// 根据状态获取订单列表
|
|
||||||
const statusArray = orderStatusTabs[index].status;
|
|
||||||
// 如果是全部,传undefined;否则传第一个数字状态值
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
|
||||||
await loadOrderList(statusValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 加载订单列表
|
|
||||||
const loadOrderList = async (statusList) => {
|
|
||||||
try {
|
|
||||||
let params = {};
|
|
||||||
if(statusList !== undefined){
|
|
||||||
params = {
|
|
||||||
status: statusList
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const res = await getProductOrderList(params);
|
|
||||||
|
|
||||||
// 根据实际接口返回结构处理数据
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
// 支持两种数据结构:res.data.rows 或 res.data.records
|
|
||||||
const dataList = res.data.rows || res.data.records || [];
|
|
||||||
|
|
||||||
// 处理订单列表数据
|
|
||||||
orderList.value = dataList.map(item => {
|
|
||||||
return {
|
|
||||||
// 基础信息
|
|
||||||
id: item.id,
|
|
||||||
orderNo: item.orderNo,
|
|
||||||
orderId: item.id,
|
|
||||||
userId: item.userId,
|
|
||||||
|
|
||||||
// 状态信息
|
|
||||||
status: item.status,
|
|
||||||
orderStatus: item.status,
|
|
||||||
payStatus: item.payStatus,
|
|
||||||
|
|
||||||
// 金额信息
|
|
||||||
totalAmount: item.totalAmount,
|
|
||||||
payAmount: item.payAmount,
|
|
||||||
price: item.price,
|
|
||||||
|
|
||||||
// 时间信息
|
|
||||||
createTime: item.createTime,
|
|
||||||
payTime: item.payTime,
|
|
||||||
|
|
||||||
// 收货信息
|
|
||||||
receiverName: item.receiverName,
|
|
||||||
receiverPhone: item.receiverPhone,
|
|
||||||
receiverAddress: item.receiverAddress,
|
|
||||||
|
|
||||||
// 物流信息
|
|
||||||
expressageNo: item.expressageNo,
|
|
||||||
|
|
||||||
// 备注
|
|
||||||
remark: item.remark,
|
|
||||||
|
|
||||||
// 商品信息
|
|
||||||
productName: item.productName,
|
|
||||||
optionName: item.optionName,
|
|
||||||
pictureUrl: item.pictureUrl,
|
|
||||||
color: item.color,
|
|
||||||
quantity: item.quantity,
|
|
||||||
|
|
||||||
// 兼容旧字段
|
|
||||||
deviceId: item.deviceNo || '',
|
|
||||||
deviceName: item.productName || item.deviceName || '',
|
|
||||||
productImage: item.pictureUrl || '',
|
|
||||||
style: item.optionName || item.style || item.deviceStyle || '',
|
|
||||||
payWay: item.payWay || '',
|
|
||||||
startTime: item.createTime || item.startTime || '',
|
|
||||||
endTime: item.endTime || '',
|
|
||||||
positionName: item.positionName || item.positionLocation || '',
|
|
||||||
amount: item.payAmount || item.totalAmount || '0.00'
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取订单列表失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.getOrderListFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理订单完成事件
|
|
||||||
const handleOrderCompleted = (orderData) => {
|
|
||||||
console.log('订单列表页收到订单完成事件:', orderData)
|
|
||||||
// 刷新订单列表,根据当前选中的标签刷新对应状态的订单
|
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0]
|
|
||||||
loadOrderList(statusValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置页面标题并监听订单完成事件
|
|
||||||
onMounted(() => {
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('order.myDeviceOrders')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听订单完成事件
|
|
||||||
uni.$on('orderCompleted', handleOrderCompleted)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 页面卸载时移除事件监听
|
|
||||||
onUnmounted(() => {
|
|
||||||
uni.$off('orderCompleted', handleOrderCompleted)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 同步订单状态
|
|
||||||
const getOrderStatus = async (order) => {
|
|
||||||
try {
|
|
||||||
const res = await getOrderByOrderNoScorePayStatus(order.orderNo);
|
|
||||||
if (res.code === 200) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.syncSuccess'),
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
|
||||||
await loadOrderList(statusValue);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.syncFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 跳转到订单详情页面(统一入口)
|
|
||||||
const navigateToOrderDetail = (order) => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/order/detail?orderId=${order.orderId || order.orderNo}&deviceId=${order.deviceId}`
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件事件:归还设备(实际跳转到订单详情页)
|
|
||||||
const onReturnDevice = (order) => {
|
|
||||||
navigateToOrderDetail(order);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 跳转到订单详情页
|
|
||||||
const navigateToDetails = (order) => {
|
|
||||||
navigateToOrderDetail(order);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 立即支付(对齐 device-goods.vue,多平台支付)
|
|
||||||
const handlePayment = async (order) => {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '正在创建支付...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
console.log('订单列表立即支付,订单信息:', order);
|
|
||||||
|
|
||||||
// 根据当前运行环境确定支付平台(与 device-goods.vue 保持一致)
|
|
||||||
let paymentPlatform = 'WECHAT'; // 默认微信
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
paymentPlatform = 'ALIPAY';
|
|
||||||
// #endif
|
|
||||||
// #ifdef H5
|
|
||||||
// H5 预留 Antom 支付,这里暂时仍按微信处理,如需接入可改为 ANTOM 并补充 paymentType / osType
|
|
||||||
paymentPlatform = 'WECHAT';
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 使用商品多支付平台统一下单接口,对已有订单进行支付
|
|
||||||
const res = await createProductOrder({
|
|
||||||
orderNo: order.orderNo,
|
|
||||||
paymentPlatform
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
|
||||||
uni.hideLoading();
|
|
||||||
|
|
||||||
// 统一获取平台订单号(商品统一支付订单号)
|
|
||||||
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo;
|
|
||||||
|
|
||||||
// ====================== 微信小程序支付 ======================
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
const payParams = res.data;
|
|
||||||
uni.requestPayment({
|
|
||||||
timeStamp: payParams.timeStamp,
|
|
||||||
nonceStr: payParams.nonceStr,
|
|
||||||
package: payParams.package,
|
|
||||||
signType: payParams.signType,
|
|
||||||
paySign: payParams.paySign,
|
|
||||||
success: async (payRes) => {
|
|
||||||
console.log('支付成功:', payRes);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付成功',
|
|
||||||
icon: 'success',
|
|
||||||
duration: 2000
|
|
||||||
});
|
|
||||||
|
|
||||||
// 刷新订单列表
|
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
|
||||||
await loadOrderList(statusValue);
|
|
||||||
},
|
|
||||||
fail: async (payErr) => {
|
|
||||||
console.error('支付失败:', payErr);
|
|
||||||
|
|
||||||
// 判断是用户取消还是支付失败
|
|
||||||
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
|
|
||||||
// 用户取消支付,这里预留调用取消订单接口
|
|
||||||
try {
|
|
||||||
// await cancelProductOrder(outOrderNo || order.orderNo);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付已取消',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 刷新订单列表
|
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
|
||||||
await loadOrderList(statusValue);
|
|
||||||
} catch (cancelError) {
|
|
||||||
console.error('取消订单失败:', cancelError);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付已取消',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 支付失败
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付失败,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// ====================== 支付宝小程序支付 ======================
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
console.log(res.data, '支付宝支付参数');
|
|
||||||
|
|
||||||
const tradeNO = res.data.tradeNo || res.data.tradeNO;
|
|
||||||
if (!tradeNO) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '未获取到支付宝支付参数',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
my.tradePay({
|
|
||||||
tradeNO,
|
|
||||||
success: async (payRes) => {
|
|
||||||
console.log('支付宝支付结果:', payRes);
|
|
||||||
if (payRes.resultCode === '9000') {
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付成功',
|
|
||||||
icon: 'success',
|
|
||||||
duration: 2000
|
|
||||||
});
|
|
||||||
|
|
||||||
// 支付成功后刷新订单列表
|
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
|
||||||
await loadOrderList(statusValue);
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付失败,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付失败,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// ====================== H5 环境(预留 Antom 支付) ======================
|
|
||||||
// #ifdef H5
|
|
||||||
uni.showToast({
|
|
||||||
title: '当前环境暂不支持购买,请使用微信或支付宝小程序',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
// #endif
|
|
||||||
} else {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: res?.msg || '创建支付订单失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('支付异常:', error);
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '支付失败,请重试',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 取消订单
|
|
||||||
const handleCancelOrder = async (order) => {
|
|
||||||
try {
|
|
||||||
showModalI18n({
|
|
||||||
title: t('order.confirmCancel'),
|
|
||||||
content: t('order.confirmCancelContent'),
|
|
||||||
success: async (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
uni.showLoading({
|
|
||||||
title: t('common.processing')
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await cancelOrder({
|
|
||||||
orderId: order.orderNo
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.cancelSuccess'),
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 刷新订单列表
|
|
||||||
await loadOrderList();
|
|
||||||
} else {
|
|
||||||
throw new Error(result.msg || t('order.cancelFailed'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || t('order.cancelFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 跳转到设备订单详情页
|
|
||||||
const navigateToDeviceOrderDetail = (order) => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/subPackages/business/device-orderDetail?orderId=${order.orderId || order.orderNo}`
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理删除订单
|
|
||||||
const handleDeleteOrder = (order) => {
|
|
||||||
showModalI18n({
|
|
||||||
title: t('common.tips'),
|
|
||||||
content: t('order.confirmDeleteContent'),
|
|
||||||
success: async (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: '删除中...',
|
|
||||||
mask: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// 调用删除订单接口
|
|
||||||
const result = await deleteProductOrder(order.id || order.orderId);
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
|
|
||||||
if (result && result.code === 200) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '删除成功',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 刷新订单列表
|
|
||||||
const statusArray = orderStatusTabs[currentTab.value].status;
|
|
||||||
const statusValue = statusArray.length === 0 ? undefined : statusArray[0];
|
|
||||||
await loadOrderList(statusValue);
|
|
||||||
} else {
|
|
||||||
throw new Error(result?.msg || '删除失败');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
console.error('删除订单失败:', error);
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || '删除失败',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.order-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #f7f8fa;
|
|
||||||
padding-bottom: 30rpx;
|
|
||||||
|
|
||||||
// 状态标签栏
|
|
||||||
.status-tabs {
|
|
||||||
display: flex;
|
|
||||||
background: #fff;
|
|
||||||
padding: 0 20rpx;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
|
||||||
|
|
||||||
.tab-item {
|
|
||||||
flex: 1;
|
|
||||||
height: 90rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: #07c160;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 40rpx;
|
|
||||||
height: 4rpx;
|
|
||||||
background: #07c160;
|
|
||||||
border-radius: 2rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 订单列表
|
|
||||||
.order-list {
|
|
||||||
padding: 20rpx;
|
|
||||||
|
|
||||||
// 订单项
|
|
||||||
.order-item {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
// 订单头部
|
|
||||||
.order-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 24rpx;
|
|
||||||
border-bottom: 1rpx solid #f0f0f0;
|
|
||||||
|
|
||||||
.order-id {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-status {
|
|
||||||
font-size: 26rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&.status-waiting {
|
|
||||||
color: #FF9800;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-shipping {
|
|
||||||
color: #1989fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-receiving {
|
|
||||||
color: #1989fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-finished {
|
|
||||||
color: #07c160;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-cancelled {
|
|
||||||
color: #9E9E9E;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-refunding {
|
|
||||||
color: #ff6b6b;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.status-express-return {
|
|
||||||
color: #FF9800;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 订单内容
|
|
||||||
.order-body {
|
|
||||||
padding: 24rpx;
|
|
||||||
|
|
||||||
.device-info {
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.device-left {
|
|
||||||
flex: 1;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
|
|
||||||
.device-name {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-id {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-right {
|
|
||||||
|
|
||||||
// 支付分标识
|
|
||||||
.payment-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 6rpx 12rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.wx-score {
|
|
||||||
background: rgba(7, 193, 96, 0.08);
|
|
||||||
|
|
||||||
.badge-icon {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 26rpx;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #07c160;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
margin: 0 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.member {
|
|
||||||
background: rgba(25, 118, 210, 0.08);
|
|
||||||
|
|
||||||
.badge-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #1976D2;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.deposit {
|
|
||||||
background: #f5f5f5;
|
|
||||||
|
|
||||||
.badge-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #666;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-times {
|
|
||||||
.time-row {
|
|
||||||
display: flex;
|
|
||||||
font-size: 26rpx;
|
|
||||||
margin-bottom: 8rpx;
|
|
||||||
|
|
||||||
.time-label {
|
|
||||||
color: #999;
|
|
||||||
width: 140rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time-value {
|
|
||||||
color: #333;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 订单底部
|
|
||||||
.order-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20rpx 24rpx;
|
|
||||||
background: #fafafa;
|
|
||||||
border-top: 1rpx solid #f0f0f0;
|
|
||||||
|
|
||||||
.price {
|
|
||||||
font-size: 34rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #ff6b6b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
.action-item {
|
|
||||||
font-size: 26rpx;
|
|
||||||
padding: 10rpx 30rpx;
|
|
||||||
border-radius: 30rpx;
|
|
||||||
margin-left: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 10rpx;
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
background: #07c160;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.secondary {
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
border: 1rpx solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 空状态
|
|
||||||
.empty-state {
|
|
||||||
padding: 100rpx 0;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
width: 180rpx;
|
|
||||||
height: 180rpx;
|
|
||||||
margin: 0 auto 30rpx;
|
|
||||||
background: #f5f5f5;
|
|
||||||
// border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="my-card-page">
|
|
||||||
<!-- 会员卡列表 -->
|
|
||||||
<view class="card-list" v-if="cardList.length > 0">
|
|
||||||
<view v-for="card in cardList" :key="card.id" style="position: relative;background-color: #f5f5f5;"
|
|
||||||
@click="viewCardDetail(card)" :style="card.cardType==='COUNT'?'height: 240rpx;':'height: 240rpx;'">
|
|
||||||
<view
|
|
||||||
style="height: 120rpx;background-color: #ffffff;z-index: 999;border-radius: 25rpx;padding: 32rpx;position: absolute;top: 0;left: 0;right: 0;">
|
|
||||||
<!-- 卡片头部:标题和日期 -->
|
|
||||||
<view class="card-header">
|
|
||||||
<text class="card-name">{{ card.name }}</text>
|
|
||||||
<view class="card-date">
|
|
||||||
<text class="date-text" v-if="card.status !== 'expired'">{{ card.endDate }}{{
|
|
||||||
$t('myCard.expire') }}</text>
|
|
||||||
<text class="date-text expired" v-else>{{ $t('myCard.expiredOn') }}{{ card.endDate }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 地区信息 -->
|
|
||||||
<view class="card-region">
|
|
||||||
<text
|
|
||||||
class="region-text">{{ $t('myCard.onlyForRegionBefore') }}{{ card.positionName }}{{ $t('myCard.onlyForRegionAfter') }}</text>
|
|
||||||
<!-- 状态标签 / 去使用按钮 -->
|
|
||||||
<view v-if="card.status !== 'expired'" class="status-tag active"
|
|
||||||
style="display: flex; align-items: center; gap: 4rpx; background-color: #FFE2B8; border-radius: 20rpx; padding: 6rpx 20rpx;"
|
|
||||||
@click.stop="handleUseCard(card)">
|
|
||||||
<text class="status-text" style="color: #A16300;">{{ $t('myCard.toUse') }}</text>
|
|
||||||
<!-- <uv-icon name="scan" size="12" color="#D4A574"></uv-icon> -->
|
|
||||||
</view>
|
|
||||||
<view v-else class="status-tag" :class="getStatusClass(card.status)">
|
|
||||||
<text class="status-text">{{ getStatusText(card.status) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 使用情况和操作按钮 -->
|
|
||||||
<view style="position: absolute; bottom: -20rpx; left: 0; right: 0; padding: 20rpx;z-index:1;">
|
|
||||||
<view class="card-footer">
|
|
||||||
<!-- 次卡信息 -->
|
|
||||||
<view v-if="card.cardType === 'COUNT'" class="card-usage-info">
|
|
||||||
<text class="usage-text">{{ $t('myCard.remainingTimes') }}{{ card.remainingCount }}{{
|
|
||||||
$t('myCard.times') }}</text>
|
|
||||||
</view>
|
|
||||||
<!-- 时长卡信息 -->
|
|
||||||
<view v-else class="card-usage-info">
|
|
||||||
<text class="usage-text">每日限用次数:{{card.dailyLimitCount}}次</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<view class="card-actions">
|
|
||||||
<!-- 续卡按钮(仅次卡显示) -->
|
|
||||||
<view v-if="card.cardType === 'COUNT'" class="renew-btn" @click.stop="renewCard(card)">
|
|
||||||
<text class="renew-text">{{ $t('myCard.renew') }}</text>
|
|
||||||
<uv-icon name="arrow-right" size="14" color="#D4A574"></uv-icon>
|
|
||||||
|
|
||||||
</view>
|
|
||||||
<view v-else class="renew-btn">
|
|
||||||
<text class="renew-text">单次限时:{{card.singleLimitMinutes}}分钟</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 空状态 -->
|
|
||||||
<view class="empty-state" v-else>
|
|
||||||
<image class="empty-icon" src="/static/empty-card.png" mode="aspectFit"></image>
|
|
||||||
<text class="empty-text">{{ $t('myCard.noCards') }}</text>
|
|
||||||
<view class="buy-btn" @click="goToBuy">
|
|
||||||
<text class="buy-text">{{ $t('myCard.buyNow') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
onMounted
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
useI18n
|
|
||||||
} from '@/utils/i18n.js'
|
|
||||||
import {
|
|
||||||
getMemberCardsByStatus
|
|
||||||
} from '@/config/api/member.js'
|
|
||||||
import {
|
|
||||||
getQueryString
|
|
||||||
} from '@/util/index.js'
|
|
||||||
import {
|
|
||||||
getDeviceInfo
|
|
||||||
} from '@/config/api/device.js'
|
|
||||||
import {
|
|
||||||
getInUseOrder,
|
|
||||||
getUnpaidOrder
|
|
||||||
} from '@/config/api/order.js'
|
|
||||||
const {
|
|
||||||
t
|
|
||||||
} = useI18n()
|
|
||||||
|
|
||||||
// 会员卡列表
|
|
||||||
const cardList = ref([])
|
|
||||||
|
|
||||||
// 获取会员卡列表
|
|
||||||
const getCardList = async () => {
|
|
||||||
try {
|
|
||||||
const response = await getMemberCardsByStatus()
|
|
||||||
// 处理API返回的数据,转换为模板需要的格式
|
|
||||||
if (response.code === 200 && response.data) {
|
|
||||||
cardList.value = response.data.map(item => ({
|
|
||||||
id: item.id,
|
|
||||||
name: item.cardName,
|
|
||||||
cardType: item.cardType, // TIME 或 COUNT
|
|
||||||
// 次卡相关
|
|
||||||
totalCount: item.totalCount,
|
|
||||||
remainingCount: item.remainingCount,
|
|
||||||
singleLimitMinutesForCount: item.singleLimitMinutesForCount,
|
|
||||||
// 时长卡相关
|
|
||||||
cycleDays: item.cycleDays,
|
|
||||||
dailyLimitCount: item.dailyLimitCount,
|
|
||||||
singleLimitMinutes: item.singleLimitMinutes,
|
|
||||||
currentCycleStartTime: item.currentCycleStartTime,
|
|
||||||
currentCycleUsedCount: item.currentCycleUsedCount,
|
|
||||||
// 通用信息
|
|
||||||
status: item.status,
|
|
||||||
startDate: item.startTime?.split(' ')[0] || item.startTime,
|
|
||||||
endDate: item.endTime?.split(' ')[0] || item.endTime,
|
|
||||||
positionId: item.positionId,
|
|
||||||
positionName: item.positionName,
|
|
||||||
purchasePrice: item.purchasePrice,
|
|
||||||
remark: item.remark
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
cardList.value = []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取会员卡列表失败:', error)
|
|
||||||
uni.showToast({
|
|
||||||
title: t('myCard.getListFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算进度条宽度
|
|
||||||
const getProgressWidth = (used, total) => {
|
|
||||||
if (!total || total === 0) return '0%'
|
|
||||||
const percentage = (used / total) * 100
|
|
||||||
return `${Math.min(percentage, 100)}%`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态类名
|
|
||||||
const getStatusClass = (status) => {
|
|
||||||
const statusMap = {
|
|
||||||
'unused': 'active',
|
|
||||||
'expired': 'expired',
|
|
||||||
'used': 'used',
|
|
||||||
'active': 'active' // 兼容原始状态
|
|
||||||
}
|
|
||||||
return statusMap[status] || 'active' // 默认为active
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态文本
|
|
||||||
const getStatusText = (status) => {
|
|
||||||
const statusMap = {
|
|
||||||
'unused': t('myCard.active'), // unused表示未使用,即活跃状态
|
|
||||||
'expired': t('myCard.expired'),
|
|
||||||
'used': t('myCard.used'),
|
|
||||||
'active': t('myCard.active') // 兼容原始状态
|
|
||||||
}
|
|
||||||
return statusMap[status] || t('myCard.active') // 默认为active
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看卡详情
|
|
||||||
const viewCardDetail = (card) => {
|
|
||||||
// TODO: 跳转到卡详情页面
|
|
||||||
// uni.showToast({
|
|
||||||
// title: t('common.functionDeveloping'),
|
|
||||||
// icon: 'none'
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 续卡
|
|
||||||
const renewCard = (card) => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/purchase/index?positionId=${card.positionId}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 去使用会员卡
|
|
||||||
const handleUseCard = async (card) => {
|
|
||||||
try {
|
|
||||||
const scanResult = await new Promise((resolve, reject) => {
|
|
||||||
uni.scanCode({
|
|
||||||
success: resolve,
|
|
||||||
fail: reject
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('扫码结果:', scanResult);
|
|
||||||
let deviceNo;
|
|
||||||
// 兼容不同平台的扫码结果
|
|
||||||
if (scanResult.scanType === 'QR_CODE' || scanResult.scanType === 'qrCode') {
|
|
||||||
deviceNo = getQueryString(scanResult.result, 'deviceNo')
|
|
||||||
} else if (scanResult.path) {
|
|
||||||
deviceNo = getQueryString(scanResult.path, 'deviceNo')
|
|
||||||
} else {
|
|
||||||
deviceNo = scanResult.result
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!deviceNo) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('home.invalidQRCode'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({
|
|
||||||
title: t('common.getting')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 检查是否有使用中的订单
|
|
||||||
const inUseRes = await getInUseOrder()
|
|
||||||
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
|
|
||||||
uni.hideLoading()
|
|
||||||
const inUseOrder = inUseRes.data
|
|
||||||
uni.reLaunch({
|
|
||||||
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有待支付订单
|
|
||||||
const orderRes = await getUnpaidOrder()
|
|
||||||
if (orderRes && orderRes.code === 200 && orderRes.data) {
|
|
||||||
uni.hideLoading()
|
|
||||||
const unpaidOrder = orderRes.data
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/order/payment?orderId=${unpaidOrder.orderId}`
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取设备信息并跳转详情
|
|
||||||
const deviceInfoRes = await getDeviceInfo(deviceNo)
|
|
||||||
uni.hideLoading()
|
|
||||||
|
|
||||||
if (deviceInfoRes.code === 200 && deviceInfoRes.data && deviceInfoRes.data.device) {
|
|
||||||
const deviceInfo = deviceInfoRes.data.device
|
|
||||||
let url = `/pages/device/detail?deviceNo=${deviceNo}`
|
|
||||||
if (deviceInfo.feeConfig) {
|
|
||||||
url += `&feeConfig=${encodeURIComponent(deviceInfo.feeConfig)}`
|
|
||||||
}
|
|
||||||
uni.navigateTo({
|
|
||||||
url
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/device/detail?deviceNo=${deviceNo}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('扫码处理失败:', error)
|
|
||||||
if (error && error.errMsg !== 'scanCode:fail cancel') {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('home.scanFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 去购买
|
|
||||||
const goToBuy = () => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/purchase/index'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('user.myCards')
|
|
||||||
})
|
|
||||||
getCardList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.my-card-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
padding: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-item {
|
|
||||||
// background-color: #ffffff;
|
|
||||||
border-radius: 25rpx;
|
|
||||||
padding: 32rpx;
|
|
||||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
|
||||||
position: relative;
|
|
||||||
z-index: 5;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卡片头部
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-name {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333333;
|
|
||||||
line-height: 50rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-date {
|
|
||||||
.date-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999999;
|
|
||||||
line-height: 34rpx;
|
|
||||||
|
|
||||||
&.expired {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 地区信息
|
|
||||||
.card-region {
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 16rpx;
|
|
||||||
|
|
||||||
.region-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #666666;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 卡片底部
|
|
||||||
.card-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20rpx;
|
|
||||||
padding-top: 50rpx;
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(255, 244, 227, 1);
|
|
||||||
z-index: 1;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
border-radius: 0 0 25rpx 25rpx;
|
|
||||||
|
|
||||||
/* text-align: 30rpx ; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-usage-info {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.usage-text {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #D4A574;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 36rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 续卡按钮
|
|
||||||
.renew-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4rpx;
|
|
||||||
padding: 8rpx 16rpx;
|
|
||||||
// background-color: #FFF9F0;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
|
|
||||||
.renew-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #D4A574;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #D4A574;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态标签
|
|
||||||
.status-tag {
|
|
||||||
padding: 8rpx 20rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
line-height: 34rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
// background-color: #FFF9F0;
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
color: #D4A574;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expired {
|
|
||||||
// background-color: #F5F5F5;
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.used {
|
|
||||||
// background-color: #F5F5F5;
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
color: #999999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 120rpx 0;
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
width: 200rpx;
|
|
||||||
height: 200rpx;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buy-btn {
|
|
||||||
padding: 20rpx 60rpx;
|
|
||||||
background-color: #B8741A;
|
|
||||||
border-radius: 48rpx;
|
|
||||||
|
|
||||||
.buy-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,542 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="my-coupon-page">
|
|
||||||
<!-- Tab 切换 -->
|
|
||||||
<!-- <view class="tab-container">
|
|
||||||
<view class="tab-item" :class="{ active: currentTab === 'available' }" @click="switchTab('available')">
|
|
||||||
<text class="tab-text">{{ $t('myCoupon.available') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="tab-item" :class="{ active: currentTab === 'used' }" @click="switchTab('used')">
|
|
||||||
<text class="tab-text">{{ $t('myCoupon.used') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="tab-item" :class="{ active: currentTab === 'expired' }" @click="switchTab('expired')">
|
|
||||||
<text class="tab-text">{{ $t('myCoupon.expired') }}</text>
|
|
||||||
</view>
|
|
||||||
</view> -->
|
|
||||||
|
|
||||||
<!-- 优惠券列表 -->
|
|
||||||
<view class="coupon-list" v-if="filteredCoupons.length > 0">
|
|
||||||
<view v-for="coupon in filteredCoupons" :key="coupon.id" class="coupon-item-wrapper">
|
|
||||||
<view class="coupon-item" :class="getCouponClass(coupon.status)">
|
|
||||||
|
|
||||||
<!-- 虚线上下圆形缺口 -->
|
|
||||||
<view class="coupon-circle-top"></view>
|
|
||||||
<view class="coupon-circle-bottom"></view>
|
|
||||||
|
|
||||||
<view class="coupon-left">
|
|
||||||
<view class="coupon-value">
|
|
||||||
<text v-if="coupon.type === 'cash'" class="coupon-unit">¥</text>
|
|
||||||
<text class="coupon-amount">{{ coupon.type === 'discount' ? coupon.discount : coupon.value }}</text>
|
|
||||||
<text v-if="coupon.type === 'discount'" class="coupon-unit">折</text>
|
|
||||||
</view>
|
|
||||||
<view style="display: flex;flex-direction: column;">
|
|
||||||
<text class="coupon-condition">{{ coupon.condition }}</text>
|
|
||||||
<text class="coupon-validity-left">{{ coupon.validity }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="coupon-divider"></view>
|
|
||||||
<view class="coupon-right">
|
|
||||||
<!-- <text class="coupon-name">{{ coupon.name }}</text> -->
|
|
||||||
<!-- <text class="coupon-region" v-if="coupon.positionName"
|
|
||||||
style="font-size: 22rpx; color: #999; margin-top: 4rpx;">
|
|
||||||
{{ $t('myCoupon.onlyForRegionBefore') }}{{ coupon.positionName }}{{ $t('myCoupon.onlyForRegionAfter') }}
|
|
||||||
</text> -->
|
|
||||||
<view class="use-btn" v-if="coupon.status == 'unused'" @click="useCoupon(coupon)">
|
|
||||||
<text class="use-text">{{ $t('myCoupon.useNow') }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="coupon-status" v-else>{{ getStatusText(coupon.status) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 空状态 -->
|
|
||||||
<view class="empty-state" v-else>
|
|
||||||
<image class="empty-icon" src="/static/empty-coupon.png" mode="aspectFit"></image>
|
|
||||||
<text class="empty-text">{{ getEmptyText() }}</text>
|
|
||||||
<view class="buy-btn" @click="goToBuy" v-if="currentTab === 'available'">
|
|
||||||
<text class="buy-text">{{ $t('myCoupon.buyNow') }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue'
|
|
||||||
import { useI18n } from '@/utils/i18n.js'
|
|
||||||
import { getUserCoupons } from '@/config/api/coupon.js'
|
|
||||||
import { getQueryString } from '@/util/index.js'
|
|
||||||
import { getDeviceInfo } from '@/config/api/device.js'
|
|
||||||
import { getInUseOrder, getUnpaidOrder } from '@/config/api/order.js'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
// 当前选中的 Tab
|
|
||||||
const currentTab = ref('available')
|
|
||||||
|
|
||||||
// // Tab 与 API 状态的映射
|
|
||||||
// const tabToStatusMap = {
|
|
||||||
// available: 'unused',
|
|
||||||
// used: 'used',
|
|
||||||
// expired: 'expired'
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 优惠券列表
|
|
||||||
const couponList = ref([])
|
|
||||||
|
|
||||||
// 过滤后的优惠券
|
|
||||||
const filteredCoupons = computed(() => {
|
|
||||||
return couponList.value;
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取优惠券列表
|
|
||||||
const getCouponList = async () => {
|
|
||||||
try {
|
|
||||||
// const apiStatus = tabToStatusMap[currentTab.value]
|
|
||||||
const res = await getUserCoupons('')
|
|
||||||
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
// 将后端数据转换为前端需要的格式
|
|
||||||
couponList.value = (res.data || []).map(item => {
|
|
||||||
// 判断优惠券类型:discount_coupon 折扣券,deduction_coupon 抵扣券
|
|
||||||
const isCashCoupon = item.couponType === 'deduction_coupon'
|
|
||||||
|
|
||||||
// 格式化使用条件
|
|
||||||
let condition = '无门槛'
|
|
||||||
if (item.usableCondition && item.usableCondition > 0) {
|
|
||||||
condition = `满${item.usableCondition}可用`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化有效期
|
|
||||||
let validity = ''
|
|
||||||
if (currentTab.value === 'used') {
|
|
||||||
// 已使用显示使用时间
|
|
||||||
validity = item.couponStartTime ? `使用时间 ${item.couponStartTime.split(' ')[0]}` : ''
|
|
||||||
} else if (item.couponEndTime) {
|
|
||||||
validity = `于 ${item.couponEndTime.split(' ')[0]} 过期`
|
|
||||||
}
|
|
||||||
console.log(item.status);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: item.id,
|
|
||||||
name: item.couponName || '优惠券',
|
|
||||||
type: isCashCoupon ? 'cash' : 'discount',
|
|
||||||
value: item.deductAmount ? parseFloat(item.deductAmount) : 0,
|
|
||||||
discount: item.discountRate ? parseFloat(item.discountRate) * 10 : null,
|
|
||||||
condition: condition,
|
|
||||||
validity: validity,
|
|
||||||
status: item.status,
|
|
||||||
positionName: item.positionName
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
couponList.value = []
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取优惠券列表失败:', error)
|
|
||||||
couponList.value = []
|
|
||||||
uni.showToast({
|
|
||||||
title: t('myCoupon.getListFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 切换 Tab
|
|
||||||
const switchTab = (tab) => {
|
|
||||||
currentTab.value = tab
|
|
||||||
getCouponList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取优惠券样式类名
|
|
||||||
const getCouponClass = (status) => {
|
|
||||||
return status === 'unused' ? '' : 'disabled'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取状态文本
|
|
||||||
const getStatusText = (status) => {
|
|
||||||
const statusMap = {
|
|
||||||
'used': t('myCoupon.usedStatus'),
|
|
||||||
'expired': t('myCoupon.expiredStatus'),
|
|
||||||
'refunded':t('myCoupon.refundedStatus')
|
|
||||||
}
|
|
||||||
console.log("获取状态文本:"+statusMap[status]);
|
|
||||||
return statusMap[status] || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取空状态文本
|
|
||||||
const getEmptyText = () => {
|
|
||||||
const textMap = {
|
|
||||||
'available': t('myCoupon.noAvailableCoupons'),
|
|
||||||
'used': t('myCoupon.noUsedCoupons'),
|
|
||||||
'expired': t('myCoupon.noExpiredCoupons')
|
|
||||||
}
|
|
||||||
return textMap[currentTab.value] || ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用优惠券
|
|
||||||
const useCoupon = async (coupon) => {
|
|
||||||
try {
|
|
||||||
const scanResult = await new Promise((resolve, reject) => {
|
|
||||||
uni.scanCode({
|
|
||||||
success: resolve,
|
|
||||||
fail: reject
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('扫码结果:', scanResult);
|
|
||||||
let deviceNo;
|
|
||||||
// 兼容不同平台的扫码结果
|
|
||||||
if (scanResult.scanType === 'QR_CODE' || scanResult.scanType === 'qrCode') {
|
|
||||||
deviceNo = getQueryString(scanResult.result, 'deviceNo')
|
|
||||||
} else if (scanResult.path) {
|
|
||||||
deviceNo = getQueryString(scanResult.path, 'deviceNo')
|
|
||||||
} else {
|
|
||||||
deviceNo = scanResult.result
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!deviceNo) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('home.invalidQRCode'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showLoading({
|
|
||||||
title: t('common.getting')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 检查是否有使用中的订单
|
|
||||||
const inUseRes = await getInUseOrder()
|
|
||||||
if (inUseRes && inUseRes.code === 200 && inUseRes.data) {
|
|
||||||
uni.hideLoading()
|
|
||||||
const inUseOrder = inUseRes.data
|
|
||||||
uni.reLaunch({
|
|
||||||
url: `/pages/order/detail?orderId=${inUseOrder.orderId}&deviceId=${deviceNo || inUseOrder.deviceNo}`
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有待支付订单
|
|
||||||
const orderRes = await getUnpaidOrder()
|
|
||||||
if (orderRes && orderRes.code === 200 && orderRes.data) {
|
|
||||||
uni.hideLoading()
|
|
||||||
const unpaidOrder = orderRes.data
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/order/payment?orderId=${unpaidOrder.orderId}`
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取设备信息并跳转详情
|
|
||||||
const deviceInfoRes = await getDeviceInfo(deviceNo)
|
|
||||||
uni.hideLoading()
|
|
||||||
|
|
||||||
if (deviceInfoRes.code === 200 && deviceInfoRes.data && deviceInfoRes.data.device) {
|
|
||||||
const deviceInfo = deviceInfoRes.data.device
|
|
||||||
let url = `/pages/device/detail?deviceNo=${deviceNo}`
|
|
||||||
if (deviceInfo.feeConfig) {
|
|
||||||
url += `&feeConfig=${encodeURIComponent(deviceInfo.feeConfig)}`
|
|
||||||
}
|
|
||||||
uni.navigateTo({
|
|
||||||
url
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/device/detail?deviceNo=${deviceNo}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('扫码处理失败:', error)
|
|
||||||
if (error && error.errMsg !== 'scanCode:fail cancel') {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('home.scanFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 去购买
|
|
||||||
const goToBuy = () => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/purchase/index?tab=coupon'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('user.myCoupons')
|
|
||||||
})
|
|
||||||
getCouponList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
// 优惠券样式变量封装
|
|
||||||
$coupon-theme-color: #A16300;
|
|
||||||
$coupon-divider-color: #B8741A;
|
|
||||||
$coupon-bg-faded: #f5f5f5;
|
|
||||||
$coupon-active-bg-start: #FFF4E6;
|
|
||||||
$coupon-active-bg-end: #FFE8CC;
|
|
||||||
$coupon-divider-left: 65%;
|
|
||||||
$coupon-circle-radius: 16rpx;
|
|
||||||
|
|
||||||
.my-coupon-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tab 切换 */
|
|
||||||
.tab-container {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
display: flex;
|
|
||||||
background-color: #ffffff;
|
|
||||||
z-index: 999;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-item {
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.tab-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
.tab-text {
|
|
||||||
color: #333;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: 60rpx;
|
|
||||||
height: 6rpx;
|
|
||||||
background-color: #FFA928;
|
|
||||||
border-radius: 3rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-list {
|
|
||||||
padding: 20rpx;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-item-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-item {
|
|
||||||
background: #FFF4E3;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 40rpx 30rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
position: relative;
|
|
||||||
overflow: visible;
|
|
||||||
min-height: 180rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 2rpx solid transparent;
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
border-color: $coupon-divider-color;
|
|
||||||
box-shadow: 0 4rpx 20rpx rgba(184, 116, 26, 0.2);
|
|
||||||
|
|
||||||
.coupon-circle-top,
|
|
||||||
.coupon-circle-bottom {
|
|
||||||
background-color: $coupon-active-bg-start;
|
|
||||||
border: 2rpx solid $coupon-divider-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
|
|
||||||
opacity: 0.6;
|
|
||||||
|
|
||||||
.coupon-value,
|
|
||||||
.coupon-condition,
|
|
||||||
.coupon-name {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-circle-top,
|
|
||||||
.coupon-circle-bottom {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 虚线顶部圆形缺口 */
|
|
||||||
.coupon-circle-top {
|
|
||||||
position: absolute;
|
|
||||||
left: $coupon-divider-left+4%;
|
|
||||||
top: -$coupon-circle-radius;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: $coupon-circle-radius * 2;
|
|
||||||
height: $coupon-circle-radius * 2;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: $coupon-bg-faded;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 虚线底部圆形缺口 */
|
|
||||||
.coupon-circle-bottom {
|
|
||||||
position: absolute;
|
|
||||||
left: $coupon-divider-left+4%;
|
|
||||||
bottom: -$coupon-circle-radius;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
width: $coupon-circle-radius * 2;
|
|
||||||
height: $coupon-circle-radius * 2;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: $coupon-bg-faded;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-left {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-value {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end; // 单位在脚
|
|
||||||
color: $coupon-theme-color;
|
|
||||||
line-height: 1;
|
|
||||||
width:120rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-amount {
|
|
||||||
font-size: 56rpx; // 值要大
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-unit {
|
|
||||||
font-size: 24rpx; // 单位小
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 6rpx; // 微调单位垂直位置,使其更贴合“脚”
|
|
||||||
margin-left: 4rpx;
|
|
||||||
margin-right: 4rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-condition {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #000;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-validity-left {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-top: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-divider {
|
|
||||||
width: 2rpx;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: $coupon-divider-left;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
align-self: stretch;
|
|
||||||
background: repeating-linear-gradient(to bottom,
|
|
||||||
$coupon-divider-color 0rpx,
|
|
||||||
$coupon-divider-color 8rpx,
|
|
||||||
transparent 8rpx,
|
|
||||||
transparent 16rpx);
|
|
||||||
margin: 0 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-right {
|
|
||||||
// flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8rpx;
|
|
||||||
align-items: center;
|
|
||||||
margin: auto 0;
|
|
||||||
width: 160rpx;
|
|
||||||
// align-content: center;
|
|
||||||
// transform: translateY(50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-name {
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-validity {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.use-btn {
|
|
||||||
// margin-top: 10rpx;
|
|
||||||
// padding: 12rpx 28rpx;
|
|
||||||
// background-color: #B8741A;
|
|
||||||
// border-radius: 40rpx;
|
|
||||||
|
|
||||||
.use-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #A16300;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.coupon-status {
|
|
||||||
margin-top: 10rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 120rpx 0;
|
|
||||||
|
|
||||||
.empty-icon {
|
|
||||||
width: 200rpx;
|
|
||||||
height: 200rpx;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #999;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buy-btn {
|
|
||||||
padding: 20rpx 60rpx;
|
|
||||||
background-color: #B8741A;
|
|
||||||
border-radius: 48rpx;
|
|
||||||
|
|
||||||
.buy-text {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,985 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="payment-container">
|
|
||||||
<!-- 地点信息卡片 -->
|
|
||||||
<view class="location-card">
|
|
||||||
<view class="location-header">
|
|
||||||
<!-- <view class="location-icon">📍</view> -->
|
|
||||||
<image src="@/static/device_location.png" mode="aspectFit" class="location-icon"></image>
|
|
||||||
<text class="location-name">{{ locationName }}</text>
|
|
||||||
<view class="status-badge">{{ $t('device.available') }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="device-info">
|
|
||||||
<text class="device-label">{{ $t('order.deviceNo') }}:</text>
|
|
||||||
<text class="device-value">{{ orderInfo.deviceNo || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 订单信息和费用信息 -->
|
|
||||||
<view class="order-card">
|
|
||||||
<view class="card-header">
|
|
||||||
<view class="card-title-bar"></view>
|
|
||||||
<view class="card-title">{{ $t('payment.orderInfo') }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('order.orderNo') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('order.deviceNo') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('payment.createTime') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.createTime || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 费用信息部分 -->
|
|
||||||
<view class="card-header" style="margin-top: 32rpx;">
|
|
||||||
<view class="card-title-bar"></view>
|
|
||||||
<view class="card-title">{{ $t('payment.feeInfo') }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="price-item">
|
|
||||||
<text class="label">{{ $t('payment.deposit') }}</text>
|
|
||||||
<text class="value">{{ currencySymbol }} {{ orderInfo.deposit || '90' }}</text>
|
|
||||||
</view>
|
|
||||||
<!-- <view class="price-item">
|
|
||||||
<text class="label">{{ $t('payment.package') }}</text>
|
|
||||||
<text class="value">{{ packageText }}</text>
|
|
||||||
</view> -->
|
|
||||||
<view class="price-item total">
|
|
||||||
<text class="label">{{ $t('payment.total') }}</text>
|
|
||||||
<view class="total-value">
|
|
||||||
<text class="currency">{{ currencySymbol }}</text>
|
|
||||||
<text class="amount">{{ totalAmount }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 支付方式选择 -->
|
|
||||||
<view class="order-card" v-if="paymentMethods.length">
|
|
||||||
<view class="card-header">
|
|
||||||
<view class="card-title-bar"></view>
|
|
||||||
<view class="card-title">{{ $t('payment.paymentMethod') }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="payment-method-item" v-for="(item, idx) in paymentMethods" :key="`${item.paymentMethodType}-${idx}`"
|
|
||||||
@click="selectPaymentMethod(item.paymentMethodType)">
|
|
||||||
<view class="method-left">
|
|
||||||
<text class="method-name">{{ item.paymentMethodName }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="method-right">
|
|
||||||
<view class="method-radio" :class="{ active: selectedPaymentMethod === item.paymentMethodType }">
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
|
||||||
<view class="bottom-bar">
|
|
||||||
<view class="pay-btn" @click="handlePayment">
|
|
||||||
<text class="currency-small">{{ currencySymbol }}</text>
|
|
||||||
<text class="amount-large">{{ totalAmount }}</text>
|
|
||||||
<text class="pay-text">{{ $t('payment.payNow') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="cancel-btn" @click="handleCancelOrder">
|
|
||||||
{{ $t('order.cancelOrder') }}
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
computed,
|
|
||||||
reactive,
|
|
||||||
onMounted
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
onLoad
|
|
||||||
} from '@dcloudio/uni-app'
|
|
||||||
import {
|
|
||||||
queryById,
|
|
||||||
cancelOrder,
|
|
||||||
createWxPayment,
|
|
||||||
getWxPaymentStatus,
|
|
||||||
createAliPayment,
|
|
||||||
getAliPaymentStatus,
|
|
||||||
createAntomPayment,
|
|
||||||
getAntomPaymentMethods,
|
|
||||||
getAntomPaymentStatus
|
|
||||||
} from '@/config/api/order.js'
|
|
||||||
import {
|
|
||||||
getDeviceInfo
|
|
||||||
} from '@/config/api/device.js'
|
|
||||||
import {
|
|
||||||
updateUserBalance
|
|
||||||
} from '@/config/api/user.js'
|
|
||||||
import {
|
|
||||||
useI18n,
|
|
||||||
showModalI18n
|
|
||||||
} from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
const {
|
|
||||||
t
|
|
||||||
} = useI18n()
|
|
||||||
|
|
||||||
const orderId = ref(null)
|
|
||||||
const deviceNo = ref(null)
|
|
||||||
const orderInfo = ref({})
|
|
||||||
const deviceInfo = ref(null)
|
|
||||||
const passedTotalAmount = ref(null)
|
|
||||||
const passedDepositAmount = ref(null)
|
|
||||||
const currencyCode = ref('USD')
|
|
||||||
|
|
||||||
// 支付方式相关(微信/支付宝/H5-Antom 多平台)
|
|
||||||
const paymentMethods = ref([])
|
|
||||||
const selectedPaymentMethod = ref('WECHAT') // 默认微信
|
|
||||||
|
|
||||||
// 地点名称(可以从设备信息中获取,这里先用默认值)
|
|
||||||
const locationName = ref('澎创办公室')
|
|
||||||
|
|
||||||
// 套餐文本(可以从设备信息中获取,这里先用默认值)
|
|
||||||
const packageText = computed(() => {
|
|
||||||
// 这里可以根据实际的设备信息动态生成
|
|
||||||
return '2元/小时'
|
|
||||||
})
|
|
||||||
|
|
||||||
const orderStatus = reactive({
|
|
||||||
get text() {
|
|
||||||
return t('payment.waitingForPayment')
|
|
||||||
},
|
|
||||||
get desc() {
|
|
||||||
return t('payment.pleasePayIn15Min')
|
|
||||||
},
|
|
||||||
class: 'waiting'
|
|
||||||
})
|
|
||||||
|
|
||||||
const totalAmount = computed(() => {
|
|
||||||
if (currencyCode.value === 'IDR') {
|
|
||||||
const raw =
|
|
||||||
passedTotalAmount.value != null && passedTotalAmount.value !== ''
|
|
||||||
? passedTotalAmount.value
|
|
||||||
: orderInfo.value.deposit
|
|
||||||
const num = Number(String(raw ?? '').replace(/[^\d.-]/g, ''))
|
|
||||||
return Number.isFinite(num) ? String(Math.trunc(num)) : String(raw || '0')
|
|
||||||
}
|
|
||||||
if (passedTotalAmount.value !== null) {
|
|
||||||
return parseFloat(passedTotalAmount.value).toFixed(2);
|
|
||||||
}
|
|
||||||
const deposit = parseFloat(orderInfo.value.deposit || passedDepositAmount.value || 99)
|
|
||||||
return deposit.toFixed(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
const currencySymbol = computed(() => {
|
|
||||||
if (currencyCode.value === 'IDR') return 'Rp'
|
|
||||||
return 'USD'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载订单信息
|
|
||||||
const loadOrderInfo = async () => {
|
|
||||||
// 检查 orderId 是否存在,如果不存在则尝试从缓存获取
|
|
||||||
if (!orderId.value) {
|
|
||||||
// #ifdef H5
|
|
||||||
const cachedOrderId = uni.getStorageSync('pendingPaymentNo');
|
|
||||||
if (cachedOrderId) {
|
|
||||||
orderId.value = cachedOrderId;
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果两种方式都获取不到 orderId,提示并跳转
|
|
||||||
if (!orderId.value) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.orderNotExist'),
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.switchTab({
|
|
||||||
url: '/pages/index/index'
|
|
||||||
});
|
|
||||||
}, 1500);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: t('common.loading')
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await queryById(orderId.value)
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
const orderData = res.data
|
|
||||||
|
|
||||||
// 处理创建时间
|
|
||||||
let formattedTime;
|
|
||||||
try {
|
|
||||||
if (orderData.createTime) {
|
|
||||||
formattedTime = formatTime(new Date(orderData.createTime));
|
|
||||||
} else {
|
|
||||||
formattedTime = formatTime(new Date());
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('时间格式化错误:', e);
|
|
||||||
formattedTime = formatTime(new Date());
|
|
||||||
}
|
|
||||||
|
|
||||||
orderInfo.value = {
|
|
||||||
orderNo: orderData.orderNo || orderData.orderId,
|
|
||||||
deviceNo: orderData.deviceNo,
|
|
||||||
createTime: formattedTime,
|
|
||||||
deposit: passedDepositAmount.value || orderData.depositAmount || '99.00',
|
|
||||||
orderStatus: orderData.orderStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceNo.value = orderData.deviceNo;
|
|
||||||
await loadDeviceInfo();
|
|
||||||
await loadPaymentMethods();
|
|
||||||
// 如果订单状态是等待支付,启动相应的支付状态轮询
|
|
||||||
if (orderInfo.value.orderStatus == 'waiting_for_payment') {
|
|
||||||
// 使用当前选中的支付方式类型进行轮询
|
|
||||||
startPaymentStatusPolling();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(t('order.getOrderFailed'))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取订单信息失败:', error)
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || t('order.getOrderFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
uni.hideLoading()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载设备信息
|
|
||||||
const loadDeviceInfo = async () => {
|
|
||||||
if (!deviceNo.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await getDeviceInfo(deviceNo.value);
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
deviceInfo.value = res.data.device;
|
|
||||||
currencyCode.value = (res.data?.position?.currency || currencyCode.value || 'USD').toUpperCase()
|
|
||||||
|
|
||||||
if (deviceInfo.value && deviceInfo.value.depositAmount) {
|
|
||||||
orderInfo.value.deposit = deviceInfo.value.depositAmount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取设备信息失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取操作系统类型
|
|
||||||
const getOsType = () => {
|
|
||||||
const systemInfo = uni.getSystemInfoSync();
|
|
||||||
console.log(uni.getSystemInfoSync());
|
|
||||||
|
|
||||||
const platform = systemInfo.platform;
|
|
||||||
console.log('当前系统类型:', uni.getSystemInfoSync().platform);
|
|
||||||
|
|
||||||
// 根据平台返回对应的 osType
|
|
||||||
if (platform === 'android') {
|
|
||||||
return 'ANDROID';
|
|
||||||
} else if (platform === 'ios') {
|
|
||||||
return 'IOS';
|
|
||||||
} else {
|
|
||||||
// 默认返回 ANDROID
|
|
||||||
return 'ANDROID';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载支付方式列表(根据平台决定可选项)
|
|
||||||
const loadPaymentMethods = async () => {
|
|
||||||
const methods = []
|
|
||||||
|
|
||||||
// 小程序环境下:微信 / 支付宝
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
methods.push({
|
|
||||||
paymentMethodType: 'WECHAT',
|
|
||||||
paymentMethodName: '微信支付'
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
methods.push({
|
|
||||||
paymentMethodType: 'ALIPAY',
|
|
||||||
paymentMethodName: '支付宝支付'
|
|
||||||
})
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// H5 环境:使用 Antom 聚合支付(多通道)
|
|
||||||
// #ifdef H5
|
|
||||||
if (orderInfo.value.orderNo) {
|
|
||||||
try {
|
|
||||||
const osType = getOsType();
|
|
||||||
const res = await getAntomPaymentMethods(orderInfo.value.orderNo, osType);
|
|
||||||
console.log(res.data);
|
|
||||||
console.log(res.data.paymentOptions,'支付方式');
|
|
||||||
// if (res.code === 200 && res.data && res.data.paymentOptions) {
|
|
||||||
// res.data.paymentOptions.forEach(item => {
|
|
||||||
// methods.push({
|
|
||||||
// paymentMethodType: item.paymentMethodType,
|
|
||||||
// paymentMethodName: item.paymentMethodName || item.paymentMethodType
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// 每条选项的 paymentMethodType 必须唯一下发到 Antom 的 paymentType 参数,否则 v-for 的 key 与单选态会异常
|
|
||||||
methods.push({
|
|
||||||
paymentMethodType: 'ALIPAY_DANA',
|
|
||||||
paymentMethodName: t('payment.ALIPAYDANA')
|
|
||||||
})
|
|
||||||
// methods.push({
|
|
||||||
// paymentMethodType: 'ALIPAY_HK',
|
|
||||||
// paymentMethodName: t('payment.alipayHk')
|
|
||||||
// })
|
|
||||||
// methods.push({
|
|
||||||
// paymentMethodType: 'ALIPAY_ID',
|
|
||||||
// paymentMethodName: t('payment.alipayId')
|
|
||||||
// })
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取 Antom 支付方式失败:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 兜底:至少保留一个微信
|
|
||||||
if (!methods.length) {
|
|
||||||
methods.push({
|
|
||||||
paymentMethodType: 'WECHAT',
|
|
||||||
paymentMethodName: '微信支付'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentMethods.value = methods
|
|
||||||
if (paymentMethods.value.length > 0) {
|
|
||||||
selectedPaymentMethod.value = paymentMethods.value[0].paymentMethodType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 选择支付方式
|
|
||||||
const selectPaymentMethod = (methodType) => {
|
|
||||||
selectedPaymentMethod.value = methodType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取支付方式图标
|
|
||||||
const getPaymentIcon = (methodType) => {
|
|
||||||
const iconMap = {
|
|
||||||
'ALIPAY': 'alipay',
|
|
||||||
'WECHATPAY': 'wechat',
|
|
||||||
'WECHAT': 'wechat'
|
|
||||||
};
|
|
||||||
return iconMap[methodType] || 'default';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理支付
|
|
||||||
const handlePayment = async () => {
|
|
||||||
if (!selectedPaymentMethod.value) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '请选择支付方式',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: t('common.processing')
|
|
||||||
})
|
|
||||||
|
|
||||||
const method = selectedPaymentMethod.value
|
|
||||||
|
|
||||||
// 微信小程序支付押金
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
if (method === 'WECHAT') {
|
|
||||||
const wxRes = await createWxPayment(orderInfo.value.orderNo)
|
|
||||||
if (wxRes.code === 200 && wxRes.data) {
|
|
||||||
const payData = wxRes.data
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
wx.requestPayment({
|
|
||||||
...payData,
|
|
||||||
success: resolve,
|
|
||||||
fail: reject
|
|
||||||
})
|
|
||||||
})
|
|
||||||
// 支付成功后轮询微信支付状态
|
|
||||||
startPaymentStatusPolling('WECHAT')
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
throw new Error(wxRes.msg || t('payment.createPayOrderFailed'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 支付宝小程序支付押金
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
if (method === 'ALIPAY') {
|
|
||||||
const aliRes = await createAliPayment(orderInfo.value.orderNo)
|
|
||||||
if (aliRes.code === 200 && aliRes.data) {
|
|
||||||
// 后端当前实际返回结构示例:
|
|
||||||
// { code:200, msg:'操作成功', data:{ tradeNo:'xxx', outTradeNo:'yyy' } }
|
|
||||||
const tradeNO = aliRes.data.tradeNo || aliRes.data.outTradeNo
|
|
||||||
const payForm = aliRes.data.payForm || aliRes.data.orderStr
|
|
||||||
|
|
||||||
if (!tradeNO && !payForm) {
|
|
||||||
throw new Error('未获取到支付宝支付参数')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先使用 tradeNO 方式,其次兼容老的 orderStr 方式
|
|
||||||
if (tradeNO) {
|
|
||||||
my.tradePay({
|
|
||||||
tradeNO,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.resultCode === '9000') {
|
|
||||||
startPaymentStatusPolling('ALIPAY')
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('payment.paymentFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('payment.paymentFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
my.tradePay({
|
|
||||||
orderStr: payForm,
|
|
||||||
success: (res) => {
|
|
||||||
if (res.resultCode === '9000') {
|
|
||||||
startPaymentStatusPolling('ALIPAY')
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('payment.paymentFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: () => {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('payment.paymentFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
throw new Error(aliRes.msg || t('payment.createPayOrderFailed'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// H5 + Antom 聚合支付
|
|
||||||
// #ifdef H5
|
|
||||||
const osType = getOsType();
|
|
||||||
const res = await createAntomPayment(orderInfo.value.orderNo, method, osType)
|
|
||||||
|
|
||||||
if (res && res.code === 200 && res.data) {
|
|
||||||
const paymentUrl = res.data.h5Url;
|
|
||||||
|
|
||||||
if (!paymentUrl) {
|
|
||||||
throw new Error('未获取到支付链接');
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.setStorageSync('pendingPaymentNo', orderId.value);
|
|
||||||
window.location.href = paymentUrl;
|
|
||||||
// plus.runtime.openURL(paymentUrl, function(err) {
|
|
||||||
// console.log('打开失败');
|
|
||||||
// window.location.href = paymentUrl;
|
|
||||||
// })
|
|
||||||
// ==========================================================
|
|
||||||
|
|
||||||
// 开始轮询支付状态(传入当前选中的支付方式类型)
|
|
||||||
startPaymentStatusPolling(method);
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
throw new Error(res?.msg || t('payment.createPayOrderFailed'))
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
} catch (error) {
|
|
||||||
console.error('支付失败:', error)
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || t('payment.paymentFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
uni.hideLoading()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCancelOrder = () => {
|
|
||||||
if (!orderId.value) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.orderNotExist'),
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showModalI18n({
|
|
||||||
title: t('order.confirmCancel'),
|
|
||||||
content: t('order.confirmCancelContent'),
|
|
||||||
success: async (res) => {
|
|
||||||
if (!res.confirm) return;
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: t('common.processing')
|
|
||||||
});
|
|
||||||
const result = await cancelOrder({
|
|
||||||
orderId: orderId.value
|
|
||||||
});
|
|
||||||
if (result && result.code === 200) {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: t('order.cancelSuccess'),
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/index/index'
|
|
||||||
});
|
|
||||||
}, 800);
|
|
||||||
} else {
|
|
||||||
throw new Error(result?.msg || t('order.cancelFailed'));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading();
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || t('order.cancelFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 轮询定时器
|
|
||||||
let pollingTimer = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 统一的支付状态轮询方法
|
|
||||||
* @param {string} paymentMethodType - 支付方式类型,如 'WECHAT' | 'ALIPAY' | 'WECHATPAY' 等,与 selectedPaymentMethod 保持一致
|
|
||||||
*/
|
|
||||||
const startPaymentStatusPolling = (paymentMethodType = null) => {
|
|
||||||
// 清除之前的定时器
|
|
||||||
if (pollingTimer) {
|
|
||||||
clearInterval(pollingTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有传入支付方式类型,使用当前选中的支付方式
|
|
||||||
const methodType = paymentMethodType || selectedPaymentMethod.value;
|
|
||||||
|
|
||||||
let pollCount = 0;
|
|
||||||
const maxPollCount = 60; // 最多轮询60次(5分钟)
|
|
||||||
|
|
||||||
pollingTimer = setInterval(async () => {
|
|
||||||
pollCount++;
|
|
||||||
|
|
||||||
if (pollCount > maxPollCount) {
|
|
||||||
clearInterval(pollingTimer);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付超时,请重新支付',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let res;
|
|
||||||
let status, successStatus, failStatuses;
|
|
||||||
|
|
||||||
// #ifdef H5
|
|
||||||
// H5 环境统一使用 Antom 聚合支付 API
|
|
||||||
const osType = getOsType();
|
|
||||||
res = await getAntomPaymentStatus(orderInfo.value.orderNo, osType);
|
|
||||||
if (res && res.code === 200 && res.data) {
|
|
||||||
status = res.data.paymentStatus;
|
|
||||||
successStatus = 'SUCCESS';
|
|
||||||
failStatuses = ['FAIL', 'CANCELLED'];
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
|
||||||
// 微信小程序:根据支付方式类型判断
|
|
||||||
if (methodType === 'WECHAT' || methodType === 'WECHATPAY') {
|
|
||||||
res = await getWxPaymentStatus(orderInfo.value.orderNo);
|
|
||||||
if (res && res.code === 200 && res.data) {
|
|
||||||
status = res.data.tradeStatus;
|
|
||||||
successStatus = 'SUCCESS';
|
|
||||||
failStatuses = ['FAIL', 'CANCELLED'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// #ifdef MP-ALIPAY
|
|
||||||
// 支付宝小程序
|
|
||||||
if (methodType === 'ALIPAY') {
|
|
||||||
res = await getAliPaymentStatus(orderInfo.value.orderNo);
|
|
||||||
if (res && res.code === 200 && res.data) {
|
|
||||||
status = res.data.tradeStatus;
|
|
||||||
successStatus = 'TRADE_SUCCESS';
|
|
||||||
failStatuses = ['TRADE_FAIL', 'TRADE_CANCELLED'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
|
|
||||||
// 处理支付状态结果
|
|
||||||
if (res && res.code === 200 && res.data && status) {
|
|
||||||
console.log(status === successStatus);
|
|
||||||
|
|
||||||
// 支付成功
|
|
||||||
if (status === successStatus) {
|
|
||||||
clearInterval(pollingTimer);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await updateUserBalance(orderId.value);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('更新用户余额失败:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.redirectTo({
|
|
||||||
url: `/pages/order/success?orderId=${orderId.value}&deviceId=${orderInfo.value.deviceNo}`
|
|
||||||
});
|
|
||||||
}, 1500);
|
|
||||||
}
|
|
||||||
// 支付失败
|
|
||||||
else if (failStatuses && failStatuses.includes(status)) {
|
|
||||||
clearInterval(pollingTimer);
|
|
||||||
uni.showToast({
|
|
||||||
title: '支付失败,请重新支付',
|
|
||||||
icon: 'none'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const errorMsg = methodType === 'ALIPAY' ? '支付宝' : (methodType === 'WECHAT' ||
|
|
||||||
methodType === 'WECHATPAY') ? '微信' : '支付';
|
|
||||||
console.error(`查询${errorMsg}支付状态失败:`, error);
|
|
||||||
}
|
|
||||||
}, 10000); // 每10秒查询一次
|
|
||||||
}
|
|
||||||
|
|
||||||
// 兼容性方法:保持原有函数名,内部调用统一方法
|
|
||||||
const startWxPaymentStatusPolling = () => startPaymentStatusPolling('WECHAT');
|
|
||||||
const startAliPaymentStatusPolling = () => startPaymentStatusPolling('ALIPAY');
|
|
||||||
|
|
||||||
// 页面卸载时清除定时器
|
|
||||||
onMounted(() => {
|
|
||||||
return () => {
|
|
||||||
if (pollingTimer) {
|
|
||||||
clearInterval(pollingTimer);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// 格式化时间
|
|
||||||
const formatTime = (date) => {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
||||||
const day = date.getDate().toString().padStart(2, '0')
|
|
||||||
const hour = date.getHours().toString().padStart(2, '0')
|
|
||||||
const minute = date.getMinutes().toString().padStart(2, '0')
|
|
||||||
|
|
||||||
return `${year}-${month}-${day} ${hour}:${minute}`
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoad((options) => {
|
|
||||||
// 设置导航栏标题为待支付
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('payment.waitingForPayment')
|
|
||||||
})
|
|
||||||
|
|
||||||
// 优先从 options 中获取 orderId
|
|
||||||
if (options && options.orderId) {
|
|
||||||
orderId.value = options.orderId
|
|
||||||
|
|
||||||
if (options.totalAmount) {
|
|
||||||
passedTotalAmount.value = options.totalAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.depositAmount) {
|
|
||||||
passedDepositAmount.value = options.depositAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.feeConfig) {
|
|
||||||
try {
|
|
||||||
const feeConfigStr = decodeURIComponent(options.feeConfig)
|
|
||||||
deviceInfo.value = {
|
|
||||||
feeConfig: feeConfigStr
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('解析URL中的feeConfig失败:', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadOrderInfo()
|
|
||||||
}
|
|
||||||
// #ifdef H5
|
|
||||||
// 如果 options 中没有 orderId,尝试从缓存中获取(H5 环境)
|
|
||||||
else {
|
|
||||||
const cachedOrderId = uni.getStorageSync('pendingPaymentNo');
|
|
||||||
console.log("订单编号:" + cachedOrderId);
|
|
||||||
|
|
||||||
if (cachedOrderId) {
|
|
||||||
orderId.value = cachedOrderId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
loadOrderInfo()
|
|
||||||
// 调用 loadOrderInfo,内部会再次检查 orderId 是否存在
|
|
||||||
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.payment-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #F5F5F5;
|
|
||||||
padding: 24rpx;
|
|
||||||
padding-bottom: 200rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.location-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
padding: 32rpx;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
|
|
||||||
.location-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
|
|
||||||
.location-icon {
|
|
||||||
font-size: 40rpx;
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.location-name {
|
|
||||||
font-size: 36rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
background: #D4F4DD;
|
|
||||||
color: #52C41A;
|
|
||||||
font-size: 24rpx;
|
|
||||||
padding: 8rpx 20rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-info {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666;
|
|
||||||
|
|
||||||
.device-label {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-value {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 24rpx;
|
|
||||||
padding: 32rpx;
|
|
||||||
margin-bottom: 24rpx;
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 32rpx;
|
|
||||||
|
|
||||||
.card-title-bar {
|
|
||||||
width: 8rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
background: #52C41A;
|
|
||||||
border-radius: 4rpx;
|
|
||||||
margin-right: 12rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item,
|
|
||||||
.price-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 24rpx 0;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.price-item.total {
|
|
||||||
padding-top: 32rpx;
|
|
||||||
justify-content: flex-end !important;
|
|
||||||
// border-top: 1rpx solid #F0F0F0;
|
|
||||||
margin-top: 16rpx;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666;
|
|
||||||
margin-right: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.total-value {
|
|
||||||
display: flex;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
.currency {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #52C41A;
|
|
||||||
margin-right: 4rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #52C41A;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.payment-method-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 24rpx 0;
|
|
||||||
border-bottom: 1rpx solid #f5f5f5;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-name {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-left: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-radio {
|
|
||||||
width: 32rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid #d9d9d9;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.method-radio.active {
|
|
||||||
border-color: #52c41a;
|
|
||||||
background-color: #52c41a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-bar {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: #fff;
|
|
||||||
padding: 24rpx;
|
|
||||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
|
||||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
|
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
.pay-btn {
|
|
||||||
width: 100%;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
// height: 96rpx;
|
|
||||||
background: linear-gradient(135deg, #52C41A 0%, #73D13D 100%);
|
|
||||||
border-radius: 48rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 8rpx 24rpx rgba(82, 196, 26, 0.3);
|
|
||||||
|
|
||||||
.currency-small {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-right: 4rpx;
|
|
||||||
// text-align: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount-large {
|
|
||||||
font-size: 52rpx;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-right: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pay-text {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.9;
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
width: 100%;
|
|
||||||
//height: 84rpx;
|
|
||||||
margin-top: 16rpx;
|
|
||||||
//border-radius: 42rpx;
|
|
||||||
//border: 2rpx solid #d9d9d9;
|
|
||||||
//background: #fff;
|
|
||||||
color: #666;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="return-map-page">
|
|
||||||
<view class="image-wrapper">
|
|
||||||
<movable-area class="movable-area">
|
|
||||||
<movable-view class="movable-view" direction="all" :scale="true" :scale-min="1" :scale-max="4"
|
|
||||||
:scale-value="scale" @scale="onScaleChange">
|
|
||||||
<image :src="imageUrl" mode="widthFix" class="map-image" lazy-load="true"></image>
|
|
||||||
</movable-view>
|
|
||||||
</movable-area>
|
|
||||||
</view>
|
|
||||||
<view class="bottom-bar">
|
|
||||||
<view class="btn" @click="resetScale">{{ $t('common.reset') }}</view>
|
|
||||||
<view class="btn primary" @click="previewImage">{{ $t('common.preview') }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
ref
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
onLoad
|
|
||||||
} from '@dcloudio/uni-app'
|
|
||||||
import {
|
|
||||||
useI18n
|
|
||||||
} from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
const {
|
|
||||||
t
|
|
||||||
} = useI18n()
|
|
||||||
|
|
||||||
const imageUrl = ref('')
|
|
||||||
const scale = ref(1)
|
|
||||||
|
|
||||||
onLoad((options) => {
|
|
||||||
if (options && options.imageUrl) {
|
|
||||||
try {
|
|
||||||
imageUrl.value = decodeURIComponent(options.imageUrl)
|
|
||||||
} catch (e) {
|
|
||||||
imageUrl.value = options.imageUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!imageUrl.value) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('common.loadFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
uni.navigateBack()
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('order.returnLocationMap')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const onScaleChange = (e) => {
|
|
||||||
if (e && typeof e.detail?.scale === 'number') {
|
|
||||||
scale.value = e.detail.scale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetScale = () => {
|
|
||||||
scale.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
const previewImage = () => {
|
|
||||||
if (!imageUrl.value) return
|
|
||||||
uni.previewImage({
|
|
||||||
urls: [imageUrl.value]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.return-map-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
background-color: #000;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 20rpx;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movable-area {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.movable-view {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.map-image {
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-bar {
|
|
||||||
padding: 16rpx 30rpx 24rpx;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 24rpx;
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
min-width: 160rpx;
|
|
||||||
height: 72rpx;
|
|
||||||
border-radius: 36rpx;
|
|
||||||
border: 2rpx solid #ffffff;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
background: #07c160;
|
|
||||||
border-color: #07c160;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,399 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="success-container">
|
|
||||||
<!-- 支付成功状态和订单信息 -->
|
|
||||||
<view class="status-order-card">
|
|
||||||
<!-- 支付成功状态 -->
|
|
||||||
<view class="status-section">
|
|
||||||
<view class="status-icon success"></view>
|
|
||||||
<view class="status-text">{{ $t('success.paymentSuccess') }}</view>
|
|
||||||
<view class="status-desc">{{ $t('success.paymentSuccessDesc') }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 分割线 -->
|
|
||||||
<view class="section-divider"></view>
|
|
||||||
|
|
||||||
<!-- 订单信息 -->
|
|
||||||
<view class="order-section">
|
|
||||||
<view class="card-title">{{ $t('success.orderInfo') }}</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('order.orderNo') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.orderNo || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('order.deviceNo') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.deviceNo || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('success.paymentAmount') }}</text>
|
|
||||||
<text class="value">¥{{ orderInfo.amount || '0.00' }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-item">
|
|
||||||
<text class="label">{{ $t('success.paymentTime') }}</text>
|
|
||||||
<text class="value">{{ orderInfo.payTime || '-' }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 设备状态 -->
|
|
||||||
<view class="device-status">
|
|
||||||
<view class="status-message">{{ deviceMessage }}</view>
|
|
||||||
<view class="loading-animation" v-if="isLoading">
|
|
||||||
<view class="loading-circle"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<view class="button-group">
|
|
||||||
<view class="secondary-btn" @click="goToHome">{{ $t('success.backToHome') }}</view>
|
|
||||||
<view class="primary-btn" @click="goToOrderList">{{ $t('success.viewOrder') }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {
|
|
||||||
ref,
|
|
||||||
getCurrentInstance
|
|
||||||
} from 'vue'
|
|
||||||
import {
|
|
||||||
onLoad
|
|
||||||
} from '@dcloudio/uni-app'
|
|
||||||
import {
|
|
||||||
queryById,
|
|
||||||
getOrderByOrderNo
|
|
||||||
} from '@/config/api/order.js'
|
|
||||||
|
|
||||||
// 获取当前实例以访问 $t 方法
|
|
||||||
const {
|
|
||||||
proxy
|
|
||||||
} = getCurrentInstance()
|
|
||||||
|
|
||||||
// 响应式数据
|
|
||||||
const orderId = ref('')
|
|
||||||
const orderInfo = ref({})
|
|
||||||
const isLoading = ref(true)
|
|
||||||
const deviceMessage = ref('')
|
|
||||||
const hasTriggeredDevice = ref(false)
|
|
||||||
|
|
||||||
// 页面加载
|
|
||||||
onLoad((options) => {
|
|
||||||
// 设置页面标题
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: proxy.$t('success.paymentSuccess')
|
|
||||||
})
|
|
||||||
|
|
||||||
deviceMessage.value = proxy.$t('success.preparingDevice')
|
|
||||||
|
|
||||||
// #ifdef H5
|
|
||||||
if (uni.getStorageSync('pendingPaymentNo')) {
|
|
||||||
orderId.value = options.orderId
|
|
||||||
loadOrderInfo()
|
|
||||||
|
|
||||||
// 添加页面显示监听,防止页面切换后重复触发弹出
|
|
||||||
uni.$once('orderSuccess:' + orderId.value, () => {
|
|
||||||
console.log('已经触发过弹出逻辑,不再重复触发')
|
|
||||||
hasTriggeredDevice.value = true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: proxy.$t('order.orderNotExist'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
goToHome()
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
// #ifndef H5
|
|
||||||
if (options && options.orderId) {
|
|
||||||
orderId.value = options.orderId
|
|
||||||
loadOrderInfo()
|
|
||||||
|
|
||||||
// 添加页面显示监听,防止页面切换后重复触发弹出
|
|
||||||
uni.$once('orderSuccess:' + orderId.value, () => {
|
|
||||||
console.log('已经触发过弹出逻辑,不再重复触发')
|
|
||||||
hasTriggeredDevice.value = true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.showToast({
|
|
||||||
title: proxy.$t('order.orderNotExist'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
goToHome()
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
// #endif
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载订单信息
|
|
||||||
const loadOrderInfo = async () => {
|
|
||||||
try {
|
|
||||||
uni.showLoading({
|
|
||||||
title: proxy.$t('common.loading')
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = await queryById(orderId.value)
|
|
||||||
if (res.code === 200 && res.data) {
|
|
||||||
const orderData = res.data
|
|
||||||
orderInfo.value = {
|
|
||||||
orderNo: orderData.orderNo || orderData.orderId,
|
|
||||||
deviceNo: orderData.deviceNo,
|
|
||||||
amount: orderData.payAmount || orderData.amount,
|
|
||||||
payTime: orderData.payTime || formatTime(new Date())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查订单状态
|
|
||||||
if (orderData.orderStatus === 'IN_USED') {
|
|
||||||
// 如果已经是使用中状态,可能说明开锁已经完成
|
|
||||||
deviceMessage.value = '设备已弹出,请取走您的风扇'
|
|
||||||
isLoading.value = false
|
|
||||||
|
|
||||||
// 如果是第一次加载页面且设备已弹出,记录状态,避免重复弹出
|
|
||||||
if (!hasTriggeredDevice.value) {
|
|
||||||
uni.$emit('orderSuccess:' + orderId.value)
|
|
||||||
hasTriggeredDevice.value = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 在此页面不再触发设备弹出操作,仅更新展示文案和加载状态
|
|
||||||
deviceMessage.value = proxy.$t('success.paymentSuccessDesc')
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('获取订单信息失败')
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.hideLoading()
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({
|
|
||||||
title: error.message || proxy.$t('order.getOrderFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化时间
|
|
||||||
const formatTime = (date) => {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
||||||
const day = date.getDate().toString().padStart(2, '0')
|
|
||||||
const hour = date.getHours().toString().padStart(2, '0')
|
|
||||||
const minute = date.getMinutes().toString().padStart(2, '0')
|
|
||||||
const second = date.getSeconds().toString().padStart(2, '0')
|
|
||||||
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回首页
|
|
||||||
const goToHome = () => {
|
|
||||||
uni.reLaunch({
|
|
||||||
url: '/pages/index/index'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看订单列表
|
|
||||||
const goToOrderList = () => {
|
|
||||||
uni.redirectTo({
|
|
||||||
url: '/pages/order/index'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.success-container {
|
|
||||||
padding: 20px;
|
|
||||||
padding-bottom: 180rpx;
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
min-height: 100vh;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-order-card {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.status-section {
|
|
||||||
padding: 30px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.status-icon {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
margin: 0 auto 16px;
|
|
||||||
background-color: #07c160;
|
|
||||||
border-radius: 50%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 30px;
|
|
||||||
height: 20px;
|
|
||||||
border: 3px solid #fff;
|
|
||||||
border-top: none;
|
|
||||||
border-right: none;
|
|
||||||
transform-origin: center;
|
|
||||||
transform: translate(-50%, -70%) rotate(-45deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-text {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #07c160;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-desc {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-divider {
|
|
||||||
height: 1px;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
margin: 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.order-section {
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
color: #333;
|
|
||||||
padding-left: 12px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
width: 4px;
|
|
||||||
height: 16px;
|
|
||||||
background-color: #07c160;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
color: #333;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.device-status {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.status-message {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-animation {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 40px;
|
|
||||||
|
|
||||||
.loading-circle {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid #f0f0f0;
|
|
||||||
border-top-color: #07c160;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 20rpx 30rpx;
|
|
||||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.04);
|
|
||||||
z-index: 10;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20rpx;
|
|
||||||
|
|
||||||
.primary-btn {
|
|
||||||
background-color: #07c160;
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 32rpx;
|
|
||||||
padding: 0 32rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
line-height: 64rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-btn {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #07c160;
|
|
||||||
border: 2rpx solid #07c160;
|
|
||||||
border-radius: 32rpx;
|
|
||||||
padding: 0 32rpx;
|
|
||||||
font-size: 24rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
line-height: 64rpx;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="deposit-container">
|
|
||||||
<!-- 押金金额卡片 -->
|
|
||||||
<view class="deposit-card">
|
|
||||||
<view class="title">{{ $t('deposit.depositBalance') }}</view>
|
|
||||||
<view class="amount">¥{{ depositAmount }}</view>
|
|
||||||
<button class="withdraw-btn" @click="handleWithdraw" :disabled="depositAmount <= 0">{{ $t('deposit.withdraw') }}</button>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 提现说明 -->
|
|
||||||
<view class="notice-card">
|
|
||||||
<view class="notice-title">
|
|
||||||
<view class="dot"></view>
|
|
||||||
<text>{{ $t('deposit.withdrawNotice') }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="notice-content">
|
|
||||||
<view class="notice-item">1. {{ $t('deposit.withdrawNotice1') }}</view>
|
|
||||||
<view class="notice-item">2. {{ $t('deposit.withdrawNotice2') }}</view>
|
|
||||||
<view class="notice-item">3. {{ $t('deposit.withdrawNotice3') }}</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 押金记录 -->
|
|
||||||
<view class="record-card" v-if="records.length > 0">
|
|
||||||
<view class="record-title">{{ $t('deposit.depositRecord') }}</view>
|
|
||||||
<view class="record-list">
|
|
||||||
<view class="record-item" v-for="(item, index) in records" :key="index">
|
|
||||||
<view class="record-info">
|
|
||||||
<text class="record-type">{{ item.typeText }}</text>
|
|
||||||
<text class="record-time">{{ item.time }}</text>
|
|
||||||
</view>
|
|
||||||
<text class="record-amount" :class="item.type === 'refund' ? 'refund' : ''">
|
|
||||||
{{ item.type === 'refund' ? '+' : '-' }}¥{{ item.amount }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import { onShow } from '@dcloudio/uni-app'
|
|
||||||
import { getUserInfo } from '@/util/index.js'
|
|
||||||
import { withdrawDeposit } from '@/config/api/user.js'
|
|
||||||
import { queryById } from '@/config/api/order.js'
|
|
||||||
import { useI18n, showModalI18n } from '@/utils/i18n.js'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const depositAmount = ref('0.00')
|
|
||||||
const orderNo = ref('')
|
|
||||||
const orderId = ref('')
|
|
||||||
const records = ref([])
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
uni.setNavigationBarTitle({
|
|
||||||
title: t('deposit.title')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onShow(() => {
|
|
||||||
loadUserInfo()
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadUserInfo = async () => {
|
|
||||||
try {
|
|
||||||
const res = await getUserInfo()
|
|
||||||
if (res.code === 200) {
|
|
||||||
depositAmount.value = res.data.balanceAmount || '0.00'
|
|
||||||
orderNo.value = res.data.latestOrderNo || ''
|
|
||||||
orderId.value = res.data.latestOrderId || ''
|
|
||||||
|
|
||||||
// 如果存在余额,获取押金记录
|
|
||||||
if (parseFloat(depositAmount.value) > 0 && orderNo.value) {
|
|
||||||
records.value = [
|
|
||||||
{
|
|
||||||
type: 'pay',
|
|
||||||
typeText: t('deposit.payRecord'),
|
|
||||||
time: formatDate(new Date()),
|
|
||||||
amount: depositAmount.value
|
|
||||||
}
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
records.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('user.getUserInfoFailed'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleWithdraw = async () => {
|
|
||||||
if (parseFloat(depositAmount.value) <= 0) {
|
|
||||||
uni.showToast({
|
|
||||||
title: t('deposit.noBalance'),
|
|
||||||
icon: 'none'
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
showModalI18n({
|
|
||||||
title: t('deposit.confirmWithdraw'),
|
|
||||||
content: t('deposit.withdrawDesc'),
|
|
||||||
success: async (res) => {
|
|
||||||
if (res.confirm) {
|
|
||||||
uni.showLoading({
|
|
||||||
title: t('deposit.withdrawing')
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await withdrawDeposit(orderNo.value)
|
|
||||||
|
|
||||||
if (result.code === 200) {
|
|
||||||
uni.hideLoading()
|
|
||||||
uni.showToast({
|
|
||||||
title: t('deposit.withdrawSubmitted'),
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
|
|
||||||
// 更新记录
|
|
||||||
records.value.push({
|
|
||||||
type: 'refund',
|
|
||||||
typeText: t('deposit.refundRecord'),
|
|
||||||
time: formatDate(new Date()),
|
|
||||||
amount: depositAmount.value
|
|
||||||
})
|
|
||||||
// 更新余额为0
|
|
||||||
depositAmount.value = '0.00'
|
|
||||||
|
|
||||||
// 重新加载用户信息
|
|
||||||
setTimeout(() => {
|
|
||||||
loadUserInfo()
|
|
||||||
}, 1500)
|
|
||||||
} else {
|
|
||||||
throw new Error(result.msg || t('deposit.withdrawFailed'))
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
uni.hideLoading()
|
|
||||||
|
|
||||||
// 更详细的错误处理
|
|
||||||
let errorMessage = t('deposit.withdrawFailed');
|
|
||||||
|
|
||||||
if (error.message) {
|
|
||||||
if (error.message.includes('尚未归还')) {
|
|
||||||
errorMessage = t('deposit.orderNotReturned');
|
|
||||||
} else if (error.message.includes('已退还')) {
|
|
||||||
errorMessage = t('deposit.alreadyRefunded');
|
|
||||||
} else if (error.message.includes('处理中')) {
|
|
||||||
errorMessage = t('deposit.refundProcessing');
|
|
||||||
} else if (error.message.includes('余额为0')) {
|
|
||||||
errorMessage = t('deposit.noBalance');
|
|
||||||
} else {
|
|
||||||
errorMessage = error.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
showModalI18n({
|
|
||||||
title: t('deposit.withdrawFailed'),
|
|
||||||
content: errorMessage,
|
|
||||||
showCancel: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatDate = (date) => {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
||||||
const day = date.getDate().toString().padStart(2, '0')
|
|
||||||
const hours = date.getHours().toString().padStart(2, '0')
|
|
||||||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
|
||||||
|
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.deposit-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #f8f8f8;
|
|
||||||
padding: 30rpx;
|
|
||||||
|
|
||||||
.deposit-card {
|
|
||||||
background: linear-gradient(135deg, #1976D2, #64B5F6);
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 40rpx;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 4rpx 20rpx rgba(25,118,210,0.2);
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 28rpx;
|
|
||||||
opacity: 0.9;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.amount {
|
|
||||||
font-size: 72rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.withdraw-btn {
|
|
||||||
background: #fff;
|
|
||||||
color: #1976D2;
|
|
||||||
width: 80%;
|
|
||||||
height: 80rpx;
|
|
||||||
line-height: 80rpx;
|
|
||||||
border-radius: 40rpx;
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[disabled] {
|
|
||||||
background: rgba(255,255,255,0.6);
|
|
||||||
color: rgba(25,118,210,0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-card {
|
|
||||||
margin-top: 30rpx;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
|
||||||
|
|
||||||
.notice-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
|
|
||||||
.dot {
|
|
||||||
width: 12rpx;
|
|
||||||
height: 12rpx;
|
|
||||||
background: #1976D2;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
text {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-content {
|
|
||||||
.notice-item {
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.8;
|
|
||||||
padding-left: 22rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-card {
|
|
||||||
margin-top: 30rpx;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 30rpx;
|
|
||||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.04);
|
|
||||||
|
|
||||||
.record-title {
|
|
||||||
font-size: 30rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
border-left: 8rpx solid #1976D2;
|
|
||||||
padding-left: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-list {
|
|
||||||
.record-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 20rpx 0;
|
|
||||||
border-bottom: 1rpx solid #f5f5f5;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-info {
|
|
||||||
.record-type {
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 6rpx;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-time {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.record-amount {
|
|
||||||
font-size: 32rpx;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&.refund {
|
|
||||||
color: #4CAF50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Terms & Conditions
|
|
||||||
|
|
||||||
**Last Update**: 2025-02-05
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Applicable Law
|
|
||||||
|
|
||||||
These Terms of Service are governed by the laws of the People's Republic of China. By using this service, you agree to be bound by Chinese law. Any disputes arising from this service shall first be resolved through friendly negotiation; if negotiation fails, either party may file a lawsuit with the People's Court having jurisdiction over the location of the service provider.
|
|
||||||
|
|
||||||
## Payment Methods
|
|
||||||
|
|
||||||
We support multiple payment methods, including but not limited to: WeChat Pay, Alipay, WeChat Pay Score deposit-free, etc. Users need to complete the payment process before using the service. After successful payment, the system will automatically unlock the device for user access. All payment transactions are conducted through secure encrypted channels to ensure user fund security.
|
|
||||||
|
|
||||||
## Refund Policy
|
|
||||||
|
|
||||||
1. **Deposit Refund**: After returning the device, the deposit will be automatically refunded to the original payment account after deducting the corresponding rental fee, expected to arrive within 0-7 business days.
|
|
||||||
2. **Order Cancellation**: Unused orders can be cancelled before use begins, and the deposit will be fully refunded.
|
|
||||||
3. **Exception Refund**: In case of special circumstances such as device failure, users can apply for a refund, which we will process within 3-5 business days after verification.
|
|
||||||
4. **Membership Cards/Coupons**: Purchased membership cards and coupons generally do not support refunds. Please contact customer service for special cases.
|
|
||||||
|
|
||||||
## Service Terms
|
|
||||||
|
|
||||||
When using this service, users should comply with the following regulations:
|
|
||||||
|
|
||||||
1. Take good care of the rented equipment and do not intentionally damage or privately occupy it;
|
|
||||||
2. Return the equipment on time to avoid additional charges;
|
|
||||||
3. Do not use the equipment for illegal purposes;
|
|
||||||
4. If equipment failure is found, contact customer service promptly.
|
|
||||||
|
|
||||||
Violation of the above regulations may result in service termination and liability.
|
|
||||||
|
|
||||||
## Liability Limitation
|
|
||||||
|
|
||||||
To the maximum extent permitted by law, we are not liable for any indirect, incidental, special, or consequential damages arising from the use or inability to use this service. Our total liability shall not exceed the fees paid by users for using this service. We are not responsible for service interruptions or delays caused by force majeure, network failures, third-party reasons, etc.
|
|
||||||
|
|
||||||
## Dispute Resolution
|
|
||||||
|
|
||||||
If users have any questions or disputes about the service, please first contact us through customer service channels. We will respond within 24 hours of receiving feedback and negotiate a resolution as soon as possible. If negotiation fails, both parties agree to submit the dispute to the People's Court with jurisdiction over the location of the service provider for resolution through litigation. During the dispute resolution period, both parties should continue to perform the undisputed terms of this agreement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
If you have questions about this agreement, please go to **My → Customer Service**.
|
|
||||||