预约表单.

This commit is contained in:
18631081161 2025-12-15 23:42:31 +08:00
parent 7cf63cfad0
commit d5a0824f20
77 changed files with 4130 additions and 1185 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/unpackage/

View File

@ -30,6 +30,10 @@
<el-icon><Menu /></el-icon>
<template #title>分类管理</template>
</el-menu-item>
<el-menu-item index="/home-content">
<el-icon><House /></el-icon>
<template #title>首页内容</template>
</el-menu-item>
<el-menu-item index="/config">
<el-icon><Setting /></el-icon>
<template #title>系统配置</template>

View File

@ -50,6 +50,12 @@ const routes = [
component: () => import('@/views/categories/index.vue'),
meta: { title: '分类管理', icon: 'Menu' }
},
{
path: 'home-content',
name: 'HomeContent',
component: () => import('@/views/home-content/index.vue'),
meta: { title: '首页内容', icon: 'House' }
},
{
path: 'config',
name: 'Config',

View File

@ -92,14 +92,16 @@ export const useAuthStore = defineStore('auth', () => {
async function login(username, password) {
const response = await api.post('/api/v1/admin/login', { username, password })
if (response.data.success) {
// 后端返回 code: 0 表示成功,转换为 success: true 格式
const isSuccess = response.data.success || response.data.code === 0
if (isSuccess) {
const { token: newToken, refreshToken: newRefreshToken, admin: adminData } = response.data.data
setTokenData(newToken, newRefreshToken)
admin.value = adminData
localStorage.setItem('admin_info', JSON.stringify(adminData))
}
return response.data
return { ...response.data, success: isSuccess }
}
function logout() {

View File

@ -159,169 +159,9 @@
</el-form>
</el-tab-pane>
<el-tab-pane label="Banner管理" name="banners">
<div class="banner-management">
<el-button type="primary" @click="showBannerDialog(null)" style="margin-bottom: 16px;">
<el-icon><Plus /></el-icon>
添加Banner
</el-button>
<el-table :data="banners" border style="width: 100%">
<el-table-column prop="id" label="ID" width="60" />
<el-table-column label="图片" width="150">
<template #default="{ row }">
<el-image
v-if="row.image_url"
:src="getFullImageUrl(row.image_url)"
style="width: 130px; height: 65px;"
fit="cover" />
</template>
</el-table-column>
<el-table-column prop="link_url" label="跳转链接" show-overflow-tooltip />
<el-table-column prop="sort_order" label="排序" width="80" />
<el-table-column label="状态" width="80">
<template #default="{ row }">
<el-tag :type="row.is_active ? 'success' : 'info'">
{{ row.is_active ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-button size="small" @click="showBannerDialog(row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteBanner(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="热门服务" name="hotServices">
<div class="hot-service-management">
<el-button type="primary" @click="showServiceDialog(null)" style="margin-bottom: 16px;">
<el-icon><Plus /></el-icon>
添加服务
</el-button>
<el-table :data="hotServices" border style="width: 100%">
<el-table-column prop="id" label="ID" width="60" />
<el-table-column prop="name_zh" label="中文名称" width="150" />
<el-table-column prop="name_en" label="英文名称" width="200" />
<el-table-column prop="name_pt" label="葡语名称" width="200" />
<el-table-column label="图片" width="100">
<template #default="{ row }">
<el-image
v-if="row.image_url"
:src="getFullImageUrl(row.image_url)"
style="width: 80px; height: 40px;"
fit="cover" />
</template>
</el-table-column>
<el-table-column prop="sort_order" label="排序" width="80" />
<el-table-column label="状态" width="80">
<template #default="{ row }">
<el-tag :type="row.is_active ? 'success' : 'info'">
{{ row.is_active ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="180">
<template #default="{ row }">
<el-button size="small" @click="showServiceDialog(row)">编辑</el-button>
<el-button size="small" type="danger" @click="deleteService(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- Banner编辑对话框 -->
<el-dialog
v-model="bannerDialogVisible"
:title="bannerForm.id ? '编辑Banner' : '添加Banner'"
width="600px">
<el-form :model="bannerForm" label-width="100px">
<el-form-item label="Banner图片" required>
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleBannerImageSuccess"
:before-upload="beforeUpload"
name="image">
<el-button type="primary">上传图片</el-button>
</el-upload>
<el-image
v-if="bannerForm.image_url"
:src="getFullImageUrl(bannerForm.image_url)"
style="width: 300px; margin-top: 10px;"
fit="cover" />
</el-form-item>
<el-form-item label="跳转链接">
<el-input v-model="bannerForm.link_url" placeholder="可选" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="bannerForm.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="bannerForm.is_active" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="bannerDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveBanner">保存</el-button>
</template>
</el-dialog>
<!-- 热门服务编辑对话框 -->
<el-dialog
v-model="serviceDialogVisible"
:title="serviceForm.id ? '编辑服务' : '添加服务'"
width="600px">
<el-form :model="serviceForm" label-width="100px">
<el-form-item label="中文名称" required>
<el-input v-model="serviceForm.name_zh" />
</el-form-item>
<el-form-item label="英文名称" required>
<el-input v-model="serviceForm.name_en" />
</el-form-item>
<el-form-item label="葡语名称" required>
<el-input v-model="serviceForm.name_pt" />
</el-form-item>
<el-form-item label="服务图片">
<el-upload
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleServiceImageSuccess"
:before-upload="beforeUpload"
name="image">
<el-button type="primary">上传图片</el-button>
</el-upload>
<el-image
v-if="serviceForm.image_url"
:src="getFullImageUrl(serviceForm.image_url)"
style="width: 150px; margin-top: 10px;"
fit="cover" />
</el-form-item>
<el-form-item label="跳转链接">
<el-input v-model="serviceForm.link_url" placeholder="可选" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="serviceForm.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="serviceForm.is_active" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="serviceDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveService">保存</el-button>
</template>
</el-dialog>
</div>
</template>
@ -678,176 +518,8 @@ const handleTabChange = (tabName) => {
}
}
// ============ Banner ============
const banners = ref([])
const bannerDialogVisible = ref(false)
const bannerForm = reactive({
id: null,
image_url: '',
link_url: '',
sort_order: 0,
is_active: true
})
const loadBanners = async () => {
try {
const response = await api.get('/api/v1/admin/home/banners')
banners.value = response.data.data || []
} catch (error) {
ElMessage.error('加载Banner列表失败')
}
}
const showBannerDialog = (banner) => {
if (banner) {
Object.assign(bannerForm, banner)
} else {
Object.assign(bannerForm, {
id: null,
image_url: '',
link_url: '',
sort_order: 0,
is_active: true
})
}
bannerDialogVisible.value = true
}
const handleBannerImageSuccess = (response) => {
if (response.code === 0) {
bannerForm.image_url = response.data.url
ElMessage.success('图片上传成功')
} else {
ElMessage.error(response.message || '图片上传失败')
}
}
const saveBanner = async () => {
if (!bannerForm.image_url) {
ElMessage.warning('请上传Banner图片')
return
}
try {
if (bannerForm.id) {
await api.put(`/api/v1/admin/home/banners/${bannerForm.id}`, bannerForm)
ElMessage.success('更新成功')
} else {
await api.post('/api/v1/admin/home/banners', bannerForm)
ElMessage.success('添加成功')
}
bannerDialogVisible.value = false
loadBanners()
} catch (error) {
ElMessage.error(error.response?.data?.message || '保存失败')
}
}
const deleteBanner = async (id) => {
try {
await ElMessageBox.confirm('确定要删除这个Banner吗?', '提示', {
type: 'warning'
})
await api.delete(`/api/v1/admin/home/banners/${id}`)
ElMessage.success('删除成功')
loadBanners()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败')
}
}
}
// ============ ============
const hotServices = ref([])
const serviceDialogVisible = ref(false)
const serviceForm = reactive({
id: null,
name_zh: '',
name_en: '',
name_pt: '',
image_url: '',
link_url: '',
sort_order: 0,
is_active: true
})
const loadHotServices = async () => {
try {
const response = await api.get('/api/v1/admin/home/hot-services')
hotServices.value = response.data.data || []
} catch (error) {
ElMessage.error('加载热门服务列表失败')
}
}
const showServiceDialog = (service) => {
if (service) {
Object.assign(serviceForm, service)
} else {
Object.assign(serviceForm, {
id: null,
name_zh: '',
name_en: '',
name_pt: '',
image_url: '',
link_url: '',
sort_order: 0,
is_active: true
})
}
serviceDialogVisible.value = true
}
const handleServiceImageSuccess = (response) => {
if (response.code === 0) {
serviceForm.image_url = response.data.url
ElMessage.success('图片上传成功')
} else {
ElMessage.error(response.message || '图片上传失败')
}
}
const saveService = async () => {
if (!serviceForm.name_zh || !serviceForm.name_en || !serviceForm.name_pt) {
ElMessage.warning('请填写所有语言的名称')
return
}
try {
if (serviceForm.id) {
await api.put(`/api/v1/admin/home/hot-services/${serviceForm.id}`, serviceForm)
ElMessage.success('更新成功')
} else {
await api.post('/api/v1/admin/home/hot-services', serviceForm)
ElMessage.success('添加成功')
}
serviceDialogVisible.value = false
loadHotServices()
} catch (error) {
ElMessage.error(error.response?.data?.message || '保存失败')
}
}
const deleteService = async (id) => {
try {
await ElMessageBox.confirm('确定要删除这个服务吗?', '提示', {
type: 'warning'
})
await api.delete(`/api/v1/admin/home/hot-services/${id}`)
ElMessage.success('删除成功')
loadHotServices()
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败')
}
}
}
onMounted(() => {
loadConfigs()
loadBanners()
loadHotServices()
})
</script>

View File

@ -0,0 +1,381 @@
<template>
<div class="home-content-container">
<el-tabs v-model="activeTab">
<!-- Banner管理 -->
<el-tab-pane label="Banner管理" name="banners">
<el-card>
<template #header>
<div class="card-header">
<span>Banner列表</span>
<el-button type="primary" size="small" @click="handleCreateBanner">
<el-icon><Plus /></el-icon>
新增Banner
</el-button>
</div>
</template>
<el-table :data="banners" v-loading="loading.banners" stripe>
<el-table-column label="图片" width="150">
<template #default="{ row }">
<el-image :src="getImageUrl(row.image_url)" style="width: 120px; height: 60px" fit="cover" />
</template>
</el-table-column>
<el-table-column prop="link_url" label="链接" show-overflow-tooltip />
<el-table-column prop="sort_order" label="排序" width="80" align="center" />
<el-table-column prop="is_active" label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.is_active ? 'success' : 'info'" size="small">
{{ row.is_active ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleEditBanner(row)">编辑</el-button>
<el-button type="danger" link size="small" @click="handleDeleteBanner(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-tab-pane>
<!-- 热门服务管理 -->
<el-tab-pane label="热门服务" name="hotServices">
<el-card>
<template #header>
<div class="card-header">
<span>热门服务列表</span>
<el-button type="primary" size="small" @click="handleCreateHotService">
<el-icon><Plus /></el-icon>
新增热门服务
</el-button>
</div>
</template>
<el-table :data="hotServices" v-loading="loading.hotServices" stripe>
<el-table-column label="列表图" width="100">
<template #default="{ row }">
<el-image v-if="row.image_url" :src="getImageUrl(row.image_url)" style="width: 60px; height: 60px" fit="cover" />
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="详情图" width="100">
<template #default="{ row }">
<el-image v-if="row.detail_image" :src="getImageUrl(row.detail_image)" style="width: 60px; height: 60px" fit="cover" />
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="name_zh" label="中文名称" min-width="120" />
<el-table-column prop="name_en" label="英文名称" min-width="120" />
<el-table-column prop="name_pt" label="葡语名称" min-width="120" />
<el-table-column prop="sort_order" label="排序" width="80" align="center" />
<el-table-column prop="is_active" label="状态" width="80" align="center">
<template #default="{ row }">
<el-tag :type="row.is_active ? 'success' : 'info'" size="small">
{{ row.is_active ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<template #default="{ row }">
<el-button type="primary" link size="small" @click="handleEditHotService(row)">编辑</el-button>
<el-button type="danger" link size="small" @click="handleDeleteHotService(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-tab-pane>
</el-tabs>
<!-- Banner编辑弹窗 -->
<el-dialog v-model="bannerDialog.visible" :title="bannerDialog.isEdit ? '编辑Banner' : '新增Banner'" width="500px">
<el-form :model="bannerForm" label-width="100px" ref="bannerFormRef">
<el-form-item label="图片" required>
<el-upload
class="image-uploader"
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="(res) => handleUploadSuccess(res, 'banner')"
:before-upload="beforeUpload"
accept="image/*">
<el-image v-if="bannerForm.image_url" :src="getImageUrl(bannerForm.image_url)" style="width: 200px; height: 100px" fit="cover" />
<el-icon v-else class="uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="链接">
<el-input v-model="bannerForm.link_url" placeholder="点击跳转链接" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="bannerForm.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="bannerForm.is_active" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="bannerDialog.visible = false">取消</el-button>
<el-button type="primary" @click="saveBanner" :loading="saving">保存</el-button>
</template>
</el-dialog>
<!-- 热门服务编辑弹窗 -->
<el-dialog v-model="hotServiceDialog.visible" :title="hotServiceDialog.isEdit ? '编辑热门服务' : '新增热门服务'" width="600px">
<el-form :model="hotServiceForm" label-width="100px" ref="hotServiceFormRef">
<el-form-item label="中文名称" required>
<el-input v-model="hotServiceForm.name_zh" placeholder="请输入中文名称" />
</el-form-item>
<el-form-item label="英文名称" required>
<el-input v-model="hotServiceForm.name_en" placeholder="Please enter English name" />
</el-form-item>
<el-form-item label="葡语名称" required>
<el-input v-model="hotServiceForm.name_pt" placeholder="Por favor, insira o nome" />
</el-form-item>
<el-form-item label="列表图">
<el-upload
class="image-uploader"
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="(res) => handleUploadSuccess(res, 'hotService')"
:before-upload="beforeUpload"
accept="image/*">
<el-image v-if="hotServiceForm.image_url" :src="getImageUrl(hotServiceForm.image_url)" style="width: 100px; height: 100px" fit="cover" />
<el-icon v-else class="uploader-icon"><Plus /></el-icon>
</el-upload>
<div class="upload-tip">首页列表显示的图片</div>
</el-form-item>
<el-form-item label="详情图">
<el-upload
class="image-uploader"
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="(res) => handleUploadSuccess(res, 'hotServiceDetail')"
:before-upload="beforeUpload"
accept="image/*">
<el-image v-if="hotServiceForm.detail_image" :src="getImageUrl(hotServiceForm.detail_image)" style="width: 100px; height: 100px" fit="cover" />
<el-icon v-else class="uploader-icon"><Plus /></el-icon>
</el-upload>
<div class="upload-tip">详情页显示的图片</div>
</el-form-item>
<el-form-item label="链接">
<el-input v-model="hotServiceForm.link_url" placeholder="点击跳转链接" />
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="hotServiceForm.sort_order" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-switch v-model="hotServiceForm.is_active" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="hotServiceDialog.visible = false">取消</el-button>
<el-button type="primary" @click="saveHotService" :loading="saving">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import api from '@/utils/api'
const activeTab = ref('hotServices')
const saving = ref(false)
const loading = reactive({ banners: false, hotServices: false })
//
const banners = ref([])
const hotServices = ref([])
// Banner
const bannerDialog = reactive({ visible: false, isEdit: false })
const bannerForm = reactive({ id: null, image_url: '', link_url: '', sort_order: 0, is_active: true })
const bannerFormRef = ref(null)
//
const hotServiceDialog = reactive({ visible: false, isEdit: false })
const hotServiceForm = reactive({
id: null, name_zh: '', name_en: '', name_pt: '',
image_url: '', detail_image: '', link_url: '', sort_order: 0, is_active: true
})
const hotServiceFormRef = ref(null)
//
const uploadUrl = computed(() => {
const baseUrl = import.meta.env.VITE_API_BASE_URL || ''
return `${baseUrl}/api/v1/admin/upload/image`
})
const uploadHeaders = computed(() => {
const token = localStorage.getItem('admin_token')
return token ? { Authorization: `Bearer ${token}` } : {}
})
// URL
function getImageUrl(path) {
if (!path) return ''
if (path.startsWith('http')) return path
const baseUrl = import.meta.env.VITE_API_BASE_URL || ''
return `${baseUrl}${path}`
}
//
function beforeUpload(file) {
const isImage = file.type.startsWith('image/')
const isLt5M = file.size / 1024 / 1024 < 5
if (!isImage) { ElMessage.error('只能上传图片文件!'); return false }
if (!isLt5M) { ElMessage.error('图片大小不能超过5MB!'); return false }
return true
}
//
function handleUploadSuccess(response, type) {
if (response.code === 0 || response.success) {
if (type === 'banner') bannerForm.image_url = response.data.url
else if (type === 'hotService') hotServiceForm.image_url = response.data.url
else if (type === 'hotServiceDetail') hotServiceForm.detail_image = response.data.url
ElMessage.success('上传成功')
} else {
ElMessage.error(response.message || '上传失败')
}
}
// Banner
async function fetchBanners() {
loading.banners = true
try {
const res = await api.get('/api/v1/admin/home/banners')
if (res.data.code === 0) banners.value = res.data.data
} catch (e) { ElMessage.error('获取Banner失败') }
finally { loading.banners = false }
}
//
async function fetchHotServices() {
loading.hotServices = true
try {
const res = await api.get('/api/v1/admin/home/hot-services')
if (res.data.code === 0) hotServices.value = res.data.data
} catch (e) { ElMessage.error('获取热门服务失败') }
finally { loading.hotServices = false }
}
// Banner
function handleCreateBanner() {
Object.assign(bannerForm, { id: null, image_url: '', link_url: '', sort_order: 0, is_active: true })
bannerDialog.isEdit = false
bannerDialog.visible = true
}
function handleEditBanner(row) {
Object.assign(bannerForm, row)
bannerDialog.isEdit = true
bannerDialog.visible = true
}
async function saveBanner() {
if (!bannerForm.image_url) { ElMessage.error('请上传图片'); return }
saving.value = true
try {
if (bannerDialog.isEdit) {
await api.put(`/api/v1/admin/home/banners/${bannerForm.id}`, bannerForm)
} else {
await api.post('/api/v1/admin/home/banners', bannerForm)
}
ElMessage.success('保存成功')
bannerDialog.visible = false
fetchBanners()
} catch (e) { ElMessage.error('保存失败') }
finally { saving.value = false }
}
async function handleDeleteBanner(row) {
await ElMessageBox.confirm('确定删除此Banner?', '提示', { type: 'warning' })
try {
await api.delete(`/api/v1/admin/home/banners/${row.id}`)
ElMessage.success('删除成功')
fetchBanners()
} catch (e) { ElMessage.error('删除失败') }
}
//
function handleCreateHotService() {
Object.assign(hotServiceForm, {
id: null, name_zh: '', name_en: '', name_pt: '',
image_url: '', detail_image: '', link_url: '', sort_order: 0, is_active: true
})
hotServiceDialog.isEdit = false
hotServiceDialog.visible = true
}
function handleEditHotService(row) {
Object.assign(hotServiceForm, row)
hotServiceDialog.isEdit = true
hotServiceDialog.visible = true
}
async function saveHotService() {
if (!hotServiceForm.name_zh || !hotServiceForm.name_en || !hotServiceForm.name_pt) {
ElMessage.error('请填写所有语言的名称')
return
}
saving.value = true
try {
if (hotServiceDialog.isEdit) {
await api.put(`/api/v1/admin/home/hot-services/${hotServiceForm.id}`, hotServiceForm)
} else {
await api.post('/api/v1/admin/home/hot-services', hotServiceForm)
}
ElMessage.success('保存成功')
hotServiceDialog.visible = false
fetchHotServices()
} catch (e) { ElMessage.error('保存失败') }
finally { saving.value = false }
}
async function handleDeleteHotService(row) {
await ElMessageBox.confirm('确定删除此热门服务?', '提示', { type: 'warning' })
try {
await api.delete(`/api/v1/admin/home/hot-services/${row.id}`)
ElMessage.success('删除成功')
fetchHotServices()
} catch (e) { ElMessage.error('删除失败') }
}
onMounted(() => {
fetchBanners()
fetchHotServices()
})
</script>
<style lang="scss" scoped>
.home-content-container {
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.image-uploader {
:deep(.el-upload) {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
&:hover { border-color: #409eff; }
}
.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
}
}
.upload-tip {
font-size: 12px;
color: #909399;
margin-top: 5px;
}
}
</style>

View File

@ -0,0 +1,27 @@
/**
* Migration: Add detail_image column to hot_services table
*/
module.exports = {
up: async (queryInterface, Sequelize) => {
// Check if column exists
const tableInfo = await queryInterface.describeTable('hot_services');
if (!tableInfo.detail_image) {
await queryInterface.addColumn('hot_services', 'detail_image', {
type: Sequelize.STRING(500),
allowNull: true,
comment: '详情图URL',
after: 'image_url'
});
console.log('✓ Added detail_image column to hot_services table');
} else {
console.log('✓ detail_image column already exists');
}
},
down: async (queryInterface, Sequelize) => {
await queryInterface.removeColumn('hot_services', 'detail_image');
console.log('✓ Removed detail_image column from hot_services table');
}
};

View File

@ -25,6 +25,11 @@ const HotService = sequelize.define(
type: DataTypes.STRING(500),
allowNull: true,
},
detail_image: {
type: DataTypes.STRING(500),
allowNull: true,
comment: '详情图URL',
},
link_url: {
type: DataTypes.STRING(500),
allowNull: true,

View File

@ -76,7 +76,7 @@ exports.getAllHotServices = async () => {
};
exports.createHotService = async (data) => {
const { name_zh, name_en, name_pt, image_url, link_url, sort_order, is_active } = data;
const { name_zh, name_en, name_pt, image_url, detail_image, link_url, sort_order, is_active } = data;
if (!name_zh || !name_en || !name_pt) {
throw new Error('All language names are required');
@ -87,6 +87,7 @@ exports.createHotService = async (data) => {
name_en,
name_pt,
image_url: image_url || null,
detail_image: detail_image || null,
link_url: link_url || null,
sort_order: sort_order || 0,
is_active: is_active !== undefined ? is_active : true,
@ -102,13 +103,14 @@ exports.updateHotService = async (id, data) => {
throw new Error('Hot service not found');
}
const { name_zh, name_en, name_pt, image_url, link_url, sort_order, is_active } = data;
const { name_zh, name_en, name_pt, image_url, detail_image, link_url, sort_order, is_active } = data;
await service.update({
name_zh: name_zh || service.name_zh,
name_en: name_en || service.name_en,
name_pt: name_pt || service.name_pt,
image_url: image_url !== undefined ? image_url : service.image_url,
detail_image: detail_image !== undefined ? detail_image : service.detail_image,
link_url: link_url !== undefined ? link_url : service.link_url,
sort_order: sort_order !== undefined ? sort_order : service.sort_order,
is_active: is_active !== undefined ? is_active : service.is_active,

View File

@ -26,7 +26,7 @@ exports.getActiveBanners = async () => {
exports.getActiveHotServices = async (lang = 'zh') => {
const services = await HotService.findAll({
where: { is_active: true },
attributes: ['id', 'name_zh', 'name_en', 'name_pt', 'image_url', 'link_url', 'sort_order'],
attributes: ['id', 'name_zh', 'name_en', 'name_pt', 'image_url', 'detail_image', 'link_url', 'sort_order'],
order: [
['sort_order', 'ASC'],
['id', 'ASC'],
@ -40,6 +40,7 @@ exports.getActiveHotServices = async (lang = 'zh') => {
id: service.id,
name: service[nameField],
image_url: service.image_url,
detail_image: service.detail_image,
link_url: service.link_url,
sort_order: service.sort_order,
};

View File

@ -66,7 +66,15 @@ export default {
required: 'Required',
pleaseEnterName: 'Please enter your name',
hasData: 'Data available',
selectCountry: 'Select Country/Region'
selectCountry: 'Select Country/Region',
remark: 'Remark',
remarkPlaceholder: 'Please enter remark',
serviceInfo: 'Service Appointment Info',
departureDate: 'Departure Date',
departureDatePlaceholder: 'Please select departure date',
year: '',
month: '',
day: ''
},
me: {
title: 'Profile',

View File

@ -66,7 +66,15 @@ export default {
required: 'Obrigatório',
pleaseEnterName: 'Por favor, insira seu nome',
hasData: 'Dados disponíveis',
selectCountry: 'Selecionar País/Região'
selectCountry: 'Selecionar País/Região',
remark: 'Observação',
remarkPlaceholder: 'Por favor, insira observação',
serviceInfo: 'Informações do Serviço',
departureDate: 'Data de Partida',
departureDatePlaceholder: 'Por favor, selecione a data de partida',
year: '',
month: '',
day: ''
},
me: {
title: 'Perfil',

View File

@ -66,7 +66,15 @@ export default {
required: '必填',
pleaseEnterName: '请输入姓名',
hasData: '有数据',
selectCountry: '选择国家/地区'
selectCountry: '选择国家/地区',
remark: '备注',
remarkPlaceholder: '请输入备注信息',
serviceInfo: '服务预约信息',
departureDate: '出发日期',
departureDatePlaceholder: '请选择出发日期',
year: '年',
month: '月',
day: '日'
},
me: {
title: '个人中心',

View File

@ -13,3 +13,5 @@
{"level":"info","message":"Found 2 seeder files","service":"overseas-appointment-api","timestamp":"2025-12-05 23:51:02"}
{"level":"info","message":"Running seeder: 001-seed-categories.js","service":"overseas-appointment-api","timestamp":"2025-12-05 23:51:02"}
{"errors":[{"instance":null,"message":"key must be unique","origin":"DB","path":"key","type":"unique violation","validatorArgs":[],"validatorKey":"not_unique","validatorName":null,"value":"airport"}],"fields":{"key":"airport"},"level":"error","message":"Database initialization failed: Validation error","name":"SequelizeUniqueConstraintError","original":{"code":"ER_DUP_ENTRY","errno":1062,"sql":"INSERT INTO `category` (`id`,`key`,`name_zh`,`name_en`,`name_pt`,`icon`,`sort_order`,`created_at`,`updated_at`) VALUES ('802e5e46-5d2d-47e2-9ee7-926726188c91','airport','机场接送','Airport Transfer','Transferência de Aeroporto','/icons/airport.png',1,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('e1b7a339-763a-44a5-9d57-efa767ed52af','train','火车票','Train Ticket','Bilhete de Trem','/icons/train.png',2,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('7dcb2121-2cad-4ecc-b107-c02148ee9c4b','high-speed-rail','高铁票','High-Speed Rail','Trem de Alta Velocidade','/icons/high-speed-rail.png',3,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('0d61dfbd-1170-4402-a268-7b6825ec8459','bus','汽车票','Bus Ticket','Bilhete de Ônibus','/icons/bus.png',4,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('e4865661-52f1-43a6-ab9e-5c9a1c0f735f','hotel','酒店预订','Hotel Booking','Reserva de Hotel','/icons/hotel.png',5,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('1cc01e13-cbb2-42a3-ae95-6bad1390533f','homestay','民宿预订','Homestay Booking','Reserva de Casa de Família','/icons/homestay.png',6,'2025-12-05 15:51:02','2025-12-05 15:51:02');","sqlMessage":"Duplicate entry 'airport' for key 'category.key'","sqlState":"23000"},"parent":{"code":"ER_DUP_ENTRY","errno":1062,"sql":"INSERT INTO `category` (`id`,`key`,`name_zh`,`name_en`,`name_pt`,`icon`,`sort_order`,`created_at`,`updated_at`) VALUES ('802e5e46-5d2d-47e2-9ee7-926726188c91','airport','机场接送','Airport Transfer','Transferência de Aeroporto','/icons/airport.png',1,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('e1b7a339-763a-44a5-9d57-efa767ed52af','train','火车票','Train Ticket','Bilhete de Trem','/icons/train.png',2,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('7dcb2121-2cad-4ecc-b107-c02148ee9c4b','high-speed-rail','高铁票','High-Speed Rail','Trem de Alta Velocidade','/icons/high-speed-rail.png',3,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('0d61dfbd-1170-4402-a268-7b6825ec8459','bus','汽车票','Bus Ticket','Bilhete de Ônibus','/icons/bus.png',4,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('e4865661-52f1-43a6-ab9e-5c9a1c0f735f','hotel','酒店预订','Hotel Booking','Reserva de Hotel','/icons/hotel.png',5,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('1cc01e13-cbb2-42a3-ae95-6bad1390533f','homestay','民宿预订','Homestay Booking','Reserva de Casa de Família','/icons/homestay.png',6,'2025-12-05 15:51:02','2025-12-05 15:51:02');","sqlMessage":"Duplicate entry 'airport' for key 'category.key'","sqlState":"23000"},"service":"overseas-appointment-api","sql":"INSERT INTO `category` (`id`,`key`,`name_zh`,`name_en`,`name_pt`,`icon`,`sort_order`,`created_at`,`updated_at`) VALUES ('802e5e46-5d2d-47e2-9ee7-926726188c91','airport','机场接送','Airport Transfer','Transferência de Aeroporto','/icons/airport.png',1,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('e1b7a339-763a-44a5-9d57-efa767ed52af','train','火车票','Train Ticket','Bilhete de Trem','/icons/train.png',2,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('7dcb2121-2cad-4ecc-b107-c02148ee9c4b','high-speed-rail','高铁票','High-Speed Rail','Trem de Alta Velocidade','/icons/high-speed-rail.png',3,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('0d61dfbd-1170-4402-a268-7b6825ec8459','bus','汽车票','Bus Ticket','Bilhete de Ônibus','/icons/bus.png',4,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('e4865661-52f1-43a6-ab9e-5c9a1c0f735f','hotel','酒店预订','Hotel Booking','Reserva de Hotel','/icons/hotel.png',5,'2025-12-05 15:51:02','2025-12-05 15:51:02'),('1cc01e13-cbb2-42a3-ae95-6bad1390533f','homestay','民宿预订','Homestay Booking','Reserva de Casa de Família','/icons/homestay.png',6,'2025-12-05 15:51:02','2025-12-05 15:51:02');","stack":"Error\n at Query.run (F:\\work\\appointment_system\\backend\\node_modules\\sequelize\\lib\\dialects\\mysql\\query.js:52:25)\n at F:\\work\\appointment_system\\backend\\node_modules\\sequelize\\lib\\sequelize.js:315:28\n at process.processTicksAndRejections (node:internal/process/task_queues:105:5)\n at async MySQLQueryInterface.bulkInsert (F:\\work\\appointment_system\\backend\\node_modules\\sequelize\\lib\\dialects\\abstract\\query-interface.js:346:21)\n at async Object.up (F:\\work\\appointment_system\\backend\\src\\seeders\\001-seed-categories.js:79:5)\n at async initDatabase (F:\\work\\appointment_system\\backend\\src\\scripts\\initDatabase.js:52:9)","timestamp":"2025-12-05 23:51:02"}
{"level":"info","message":"Redis client connected","service":"overseas-appointment-api","timestamp":"2025-12-15 18:08:24"}
{"level":"info","message":"Redis client ready","service":"overseas-appointment-api","timestamp":"2025-12-15 18:08:24"}

View File

@ -337,10 +337,11 @@ AppServer.prototype.SetLanguage = async function(language) {
/**
* 获取服务分类列表
* @param {Object} params - { language }
*/
AppServer.prototype.GetCategories = async function() {
AppServer.prototype.GetCategories = async function(params = {}) {
var url = serverConfig.apiUrl_Service_GetCategories
return this.getData(url).then((data) => {
return this.getData(url, params).then((data) => {
return data;
})
}

View File

@ -1,9 +1,9 @@
// @ts-nocheck
export {};
declare global {
var __VLS_PROPS_FALLBACK: Record<string, unknown>;
; declare global {
const __VLS_directiveBindingRestFields: { instance: null, oldValue: null, modifiers: any, dir: any };
const __VLS_unref: typeof import('vue').unref;
const __VLS_placeholder: any;
const __VLS_intrinsics: import('vue/jsx-runtime').JSX.IntrinsicElements;
@ -13,7 +13,7 @@ export {};
type __VLS_IsAny<T> = 0 extends 1 & T ? true : false;
type __VLS_PickNotAny<A, B> = __VLS_IsAny<A> extends true ? B : A;
type __VLS_SpreadMerge<A, B> = Omit<A, keyof B> & B;
type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string, N3 extends string> =
type __VLS_WithComponent<N0 extends string, LocalComponents, Self, N1 extends string, N2 extends string = N1, N3 extends string = N1> =
N1 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N1] } :
N2 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N2] } :
N3 extends keyof LocalComponents ? { [K in N0]: LocalComponents[N3] } :
@ -35,7 +35,7 @@ export {};
attrs?: any;
slots?: T extends { $slots: infer Slots } ? Slots : Record<string, any>;
emit?: T extends { $emit: infer Emit } ? Emit : {};
props?: (T extends { $props: infer Props } ? Props : {}) & Record<string, unknown>;
props?: typeof props;
expose?: (exposed: T) => void;
};
};
@ -95,12 +95,7 @@ export {};
type __VLS_ResolveDirectives<T> = {
[K in keyof T & string as `v${Capitalize<K>}`]: T[K];
};
type __VLS_PrettifyGlobal<T> = { [K in keyof T as K]: T[K]; } & {};
type __VLS_WithDefaultsGlobal<P, D> = {
[K in keyof P as K extends keyof D ? K : never]-?: P[K];
} & {
[K in keyof P as K extends keyof D ? never : K]: P[K];
};
type __VLS_PrettifyGlobal<T> = (T extends any ? { [K in keyof T]: T[K]; } : { [K in keyof T as K]: T[K]; }) & {};
type __VLS_UseTemplateRef<T> = Readonly<import('vue').ShallowRef<T | null>>;
type __VLS_ProxyRefs<T> = import('vue').ShallowUnwrapRef<T>;

View File

@ -95,7 +95,7 @@
async loadCategories() {
try {
const appserver = new AppServer()
const response = await appserver.GetCategories()
const response = await appserver.GetCategories({ language: this.currentLanguage })
console.log('分类列表响应:', response)

View File

@ -1,7 +1,7 @@
<template>
<view class="content">
<view class="row" style="width: 100%; margin-top: 100rpx; align-items: center; justify-content: space-between;">
<view class="page">
<!-- 固定头部 -->
<view class="header">
<view class="center" style="width: 50rpx; height: 50rpx; margin-left: 32rpx;">
<image src="/static/ic_back.png" @click="back" style="width: 48rpx; height: 48rpx;" mode=""></image>
</view>
@ -9,68 +9,231 @@
<view style="width: 50rpx;margin-right: 32rpx;"></view>
</view>
<view class=""
style="width: 680rpx; height: 396rpx; background-image: linear-gradient(-45deg, #60D7FF, #68BBD7); margin-top: 32rpx; border-radius: 20rpx; box-shadow: 0 0 10rpx 10rpx rgba(0, 0, 0, 0.1);">
<!-- 可滚动内容区域 -->
<view class="scroll-content">
<view class="content">
<view class=""
style="width: 680rpx; height: 396rpx; background-image: linear-gradient(-45deg, #60D7FF, #68BBD7); margin-top: 32rpx; border-radius: 20rpx; box-shadow: 0 0 10rpx 10rpx rgba(0, 0, 0, 0.1);">
</view>
</view>
<view class="" style="width: 100%; font-size: 40rpx; padding-left: 54rpx; margin-top: 38rpx;">
{{ $t('infoEntry.personalInfo') }}
</view>
<view class="" style="width: 100%; font-size: 40rpx; padding-left: 54rpx; margin-top: 38rpx;">
{{ $t('infoEntry.personalInfo') }}
</view>
<!-- 真实姓名 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'userName' }"
style="width: 680rpx; margin-top: 38rpx;" id="fieldUserName">
<text style="font-size: 30rpx;"><text
style="color: #FF0000;">*</text>{{ $t('infoEntry.realName') }}</text>
<up-input :placeholder="$t('infoEntry.realNamePlaceholder')" border="surround"
v-model="userName"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 真实姓名 -->
<view class="column" :class="{ 'flash-animation': isFlashing }" style="width: 680rpx; margin-top: 38rpx;">
<!-- 微信号 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'contact' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldContact">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.wechat') }}
({{ $t('infoEntry.contactMethod') }})</text>
<up-input :placeholder="$t('infoEntry.wechatPlaceholder')" border="surround"
v-model="userWechat"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.realName') }}</text>
<!-- 手机号 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'contact' }"
style="width: 680rpx; margin-top: 14rpx;">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.phone') }}
({{ $t('infoEntry.contactMethod') }})</text>
<view class="row" style="margin-top: 10rpx; margin-bottom: 10rpx;">
<aure-country-picker v-model="selectedDialCode" :title="$t('infoEntry.selectCountry')"
:height="'70%'" :width="'60vw'" :duration="350" :position="'bottom'" :round="true"
:radius="'24rpx'" :mask-closable="true"></aure-country-picker>
<up-input :placeholder="$t('infoEntry.phonePlaceholder')" border="surround"
v-model="userPhone"></up-input>
</view>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<up-input :placeholder="$t('infoEntry.realNamePlaceholder')" border="surround" v-model="userName"></up-input>
<!-- whatsApp -->
<view class="column" :class="{ 'flash-animation': flashingField === 'contact' }"
style="width: 680rpx; margin-top: 14rpx;">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.whatsapp') }}
({{ $t('infoEntry.contactMethod') }})</text>
<up-input :placeholder="$t('infoEntry.whatsappPlaceholder')" border="surround"
v-model="userWhats"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
</view>
<!-- 备注 -->
<view class="column" style="width: 680rpx; margin-top: 14rpx;">
<text style="font-size: 30rpx;">{{ $t('infoEntry.remark') }}</text>
<up-input :placeholder="$t('infoEntry.remarkPlaceholder')" border="surround"
v-model="remark"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 服务预约信息 -->
<view class="" style="width: 100%; font-size: 40rpx; padding-left: 54rpx; margin-top: 38rpx;">
{{ $t('infoEntry.serviceInfo') }}
</view>
<!-- 单程/往返 -->
<view class="column" style="width: 680rpx; margin-top: 14rpx;">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>单程 / 往返</text>
<view class="date-item" @click="showTripPicker = true">
<text class="date-text">{{ tripTypeText }}</text>
<image src="/static/arrow_down.png" style="width: 32rpx; height: 32rpx;" mode="aspectFit">
</image>
</view>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 微信号 -->
<view class="column" :class="{ 'flash-animation': isFlashing }" style="width: 680rpx; margin-top: 14rpx;">
<!-- 出发日期 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'departureDate' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldDepartureDate">
<text style="font-size: 30rpx;"><text
style="color: #FF0000;">*</text>{{ $t('infoEntry.departureDate') }}</text>
<view class="date-item" @click="openDepartureCalendar">
<text class="date-text" :class="{ 'date-placeholder': !departureDate }">
{{ departureDate || $t('infoEntry.departureDatePlaceholder') }}
</text>
<image src="/static/arrow_right2.png" style="width: 32rpx; height: 32rpx;" mode="aspectFit">
</image>
</view>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.wechat') }} ({{ $t('infoEntry.contactMethod') }})</text>
<!-- 返程日期 (往返时显示) -->
<view v-if="tripType === 'round'" class="column"
:class="{ 'flash-animation': flashingField === 'returnDate' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldReturnDate">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>返程日期</text>
<view class="date-item" @click="openReturnCalendar">
<text class="date-text" :class="{ 'date-placeholder': !returnDate }">
{{ returnDate || '请选择返程日期' }}
</text>
<image src="/static/arrow_right2.png" style="width: 32rpx; height: 32rpx;" mode="aspectFit">
</image>
</view>
</view>
<view v-if="tripType === 'round'" class=""
style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<up-input :placeholder="$t('infoEntry.wechatPlaceholder')" border="surround" v-model="userName"></up-input>
<!-- 出发城市 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'departureCity' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldDepartureCity">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>出发城市</text>
<up-input placeholder="请输入出发城市" border="surround" v-model="departureCity"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
</view>
<!-- 到达城市 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'arrivalCity' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldArrivalCity">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>到达城市</text>
<up-input placeholder="请输入到达城市" border="surround" v-model="arrivalCity"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 人数 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'personCount' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldPersonCount">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>人数</text>
<!-- 手机号 -->
<view class="column" :class="{ 'flash-animation': isFlashing }" style="width: 680rpx; margin-top: 14rpx;">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.phone') }} ({{ $t('infoEntry.contactMethod') }})</text>
<view class="row" style="margin-top: 10rpx; margin-bottom: 10rpx;">
<aure-country-picker v-model="selectedDialCode" :title="$t('infoEntry.selectCountry')" :height="'70%'" :width="'60vw'"
:duration="350" :position="'bottom'" :round="true" :radius="'24rpx'"
:mask-closable="true"></aure-country-picker>
<up-input :placeholder="$t('infoEntry.phonePlaceholder')" border="surround" v-model="userName"></up-input>
<!-- 成人 -->
<view class="person-row">
<view class="person-info">
<text class="person-title">成人</text>
<text class="person-desc">13岁或以上</text>
</view>
<view class="person-counter">
<view class="counter-btn" @click="decreaseCount('adult')">
<text class="counter-icon"></text>
</view>
<text class="counter-value">{{ adultCount }}</text>
<view class="counter-btn" @click="increaseCount('adult')">
<text class="counter-icon"></text>
</view>
</view>
</view>
<!-- 儿童 -->
<view class="person-row">
<view class="person-info">
<text class="person-title">儿童</text>
<text class="person-desc">2~12</text>
</view>
<view class="person-counter">
<view class="counter-btn" @click="decreaseCount('child')">
<text class="counter-icon"></text>
</view>
<text class="counter-value">{{ childCount }}</text>
<view class="counter-btn" @click="increaseCount('child')">
<text class="counter-icon"></text>
</view>
</view>
</view>
<!-- 婴儿 -->
<view class="person-row">
<view class="person-info">
<text class="person-title">婴儿</text>
<text class="person-desc">2岁以下</text>
</view>
<view class="person-counter">
<view class="counter-btn" @click="decreaseCount('infant')">
<text class="counter-icon"></text>
</view>
<text class="counter-value">{{ infantCount }}</text>
<view class="counter-btn" @click="increaseCount('infant')">
<text class="counter-icon"></text>
</view>
</view>
</view>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 舱位选择 -->
<view class="column" style="width: 680rpx; margin-top: 14rpx;">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>舱位选择</text>
<view class="date-item" @click="showCabinPicker = true">
<text class="date-text">{{ cabinTypeText }}</text>
<image src="/static/arrow_down.png" style="width: 32rpx; height: 32rpx;" mode="aspectFit">
</image>
</view>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- 行李件数 -->
<view class="column" :class="{ 'flash-animation': flashingField === 'luggageCount' }"
style="width: 680rpx; margin-top: 14rpx;" id="fieldLuggageCount">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>行李件数</text>
<up-input placeholder="请输入行李件数" border="surround" v-model="luggageCount" type="number"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<view class="center" @click="checkData()"
style="width: 642rpx; height: 72rpx; background-color: #57C9DD; border-radius: 16rpx; box-shadow: 0 0 10rpx 10rpx rgba(0, 0, 0, 0.1); margin-top: 50rpx; margin-bottom: 100rpx;">
{{ $t('common.submit') }}
</view>
</view>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<!-- whatsApp -->
<view class="column" :class="{ 'flash-animation': isFlashing }" style="width: 680rpx; margin-top: 14rpx;">
<text style="font-size: 30rpx;"><text style="color: #FF0000;">*</text>{{ $t('infoEntry.whatsapp') }} ({{ $t('infoEntry.contactMethod') }})</text>
<up-input :placeholder="$t('infoEntry.whatsappPlaceholder')" border="surround" v-model="userName"></up-input>
</view>
<view class="" style="width: 680rpx; height: 2rpx; background-color: #EAEAEA;"></view>
<view class="center" @click="checkData()"
style="width: 642rpx; height: 72rpx; background-color: #57C9DD; border-radius: 16rpx; box-shadow: 0 0 10rpx 10rpx rgba(0, 0, 0, 0.1); margin-top: 50rpx;">
{{ $t('common.submit') }}
</view>
<!-- 行程类型选择器 -->
<up-picker :show="showTripPicker" :columns="tripTypeColumns" @confirm="onTripTypeConfirm"
@cancel="showTripPicker = false" @close="showTripPicker = false" :defaultIndex="[tripTypeIndex]"
keyName="label">
</up-picker>
<!-- 舱位选择器 -->
<up-picker :show="showCabinPicker" :columns="cabinTypeColumns" @confirm="onCabinTypeConfirm"
@cancel="showCabinPicker = false" @close="showCabinPicker = false" :defaultIndex="[cabinTypeIndex]"
keyName="label">
</up-picker>
<!-- 日历选择器 -->
<up-calendar :show="showCalendar" mode="single" :minDate="minDate" :maxDate="maxDate"
@confirm="onCalendarConfirm" @close="closeCalendar" :confirmText="$t('common.confirm')" color="#57C9DD">
</up-calendar>
</view>
</template>
<script>
@ -78,72 +241,385 @@
data() {
return {
userName: "",
isFlashing: false, //
selectedDialCode: '86'
userWechat: "",
userPhone: "",
userWhats: "",
remark: "",
departureDate: "",
returnDate: "",
departureCity: "",
arrivalCity: "",
luggageCount: "",
adultCount: 0,
childCount: 0,
infantCount: 0,
calendarType: "departure",
tripType: "single",
tripTypeIndex: 0,
tripTypeColumns: [
[{
label: '单程',
value: 'single'
},
{
label: '往返',
value: 'round'
}
]
],
cabinType: "economy",
cabinTypeIndex: 0,
cabinTypeColumns: [
[{
label: '经济舱',
value: 'economy'
},
{
label: '超级经济舱',
value: 'premium_economy'
},
{
label: '商务舱',
value: 'business'
}
]
],
flashingField: '',
selectedDialCode: '86',
showCalendar: false,
showTripPicker: false,
showCabinPicker: false,
minDate: '',
maxDate: ''
}
},
methods: {
checkData() {
if (this.userName != "") {
//
uni.showToast({
title: this.$t('infoEntry.hasData'),
icon: 'none'
});
} else {
uni.showToast({
title: this.$t('infoEntry.pleaseEnterName'),
icon: 'none'
});
//
this.isFlashing = true;
// 1 CSS
setTimeout(() => {
this.isFlashing = false;
}, 1500);
}
computed: {
tripTypeText() {
const item = this.tripTypeColumns[0].find(t => t.value === this.tripType)
return item ? item.label : '单程'
},
cabinTypeText() {
const item = this.cabinTypeColumns[0].find(t => t.value === this.cabinType)
return item ? item.label : '经济舱'
}
},
onLoad() {
this.initDateRange()
},
methods: {
initDateRange() {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
this.minDate = `${year}-${month}-${day}`
this.maxDate = `${year + 2}-12-31`
},
openDepartureCalendar() {
console.log('打开出发日期选择器')
this.calendarType = 'departure'
this.initDateRange()
this.$nextTick(() => {
this.showCalendar = true
})
},
openReturnCalendar() {
console.log('打开返程日期选择器')
this.calendarType = 'return'
//
if (this.departureDate) {
this.minDate = this.departureDate
} else {
this.initDateRange()
}
this.$nextTick(() => {
this.showCalendar = true
})
},
closeCalendar() {
this.showCalendar = false
this.initDateRange()
},
onCalendarConfirm(dates) {
console.log('日历确认:', dates)
if (dates && dates.length > 0) {
if (this.calendarType === 'departure') {
this.departureDate = dates[0]
//
if (this.returnDate && this.returnDate < dates[0]) {
this.returnDate = ''
}
} else {
this.returnDate = dates[0]
}
}
this.showCalendar = false
this.initDateRange()
},
onTripTypeConfirm(e) {
const selected = e.value[0]
this.tripType = selected.value
this.tripTypeIndex = this.tripTypeColumns[0].findIndex(t => t.value === selected.value)
this.showTripPicker = false
},
onCabinTypeConfirm(e) {
const selected = e.value[0]
this.cabinType = selected.value
this.cabinTypeIndex = this.cabinTypeColumns[0].findIndex(t => t.value === selected.value)
this.showCabinPicker = false
},
checkData() {
//
const validations = [{
field: 'userName',
selector: '#fieldUserName',
check: () => !this.userName.trim(),
message: '请输入真实姓名'
},
{
field: 'contact',
selector: '#fieldContact',
check: () => !this.userWechat.trim() && !this.userPhone.trim() && !this.userWhats.trim(),
message: '请至少填写一种联系方式(微信号/手机号/WhatsApp'
},
{
field: 'departureDate',
selector: '#fieldDepartureDate',
check: () => !this.departureDate,
message: '请选择出发日期'
},
{
field: 'returnDate',
selector: '#fieldReturnDate',
check: () => this.tripType === 'round' && !this.returnDate,
message: '请选择返程日期'
},
{
field: 'departureCity',
selector: '#fieldDepartureCity',
check: () => !this.departureCity.trim(),
message: '请输入出发城市'
},
{
field: 'arrivalCity',
selector: '#fieldArrivalCity',
check: () => !this.arrivalCity.trim(),
message: '请输入到达城市'
},
{
field: 'personCount',
selector: '#fieldPersonCount',
check: () => this.adultCount === 0 && this.childCount === 0 && this.infantCount === 0,
message: '请至少选择一位乘客'
},
{
field: 'luggageCount',
selector: '#fieldLuggageCount',
check: () => !this.luggageCount || this.luggageCount === '',
message: '请输入行李件数'
}
]
//
for (const validation of validations) {
if (validation.check()) {
uni.showToast({
title: validation.message,
icon: 'none'
})
//
this.scrollToElement(validation.selector)
//
this.flashingField = validation.field
setTimeout(() => {
this.flashingField = ''
}, 1500)
return
}
}
//
uni.showToast({
title: this.$t('infoEntry.hasData'),
icon: 'none'
})
// TODO:
},
scrollToElement(selector) {
const systemInfo = uni.getSystemInfoSync()
const screenHeight = systemInfo.windowHeight
const query = uni.createSelectorQuery().in(this)
query.select(selector).boundingClientRect()
query.selectViewport().scrollOffset()
query.exec((res) => {
if (res[0] && res[1]) {
const rect = res[0]
const scrollInfo = res[1]
// + top - +
const targetScrollTop = scrollInfo.scrollTop + rect.top - (screenHeight / 2) + (rect
.height / 2)
uni.pageScrollTo({
scrollTop: Math.max(0, targetScrollTop),
duration: 300
})
}
})
},
back() {
uni.navigateBack({
delta: 1
});
},
increaseCount(type) {
if (type === 'adult') {
this.adultCount++
} else if (type === 'child') {
this.childCount++
} else if (type === 'infant') {
this.infantCount++
}
},
decreaseCount(type) {
if (type === 'adult' && this.adultCount > 0) {
this.adultCount--
} else if (type === 'child' && this.childCount > 0) {
this.childCount--
} else if (type === 'infant' && this.infantCount > 0) {
this.infantCount--
}
}
}
}
</script>
<style lang="scss">
.page {
min-height: 100vh;
background-color: #F3F3F3;
}
.header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
padding-top: 88rpx;
padding-bottom: 20rpx;
background-color: #F3F3F3;
position: fixed;
top: 0;
left: 0;
z-index: 100;
}
.scroll-content {
padding-top: 140rpx;
background-color: #F3F3F3;
min-height: 100vh;
}
.content {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
background-color: #F3F3F3;
min-height: 100%;
}
.date-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
height: 80rpx;
padding: 0 10rpx;
box-sizing: border-box;
}
.date-text {
font-size: 28rpx;
color: #333;
}
.date-placeholder {
color: #c0c4cc;
}
/* 闪烁动画关键帧 */
@keyframes flash {
0% {
background-color: #F3F3F3;
/* 原背景 */
}
50% {
background-color: #ff6666;
/* 高亮色(红色示例) */
}
100% {
background-color: #F3F3F3;
/* 恢复原背景 */
}
}
/* 动画类名(触发时添加) */
.flash-animation {
animation: flash 0.5s ease-in-out 3;
/* 0.3秒/次执行3次总时长0.9秒接近1秒 */
}
.person-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 20rpx 10rpx;
}
.person-info {
display: flex;
flex-direction: column;
}
.person-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.person-desc {
font-size: 24rpx;
color: #999;
margin-top: 6rpx;
}
.person-counter {
display: flex;
flex-direction: row;
align-items: center;
}
.counter-btn {
width: 50rpx;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #BFBFBF;
border-radius: 8rpx;
}
.counter-icon {
font-size: 28rpx;
color: #fff;
font-weight: bold;
line-height: 50rpx;
text-align: center;
color: #FFFFFF;
}
.counter-value {
font-size: 28rpx;
color: #333;
min-width: 80rpx;
text-align: center;
}
</style>

View File

@ -201,7 +201,7 @@
//
uni.navigateTo({
url: '/pages/index/reserve-details-page?title=' + item.name
url: '/pages/index/reserve-details-page?id=' + item.id + '&title=' + encodeURIComponent(item.name)
});
}
}

View File

@ -5,18 +5,22 @@
<view class="center" style="width: 50rpx; height: 50rpx; margin-left: 32rpx;">
<image src="/static/ic_back.png" @click="back" style="width: 48rpx; height: 48rpx;" mode=""></image>
</view>
<text style="font-size: 30rpx;">{{title}}</text>
<text style="font-size: 30rpx;">{{ title }}</text>
<view style="width: 50rpx;margin-right: 32rpx;"></view>
</view>
<view class=""
style="width: 100%; flex: 1; background-color: antiquewhite; overflow-y: auto; margin-top: 28rpx;">
<view class="" style="width: 50rpx; height: 1660rpx; background-color: aquamarine;">
<view class="" style="width: 100%; flex: 1; overflow-y: auto; margin-top: 28rpx;">
<!-- 详情图片 -->
<image
v-if="detailImage"
:src="detailImage"
style="width: 100%;"
mode="widthFix"
@error="handleImageError">
</image>
<view v-else class="center" style="width: 100%; height: 400rpx; background-color: #f5f5f5;">
<text style="color: #999;">{{ loading ? $t('common.loading') : '暂无详情图片' }}</text>
</view>
</view>
<view class="center" @click="toEntry()"
@ -24,22 +28,67 @@
{{ $t('reserveDetails.reserve') }}
</view>
</view>
</template>
<script>
import Config from '@/modules/Config.js'
export default {
data() {
return {
title: ""
id: '',
title: '',
detailImage: '',
loading: true
}
},
onLoad(options) {
this.title = options.title;
this.id = options.id || ''
this.title = decodeURIComponent(options.title || '')
if (this.id) {
this.loadHotServiceDetail()
} else {
this.loading = false
}
},
methods: {
/**
* 加载热门服务详情
*/
async loadHotServiceDetail() {
try {
uni.request({
url: Config.API_BASE_URL + '/api/v1/home/hot-services',
method: 'GET',
header: {
'Accept-Language': this.$i18n?.locale || 'zh'
},
success: (res) => {
if (res.statusCode === 200 && res.data.code === 0) {
const service = res.data.data.find(item => item.id == this.id)
if (service && service.detail_image) {
this.detailImage = Config.getImageUrl(service.detail_image)
}
}
this.loading = false
},
fail: (error) => {
console.error('加载热门服务详情失败:', error)
this.loading = false
}
})
} catch (error) {
console.error('加载热门服务详情失败:', error)
this.loading = false
}
},
handleImageError(e) {
console.error('详情图片加载失败:', e)
this.detailImage = ''
},
back() {
uni.navigateBack({
delta: 1,
@ -48,12 +97,11 @@
});
},
toEntry(id) {
toEntry() {
uni.navigateTo({
url: '/pages/appointment/info-entry-page?id=' + id
url: '/pages/appointment/info-entry-page?id=' + this.id + '&title=' + encodeURIComponent(this.title)
});
}
}
}
</script>
@ -66,4 +114,4 @@
align-items: center;
background-color: #F7F7F7;
}
</style>
</style>

View File

@ -1,31 +1,26 @@
<template>
<view class="login-container">
<!-- Header -->
<view class="header">
<view class="status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="header-content">
<navigator open-type="navigateBack" class="back-button">
<text class="back-icon"></text>
</navigator>
<text class="header-title">{{ $t('login.title') }}</text>
<view class="header-placeholder"></view>
<view class="header-row">
<view class="back-button" @click="back">
<image src="/static/ic_back.png" class="back-icon"></image>
</view>
<text class="title">{{ $t('login.title') }}</text>
<view class="back-button" @click="markAllRead">
</view>
</view>
<!-- Logo Section -->
<view class="logo-section">
<view class="logo-box">
<text class="logo-text">LOGO</text>
<image v-if="appLogo" :src="appLogo" class="logo-image" mode="aspectFit"></image>
<text v-else class="logo-text">LOGO</text>
</view>
</view>
<!-- Main Content -->
<view class="main-content">
<!-- One-Click Login Button -->
<button
class="login-button"
@click="handleWechatLogin"
:loading="isLoading">
<button class="login-button" @click="handleWechatLogin" :loading="isLoading">
{{ $t('login.oneClickLogin') }}
</button>
@ -80,194 +75,212 @@
</template>
<script>
import { AppServer } from '../../modules/api/AppServer';
import { saveAuthData } from '@/utils/auth.js';
import {
AppServer
} from '../../modules/api/AppServer';
import {
saveAuthData
} from '@/utils/auth.js';
import Config from '@/modules/Config.js';
export default {
data() {
return {
isLoading: false,
agreeToTerms: false,
showAgreementModal: false,
showPrivacyModal: false,
userAgreementContent: this.getUserAgreementContent(),
privacyPolicyContent: this.getPrivacyPolicyContent(),
statusBarHeight: 0
}
},
methods: {
// Handle WeChat login
async handleWechatLogin() {
//
if (this.isLoading) {
return;
}
//
if (!this.agreeToTerms) {
uni.showToast({
title: this.$t('login.mustAgreeToTerms'),
icon: 'none'
})
return
}
this.isLoading = true
try {
// code
const loginRes = await this.getWechatLoginCode();
console.log('微信登录 code:', loginRes);
if (!loginRes || !loginRes.code) {
uni.showToast({
title: this.$t('login.wechatLoginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
//
const appserver = new AppServer();
const data = await appserver.WechatLogin(loginRes.code);
console.log('登录接口返回:', data);
// API
if (!data) {
console.error('登录接口无响应');
uni.showToast({
title: this.$t('login.loginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
// 0
if (data.code !== 0) {
console.error('登录失败code:', data.code, 'message:', data.message);
uni.showToast({
title: data.message || this.$t('login.loginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
// token
if (!data.data || !data.data.token) {
console.error('Token 数据缺失:', data.data);
uni.showToast({
title: this.$t('login.loginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
console.log('登录成功,保存认证信息');
//
const token = "Bearer " + data.data.token
saveAuthData(token, data.data.refreshToken, data.data.user)
//
uni.showToast({
title: this.$t('login.loginSuccess') || '登录成功',
icon: 'success',
duration: 1500
});
//
setTimeout(() => {
this.redirectToHome();
}, 500);
} catch (error) {
console.error('登录异常:', error);
console.error('错误堆栈:', error.stack);
uni.showToast({
title: this.$t('login.loginError') + ': ' + (error.message || ''),
icon: 'none',
duration: 3000
})
} finally {
this.isLoading = false
export default {
data() {
return {
isLoading: false,
agreeToTerms: false,
showAgreementModal: false,
showPrivacyModal: false,
userAgreementContent: this.getUserAgreementContent(),
privacyPolicyContent: this.getPrivacyPolicyContent(),
statusBarHeight: 0,
appLogo: ''
}
},
onLoad() {
// Get status bar height
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
//
this.loadConfig()
},
methods: {
back() {
uni.navigateBack({
delta: 1
});
},
// Handle WeChat login
async handleWechatLogin() {
//
if (this.isLoading) {
return;
}
// codePromise
getWechatLoginCode() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: (res) => {
resolve(res);
//
if (!this.agreeToTerms) {
uni.showToast({
title: this.$t('login.mustAgreeToTerms'),
icon: 'none'
})
return
}
this.isLoading = true
try {
// code
const loginRes = await this.getWechatLoginCode();
console.log('微信登录 code:', loginRes);
if (!loginRes || !loginRes.code) {
uni.showToast({
title: this.$t('login.wechatLoginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
//
const appserver = new AppServer();
const data = await appserver.WechatLogin(loginRes.code);
console.log('登录接口返回:', data);
// API
if (!data) {
console.error('登录接口无响应');
uni.showToast({
title: this.$t('login.loginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
// 0
if (data.code !== 0) {
console.error('登录失败code:', data.code, 'message:', data.message);
uni.showToast({
title: data.message || this.$t('login.loginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
// token
if (!data.data || !data.data.token) {
console.error('Token 数据缺失:', data.data);
uni.showToast({
title: this.$t('login.loginFailed'),
icon: 'none'
})
this.isLoading = false
return
}
console.log('登录成功,保存认证信息');
//
const token = "Bearer " + data.data.token
saveAuthData(token, data.data.refreshToken, data.data.user)
//
uni.showToast({
title: this.$t('login.loginSuccess') || '登录成功',
icon: 'success',
duration: 1500
});
//
setTimeout(() => {
this.redirectToHome();
}, 500);
} catch (error) {
console.error('登录异常:', error);
console.error('错误堆栈:', error.stack);
uni.showToast({
title: this.$t('login.loginError') + ': ' + (error.message || ''),
icon: 'none',
duration: 3000
})
} finally {
this.isLoading = false
}
},
// codePromise
getWechatLoginCode() {
return new Promise((resolve, reject) => {
uni.login({
provider: 'weixin',
success: (res) => {
resolve(res);
},
fail: (err) => {
console.error('uni.login 失败:', err);
reject(err);
}
});
});
},
//
redirectToHome() {
//
const app = getApp();
if (app.globalData) {
app.globalData.shouldRefresh = true;
app.globalData.loginTime = Date.now();
}
// tabBar
uni.switchTab({
url: '/pages/index/index',
success: () => {
console.log('登录成功,已跳转到首页');
},
fail: (err) => {
console.error('uni.login 失败:', err);
reject(err);
console.error('跳转首页失败:', err);
// switchTab 使 reLaunch
uni.reLaunch({
url: '/pages/index/index'
});
}
});
});
},
},
//
redirectToHome() {
//
const app = getApp();
if (app.globalData) {
app.globalData.shouldRefresh = true;
app.globalData.loginTime = Date.now();
}
// Handle agreement checkbox change
handleAgreementChange(e) {
this.agreeToTerms = e.detail.value.includes('agree')
},
// tabBar
uni.switchTab({
url: '/pages/index/index',
success: () => {
console.log('登录成功,已跳转到首页');
},
fail: (err) => {
console.error('跳转首页失败:', err);
// switchTab 使 reLaunch
uni.reLaunch({
url: '/pages/index/index'
});
}
});
},
// Show user agreement
showUserAgreement() {
this.showAgreementModal = true
},
// Handle agreement checkbox change
handleAgreementChange(e) {
this.agreeToTerms = e.detail.value.includes('agree')
},
// Close user agreement
closeUserAgreement() {
this.showAgreementModal = false
},
// Show user agreement
showUserAgreement() {
this.showAgreementModal = true
},
// Show privacy policy
showPrivacyPolicy() {
this.showPrivacyModal = true
},
// Close user agreement
closeUserAgreement() {
this.showAgreementModal = false
},
// Close privacy policy
closePrivacyPolicy() {
this.showPrivacyModal = false
},
// Show privacy policy
showPrivacyPolicy() {
this.showPrivacyModal = true
},
// Close privacy policy
closePrivacyPolicy() {
this.showPrivacyModal = false
},
// Get user agreement content
getUserAgreementContent() {
return this.$t('login.userAgreementContent') || `
// Get user agreement content
getUserAgreementContent() {
return this.$t('login.userAgreementContent') || `
用户协议
欢迎使用本应用本协议规定了您使用本应用的条款和条件
@ -287,11 +300,11 @@ export default {
5. 修改权利
我们保留随时修改本协议的权利
`
},
},
// Get privacy policy content
getPrivacyPolicyContent() {
return this.$t('login.privacyPolicyContent') || `
// Get privacy policy content
getPrivacyPolicyContent() {
return this.$t('login.privacyPolicyContent') || `
隐私政策
我们重视您的隐私本政策说明我们如何收集使用和保护您的信息
@ -311,256 +324,252 @@ export default {
5. 联系我们
如有隐私问题请通过应用内的联系方式与我们联系
`
}
},
onLoad() {
// Get status bar height
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
},
//
async loadConfig() {
try {
const config = await Config.getPublicConfig()
if (config.app_logo) {
this.appLogo = Config.getImageUrl(config.app_logo)
}
} catch (error) {
console.error('加载配置失败:', error)
}
},
},
}
}
</script>
<style lang="scss" scoped>
.login-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
// Header
.header {
background-color: #fff;
border-bottom: 1px solid #eee;
.status-bar {
width: 100%;
background-color: #fff;
.login-container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #f5f5f5;
}
.header-content {
.header-row {
width: 100%;
margin-top: 100rpx;
padding-bottom: 20rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 16px;
}
.back-button {
width: 40px;
height: 40px;
width: 80rpx;
height: 50rpx;
margin-left: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 28px;
color: #333;
width: 48rpx;
height: 48rpx;
}
.header-title {
font-size: 18px;
font-weight: 600;
color: #333;
flex: 1;
text-align: center;
}
.header-placeholder {
width: 40px;
}
}
// Logo Section
.logo-section {
display: flex;
justify-content: center;
padding: 30px 20px 20px;
flex: 0 0 auto;
.logo-box {
width: 140px;
height: 140px;
border: 2px solid #17a2b8;
border-radius: 16px;
// Logo Section
.logo-section {
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
box-shadow: 0 2px 8px rgba(23, 162, 184, 0.1);
padding: 30px 20px 20px;
flex: 0 0 auto;
.logo-text {
font-size: 36px;
color: #17a2b8;
font-weight: 600;
}
}
}
// Main Content
.main-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 0 20px 40px;
}
// Login Button
.login-button {
width: 100%;
height: 50px;
background-color: #17a2b8;
color: #fff;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
border: none;
margin-bottom: 20px;
box-shadow: 0 3px 10px rgba(23, 162, 184, 0.2);
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
line-height: 50px;
padding: 0;
text-align: center;
&:active {
background-color: #138496;
box-shadow: 0 2px 6px rgba(23, 162, 184, 0.15);
}
&::after {
border: none;
}
}
// Agreement Section
.agreement-section {
display: flex;
align-items: flex-start;
checkbox-group {
width: 100%;
}
.agreement-label {
display: flex;
align-items: flex-start;
font-size: 13px;
color: #666;
line-height: 1.8;
checkbox {
margin-right: 8px;
margin-top: 3px;
flex-shrink: 0;
transform: scale(0.9);
}
.agreement-text {
flex: 1;
padding-top: 1px;
}
.link-text {
color: #17a2b8;
text-decoration: underline;
font-weight: 500;
}
}
}
// Modal Overlay
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
padding: 20px;
}
// Modal Styles
.modal-content {
width: 100%;
max-width: 480px;
max-height: 85vh;
background-color: #fff;
border-radius: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 20px;
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
border-bottom: none;
.modal-title {
font-size: 18px;
font-weight: 700;
color: #fff;
}
.modal-close {
font-size: 32px;
color: #fff;
width: 36px;
height: 36px;
.logo-box {
width: 140px;
height: 140px;
border: 2px solid #17a2b8;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.9;
}
}
background-color: #fff;
box-shadow: 0 2px 8px rgba(23, 162, 184, 0.1);
overflow: hidden;
.modal-body {
flex: 1;
padding: 20px;
overflow-y: auto;
.logo-image {
width: 100%;
height: 100%;
}
.modal-text {
font-size: 14px;
color: #555;
line-height: 1.8;
white-space: pre-wrap;
word-break: break-word;
}
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: flex-end;
background-color: #fafafa;
.modal-button {
padding: 10px 28px;
background-color: #17a2b8;
color: #fff;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
border: none;
transition: all 0.3s ease;
&:active {
background-color: #138496;
.logo-text {
font-size: 36px;
color: #17a2b8;
font-weight: 600;
}
}
}
}
</style>
// Main Content
.main-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 0 20px 40px;
}
// Login Button
.login-button {
width: 100%;
height: 50px;
background-color: #17a2b8;
color: #fff;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
border: none;
margin-bottom: 20px;
box-shadow: 0 3px 10px rgba(23, 162, 184, 0.2);
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
line-height: 50px;
padding: 0;
text-align: center;
&:active {
background-color: #138496;
box-shadow: 0 2px 6px rgba(23, 162, 184, 0.15);
}
&::after {
border: none;
}
}
// Agreement Section
.agreement-section {
display: flex;
align-items: flex-start;
checkbox-group {
width: 100%;
}
.agreement-label {
display: flex;
align-items: flex-start;
font-size: 13px;
color: #666;
line-height: 1.8;
checkbox {
margin-right: 8px;
margin-top: 3px;
flex-shrink: 0;
transform: scale(0.9);
}
.agreement-text {
flex: 1;
padding-top: 1px;
}
.link-text {
color: #17a2b8;
text-decoration: underline;
font-weight: 500;
}
}
}
// Modal Overlay
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 999;
padding: 20px;
}
// Modal Styles
.modal-content {
width: 100%;
max-width: 480px;
max-height: 85vh;
background-color: #fff;
border-radius: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 20px;
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
border-bottom: none;
.modal-title {
font-size: 18px;
font-weight: 700;
color: #fff;
}
.modal-close {
font-size: 32px;
color: #fff;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.9;
}
}
.modal-body {
flex: 1;
padding: 20px;
overflow-y: auto;
.modal-text {
font-size: 14px;
color: #555;
line-height: 1.8;
white-space: pre-wrap;
word-break: break-word;
}
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #f0f0f0;
display: flex;
justify-content: flex-end;
background-color: #fafafa;
.modal-button {
padding: 10px 28px;
background-color: #17a2b8;
color: #fff;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
border: none;
transition: all 0.3s ease;
&:active {
background-color: #138496;
}
}
}
}
</style>

View File

@ -301,20 +301,29 @@
},
changeLanguage(langCode) {
//
if (langCode === this.$i18n.locale) {
this.showLanguagePicker = false
return
}
this.$i18n.locale = langCode
uni.setStorageSync('language', langCode)
this.showLanguagePicker = false
// TabBar
this.$nextTick(() => {
updateTabBarI18n(this)
})
//
uni.showToast({
title: this.$t('common.success'),
icon: 'success'
icon: 'success',
duration: 1000
})
//
setTimeout(() => {
uni.reLaunch({
url: '/pages/index/index'
})
}, 1000)
},
/**

View File

@ -214,7 +214,7 @@
}
.title {
font-size: 30rpx;
font-size: 36rpx;
font-weight: 500;
color: #333;
}

BIN
static/arrow_down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

View File

@ -1 +1 @@
{"version":3,"file":"assets.js","sources":["static/arrow_right.png","../../static/default_avatar.png","static/ic_general.png","static/ic_notice.png","static/arrow_right2.png","static/customer_service.png","static/contact_us.png","static/invite_new.png","static/ic_other.png","static/ic_inter.png","static/ic_about.png","static/ic_agreement2.png","static/ic_agreement.png","static/ic_exit.png","static/ic_back.png","../../static/contact_qr.png","static/new_bg1.png","static/new_user.png","static/red_box.png","static/ic_wallet.png","static/arrow_right3.png","static/new_bg2.png","static/approved.png","static/ic_colse.png"],"sourcesContent":["export default \"__VITE_ASSET__be7f263e__\"","export default \"/static/default_avatar.png\"","export default \"__VITE_ASSET__8777109f__\"","export default \"__VITE_ASSET__9a754ff3__\"","export default \"__VITE_ASSET__eb2ce0a4__\"","export default \"__VITE_ASSET__f466dfff__\"","export default \"__VITE_ASSET__ec996f6a__\"","export default \"__VITE_ASSET__38f707fb__\"","export default \"__VITE_ASSET__2a39e08b__\"","export default \"__VITE_ASSET__ca09014a__\"","export default \"__VITE_ASSET__2f0fd9e8__\"","export default \"__VITE_ASSET__7a5289f6__\"","export default \"__VITE_ASSET__3edbec5f__\"","export default \"__VITE_ASSET__30832829__\"","export default \"__VITE_ASSET__f2f62cd2__\"","export default \"/static/contact_qr.png\"","export default \"__VITE_ASSET__f0922286__\"","export default \"__VITE_ASSET__2553be8d__\"","export default \"__VITE_ASSET__7e4678c9__\"","export default \"__VITE_ASSET__a73107e8__\"","export default \"__VITE_ASSET__2312fab2__\"","export default \"__VITE_ASSET__b4e916f4__\"","export default \"__VITE_ASSET__44c4db1e__\"","export default \"__VITE_ASSET__21e357d8__\""],"names":[],"mappings":";AAAA,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,aAAA;ACAf,MAAe,cAAA;ACAf,MAAe,cAAA;ACAf,MAAe,cAAA;ACAf,MAAe,cAAA;ACAf,MAAe,aAAA;ACAf,MAAe,eAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;"}
{"version":3,"file":"assets.js","sources":["static/ic_back.png","static/arrow_right.png","../../../static/default_avatar.png","static/ic_general.png","static/ic_notice.png","static/arrow_right2.png","static/customer_service.png","static/contact_us.png","static/invite_new.png","static/ic_other.png","static/ic_inter.png","static/ic_about.png","static/ic_agreement2.png","static/ic_agreement.png","static/ic_exit.png","static/arrow_down.png","../../../static/contact_qr.png","static/new_bg1.png","static/new_user.png","static/red_box.png","static/ic_wallet.png","static/arrow_right3.png","static/new_bg2.png","static/approved.png","static/ic_colse.png"],"sourcesContent":["export default \"__VITE_ASSET__f2f62cd2__\"","export default \"__VITE_ASSET__be7f263e__\"","export default \"/static/default_avatar.png\"","export default \"__VITE_ASSET__8777109f__\"","export default \"__VITE_ASSET__9a754ff3__\"","export default \"__VITE_ASSET__eb2ce0a4__\"","export default \"__VITE_ASSET__f466dfff__\"","export default \"__VITE_ASSET__ec996f6a__\"","export default \"__VITE_ASSET__38f707fb__\"","export default \"__VITE_ASSET__2a39e08b__\"","export default \"__VITE_ASSET__ca09014a__\"","export default \"__VITE_ASSET__2f0fd9e8__\"","export default \"__VITE_ASSET__7a5289f6__\"","export default \"__VITE_ASSET__3edbec5f__\"","export default \"__VITE_ASSET__30832829__\"","export default \"__VITE_ASSET__89bc1370__\"","export default \"/static/contact_qr.png\"","export default \"__VITE_ASSET__f0922286__\"","export default \"__VITE_ASSET__2553be8d__\"","export default \"__VITE_ASSET__7e4678c9__\"","export default \"__VITE_ASSET__a73107e8__\"","export default \"__VITE_ASSET__2312fab2__\"","export default \"__VITE_ASSET__b4e916f4__\"","export default \"__VITE_ASSET__44c4db1e__\"","export default \"__VITE_ASSET__21e357d8__\""],"names":[],"mappings":";AAAA,MAAe,eAAA;ACAf,MAAe,aAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,aAAA;ACAf,MAAe,cAAA;ACAf,MAAe,cAAA;ACAf,MAAe,cAAA;ACAf,MAAe,cAAA;ACAf,MAAe,eAAA;ACAf,MAAe,eAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;ACAf,MAAe,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"u-overlay.js","sources":["node_modules/uview-plus/components/u-overlay/u-overlay.vue","../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/Rjovd29yay9hcHBvaW50bWVudF9zeXN0ZW0vbm9kZV9tb2R1bGVzL3V2aWV3LXBsdXMvY29tcG9uZW50cy91LW92ZXJsYXkvdS1vdmVybGF5LnZ1ZQ"],"sourcesContent":["<template>\n\t<u-transition\n\t :show=\"show\"\n\t custom-class=\"u-overlay\"\n\t :duration=\"duration\"\n\t :custom-style=\"overlayStyle\"\n\t @click=\"clickHandler\"\n\t\t@touchmove.stop.prevent=\"noop\"\n\t>\n\t\t<slot />\n\t</u-transition>\n</template>\n\n<script>\n\timport { props } from './props';\n\timport { mpMixin } from '../../libs/mixin/mpMixin';\n\timport { mixin } from '../../libs/mixin/mixin';\n\timport { addStyle, deepMerge } from '../../libs/function/index';\n\t/**\n\t * overlay 遮罩\n\t * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景\n\t * @tutorial https://ijry.github.io/uview-plus/components/overlay.html\n\t * @property {Boolean}\t\t\tshow\t\t是否显示遮罩默认 false \n\t * @property {String | Number}\tzIndex\t\tzIndex 层级(默认 10070 \n\t * @property {String | Number}\tduration\t动画时长单位毫秒默认 300 \n\t * @property {String | Number}\topacity\t\t不透明度值当做rgba的第四个参数 (默认 0.5 \n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @event {Function} click 点击遮罩发送事件\n\t * @example <u-overlay :show=\"show\" @click=\"show = false\"></u-overlay>\n\t */\n\texport default {\n\t\tname: \"u-overlay\",\n\t\tmixins: [mpMixin, mixin,props],\n\t\tcomputed: {\n\t\t\toverlayStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tposition: 'fixed',\n\t\t\t\t\ttop: 0,\n\t\t\t\t\tleft: 0,\n\t\t\t\t\tright: 0,\n\t\t\t\t\tzIndex: this.zIndex,\n\t\t\t\t\tbottom: 0,\n\t\t\t\t\t'background-color': `rgba(0, 0, 0, ${this.opacity})`\n\t\t\t\t}\n\t\t\t\treturn deepMerge(style, addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\temits: [\"click\"],\n\t\tmethods: {\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click')\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n $u-overlay-top:0 !default;\n $u-overlay-left:0 !default;\n $u-overlay-width:100% !default;\n $u-overlay-height:100% !default;\n $u-overlay-background-color:rgba(0, 0, 0, .7) !default;\n\t.u-overlay {\n\t\tposition: fixed;\n\t\ttop:$u-overlay-top;\n\t\tleft:$u-overlay-left;\n\t\twidth: $u-overlay-width;\n\t\theight:$u-overlay-height;\n\t\tbackground-color:$u-overlay-background-color;\n\t}\n</style>\n","import Component from 'F:/work/appointment_system/node_modules/uview-plus/components/u-overlay/u-overlay.vue'\nwx.createComponent(Component)"],"names":["mpMixin","mixin","props","deepMerge","addStyle"],"mappings":";;AA8BC,MAAK,YAAU;AAAA,EACd,MAAM;AAAA,EACN,QAAQ,CAACA,cAAAA,SAASC,cAAK,OAACC,qBAAK;AAAA,EAC7B,UAAU;AAAA,IACT,eAAe;AACd,YAAM,QAAQ;AAAA,QACb,UAAU;AAAA,QACV,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ,KAAK;AAAA,QACb,QAAQ;AAAA,QACR,oBAAoB,iBAAiB,KAAK,OAAO;AAAA,MAClD;AACA,aAAOC,cAAS,UAAC,OAAOC,cAAQ,SAAC,KAAK,WAAW,CAAC;AAAA,IACnD;AAAA,EACA;AAAA,EACD,OAAO,CAAC,OAAO;AAAA,EACf,SAAS;AAAA,IACR,eAAe;AACd,WAAK,MAAM,OAAO;AAAA,IACnB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;ACpDD,GAAG,gBAAgB,SAAS;","x_google_ignoreList":[0]}
{"version":3,"file":"u-overlay.js","sources":["node_modules/uview-plus/components/u-overlay/u-overlay.vue","../../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/RjovZ2l0Q29kZS91bmlhcHAvYXBwb2ludG1lbnRfc3lzdGVtL25vZGVfbW9kdWxlcy91dmlldy1wbHVzL2NvbXBvbmVudHMvdS1vdmVybGF5L3Utb3ZlcmxheS52dWU"],"sourcesContent":["<template>\n\t<u-transition\n\t :show=\"show\"\n\t custom-class=\"u-overlay\"\n\t :duration=\"duration\"\n\t :custom-style=\"overlayStyle\"\n\t @click=\"clickHandler\"\n\t\t@touchmove.stop.prevent=\"noop\"\n\t>\n\t\t<slot />\n\t</u-transition>\n</template>\n\n<script>\n\timport { props } from './props';\n\timport { mpMixin } from '../../libs/mixin/mpMixin';\n\timport { mixin } from '../../libs/mixin/mixin';\n\timport { addStyle, deepMerge } from '../../libs/function/index';\n\t/**\n\t * overlay 遮罩\n\t * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景\n\t * @tutorial https://ijry.github.io/uview-plus/components/overlay.html\n\t * @property {Boolean}\t\t\tshow\t\t是否显示遮罩默认 false \n\t * @property {String | Number}\tzIndex\t\tzIndex 层级(默认 10070 \n\t * @property {String | Number}\tduration\t动画时长单位毫秒默认 300 \n\t * @property {String | Number}\topacity\t\t不透明度值当做rgba的第四个参数 (默认 0.5 \n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @event {Function} click 点击遮罩发送事件\n\t * @example <u-overlay :show=\"show\" @click=\"show = false\"></u-overlay>\n\t */\n\texport default {\n\t\tname: \"u-overlay\",\n\t\tmixins: [mpMixin, mixin,props],\n\t\tcomputed: {\n\t\t\toverlayStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tposition: 'fixed',\n\t\t\t\t\ttop: 0,\n\t\t\t\t\tleft: 0,\n\t\t\t\t\tright: 0,\n\t\t\t\t\tzIndex: this.zIndex,\n\t\t\t\t\tbottom: 0,\n\t\t\t\t\t'background-color': `rgba(0, 0, 0, ${this.opacity})`\n\t\t\t\t}\n\t\t\t\treturn deepMerge(style, addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\temits: [\"click\"],\n\t\tmethods: {\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click')\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n $u-overlay-top:0 !default;\n $u-overlay-left:0 !default;\n $u-overlay-width:100% !default;\n $u-overlay-height:100% !default;\n $u-overlay-background-color:rgba(0, 0, 0, .7) !default;\n\t.u-overlay {\n\t\tposition: fixed;\n\t\ttop:$u-overlay-top;\n\t\tleft:$u-overlay-left;\n\t\twidth: $u-overlay-width;\n\t\theight:$u-overlay-height;\n\t\tbackground-color:$u-overlay-background-color;\n\t}\n</style>\n","import Component from 'F:/gitCode/uniapp/appointment_system/node_modules/uview-plus/components/u-overlay/u-overlay.vue'\nwx.createComponent(Component)"],"names":["mpMixin","mixin","props","deepMerge","addStyle"],"mappings":";;AA8BC,MAAK,YAAU;AAAA,EACd,MAAM;AAAA,EACN,QAAQ,CAACA,cAAAA,SAASC,cAAK,OAACC,qBAAK;AAAA,EAC7B,UAAU;AAAA,IACT,eAAe;AACd,YAAM,QAAQ;AAAA,QACb,UAAU;AAAA,QACV,KAAK;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,QACP,QAAQ,KAAK;AAAA,QACb,QAAQ;AAAA,QACR,oBAAoB,iBAAiB,KAAK,OAAO;AAAA,MAClD;AACA,aAAOC,cAAS,UAAC,OAAOC,cAAQ,SAAC,KAAK,WAAW,CAAC;AAAA,IACnD;AAAA,EACA;AAAA,EACD,OAAO,CAAC,OAAO;AAAA,EACf,SAAS;AAAA,IACR,eAAe;AACd,WAAK,MAAM,OAAO;AAAA,IACnB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;ACpDD,GAAG,gBAAgB,SAAS;","x_google_ignoreList":[0]}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"u-safe-bottom.js","sources":["node_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue","../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/Rjovd29yay9hcHBvaW50bWVudF9zeXN0ZW0vbm9kZV9tb2R1bGVzL3V2aWV3LXBsdXMvY29tcG9uZW50cy91LXNhZmUtYm90dG9tL3Utc2FmZS1ib3R0b20udnVl"],"sourcesContent":["<template>\r\n\t<view\r\n\t\tclass=\"u-safe-bottom\"\r\n\t\t:style=\"[style]\"\r\n\t\t:class=\"[!isNvue && 'u-safe-area-inset-bottom']\"\r\n\t>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { props } from \"./props.js\";\r\n\timport { mpMixin } from '../../libs/mixin/mpMixin';\r\n\timport { mixin } from '../../libs/mixin/mixin';\r\n\timport { addStyle, deepMerge, addUnit, getWindowInfo } from '../../libs/function/index';\r\n\t/**\r\n\t * SafeBottom 底部安全区\r\n\t * @description 这个适配主要是针对IPhone X等一些底部带指示条的机型指示条的操作区域与页面底部存在重合容易导致用户误操作因此我们需要针对这些机型进行底部安全区适配。\r\n\t * @tutorial https://ijry.github.io/uview-plus/components/safeAreaInset.html\r\n\t * @property {type}\t\tprop_name\r\n\t * @property {Object}\tcustomStyle\t定义需要用到的外部样式\r\n\t *\r\n\t * @event {Function()}\r\n\t * @example <u-status-bar></u-status-bar>\r\n\t */\r\n\texport default {\r\n\t\tname: \"u-safe-bottom\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tsafeAreaBottomHeight: 0,\r\n\t\t\t\tisNvue: false,\r\n\t\t\t};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tstyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// #ifdef APP-NVUE || MP-TOUTIAO || MP-WEIXIN\r\n\t\t\t\t// nvue下高度使用js计算填充\r\n\t\t\t\tstyle.height = addUnit(getWindowInfo().safeAreaInsets.bottom, 'px');\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn deepMerge(style, addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\t// #ifdef APP-NVUE || MP-TOUTIAO || MP-WEIXIN\r\n\t\t\t// 标识为是否nvue\r\n\t\t\tthis.isNvue = true;\r\n\t\t\t// #endif\r\n\t\t},\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.u-safe-bottom {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\twidth: 100%;\r\n\t\t/* #endif */\r\n\t}\r\n</style>\r\n","import Component from 'F:/work/appointment_system/node_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue'\nwx.createComponent(Component)"],"names":["mpMixin","mixin","props","addUnit","getWindowInfo","deepMerge","addStyle"],"mappings":";;AAwBC,MAAK,YAAU;AAAA,EACd,MAAM;AAAA,EACN,QAAQ,CAACA,cAAAA,SAASC,cAAK,OAAEC,qBAAK;AAAA,EAC9B,OAAO;AACN,WAAO;AAAA,MACN,sBAAsB;AAAA,MACtB,QAAQ;AAAA;EAET;AAAA,EACD,UAAU;AAAA,IACT,QAAQ;AACP,YAAM,QAAQ,CAAA;AAGd,YAAM,SAASC,sBAAQC,cAAAA,cAAe,EAAC,eAAe,QAAQ,IAAI;AAElE,aAAOC,cAAAA,UAAU,OAAOC,cAAAA,SAAS,KAAK,WAAW,CAAC;AAAA,IAClD;AAAA,EACD;AAAA,EACD,UAAU;AAGT,SAAK,SAAS;AAAA,EAEd;;;;;;;;;AC/CH,GAAG,gBAAgB,SAAS;","x_google_ignoreList":[0]}
{"version":3,"file":"u-safe-bottom.js","sources":["node_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue","../../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/RjovZ2l0Q29kZS91bmlhcHAvYXBwb2ludG1lbnRfc3lzdGVtL25vZGVfbW9kdWxlcy91dmlldy1wbHVzL2NvbXBvbmVudHMvdS1zYWZlLWJvdHRvbS91LXNhZmUtYm90dG9tLnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view\r\n\t\tclass=\"u-safe-bottom\"\r\n\t\t:style=\"[style]\"\r\n\t\t:class=\"[!isNvue && 'u-safe-area-inset-bottom']\"\r\n\t>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { props } from \"./props.js\";\r\n\timport { mpMixin } from '../../libs/mixin/mpMixin';\r\n\timport { mixin } from '../../libs/mixin/mixin';\r\n\timport { addStyle, deepMerge, addUnit, getWindowInfo } from '../../libs/function/index';\r\n\t/**\r\n\t * SafeBottom 底部安全区\r\n\t * @description 这个适配主要是针对IPhone X等一些底部带指示条的机型指示条的操作区域与页面底部存在重合容易导致用户误操作因此我们需要针对这些机型进行底部安全区适配。\r\n\t * @tutorial https://ijry.github.io/uview-plus/components/safeAreaInset.html\r\n\t * @property {type}\t\tprop_name\r\n\t * @property {Object}\tcustomStyle\t定义需要用到的外部样式\r\n\t *\r\n\t * @event {Function()}\r\n\t * @example <u-status-bar></u-status-bar>\r\n\t */\r\n\texport default {\r\n\t\tname: \"u-safe-bottom\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tsafeAreaBottomHeight: 0,\r\n\t\t\t\tisNvue: false,\r\n\t\t\t};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tstyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// #ifdef APP-NVUE || MP-TOUTIAO || MP-WEIXIN\r\n\t\t\t\t// nvue下高度使用js计算填充\r\n\t\t\t\tstyle.height = addUnit(getWindowInfo().safeAreaInsets.bottom, 'px');\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn deepMerge(style, addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\t// #ifdef APP-NVUE || MP-TOUTIAO || MP-WEIXIN\r\n\t\t\t// 标识为是否nvue\r\n\t\t\tthis.isNvue = true;\r\n\t\t\t// #endif\r\n\t\t},\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.u-safe-bottom {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\twidth: 100%;\r\n\t\t/* #endif */\r\n\t}\r\n</style>\r\n","import Component from 'F:/gitCode/uniapp/appointment_system/node_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue'\nwx.createComponent(Component)"],"names":["mpMixin","mixin","props","addUnit","getWindowInfo","deepMerge","addStyle"],"mappings":";;AAwBC,MAAK,YAAU;AAAA,EACd,MAAM;AAAA,EACN,QAAQ,CAACA,cAAAA,SAASC,cAAK,OAAEC,qBAAK;AAAA,EAC9B,OAAO;AACN,WAAO;AAAA,MACN,sBAAsB;AAAA,MACtB,QAAQ;AAAA;EAET;AAAA,EACD,UAAU;AAAA,IACT,QAAQ;AACP,YAAM,QAAQ,CAAA;AAGd,YAAM,SAASC,sBAAQC,cAAAA,cAAe,EAAC,eAAe,QAAQ,IAAI;AAElE,aAAOC,cAAAA,UAAU,OAAOC,cAAAA,SAAS,KAAK,WAAW,CAAC;AAAA,IAClD;AAAA,EACD;AAAA,EACD,UAAU;AAGT,SAAK,SAAS;AAAA,EAEd;;;;;;;;;AC/CH,GAAG,gBAAgB,SAAS;","x_google_ignoreList":[0]}

View File

@ -1 +1 @@
{"version":3,"file":"u-status-bar.js","sources":["node_modules/uview-plus/components/u-status-bar/u-status-bar.vue","../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/Rjovd29yay9hcHBvaW50bWVudF9zeXN0ZW0vbm9kZV9tb2R1bGVzL3V2aWV3LXBsdXMvY29tcG9uZW50cy91LXN0YXR1cy1iYXIvdS1zdGF0dXMtYmFyLnZ1ZQ"],"sourcesContent":["<template>\n\t<view\n\t :style=\"[style]\"\n\t class=\"u-status-bar\"\n\t\t:class=\"[isH5 && 'u-safe-area-inset-top']\"\n\t>\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\n\timport { props } from './props';\n\timport { mpMixin } from '../../libs/mixin/mpMixin';\n\timport { mixin } from '../../libs/mixin/mixin';\n\timport { addUnit, addStyle, deepMerge, getWindowInfo } from '../../libs/function/index';\n\t/**\n\t * StatbusBar 状态栏占位\n\t * @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。\n\t * @tutorial https://uview-plus.jiangruyi.com/components/statusBar.html\n\t * @property {String}\t\t\tbgColor\t\t\t背景色 (默认 'transparent' )\n\t * @property {String | Object}\tcustomStyle\t\t自定义样式 \n\t * @example <u-status-bar></u-status-bar>\n\t */\n\texport default {\n\t\tname: 'u-status-bar',\n\t\tmixins: [mpMixin, mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisH5: false\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\t// #ifdef H5\n\t\t\tthis.isH5 = true\n\t\t\t// #endif\n\t\t},\n\t\temits: ['update:height'],\n\t\tcomputed: {\n\t\t\tstyle() {\n\t\t\t\tconst style = {}\n\t\t\t\t// 状态栏高度由于某些安卓和微信开发工具无法识别css的顶部状态栏变量所以使用js获取的方式\n\t\t\t\tlet sheight = getWindowInfo().statusBarHeight\n\t\t\t\tthis.$emit('update:height', sheight)\n\t\t\t\tif (sheight == 0) {\n\t\t\t\t\tthis.isH5 = true\n\t\t\t\t} else {\n\t\t\t\t\tstyle.height = addUnit(sheight, 'px')\n\t\t\t\t}\n\t\t\t\tstyle.backgroundColor = this.bgColor\n\t\t\t\treturn deepMerge(style, addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t.u-status-bar {\n\t\t// nvue会默认100%如果nvue下显式写100%的话会导致宽度不为100%而异常\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: 100%;\n\t\t/* #endif */\n\t}\n</style>\n","import Component from 'F:/work/appointment_system/node_modules/uview-plus/components/u-status-bar/u-status-bar.vue'\nwx.createComponent(Component)"],"names":["mpMixin","mixin","props","getWindowInfo","addUnit","deepMerge","addStyle"],"mappings":";;AAuBC,MAAK,YAAU;AAAA,EACd,MAAM;AAAA,EACN,QAAQ,CAACA,cAAAA,SAASC,cAAK,OAAEC,qBAAK;AAAA,EAC9B,OAAO;AACN,WAAO;AAAA,MACN,MAAM;AAAA,IACP;AAAA,EACA;AAAA,EACD,UAAU;AAAA,EAIT;AAAA,EACD,OAAO,CAAC,eAAe;AAAA,EACvB,UAAU;AAAA,IACT,QAAQ;AACP,YAAM,QAAQ,CAAC;AAEf,UAAI,UAAUC,cAAa,cAAA,EAAG;AAC9B,WAAK,MAAM,iBAAiB,OAAO;AACnC,UAAI,WAAW,GAAG;AACjB,aAAK,OAAO;AAAA,aACN;AACN,cAAM,SAASC,sBAAQ,SAAS,IAAI;AAAA,MACrC;AACA,YAAM,kBAAkB,KAAK;AAC7B,aAAOC,cAAS,UAAC,OAAOC,cAAQ,SAAC,KAAK,WAAW,CAAC;AAAA,IACnD;AAAA,EACA;AACF;;;;;;;;ACnDD,GAAG,gBAAgB,SAAS;","x_google_ignoreList":[0]}
{"version":3,"file":"u-status-bar.js","sources":["node_modules/uview-plus/components/u-status-bar/u-status-bar.vue","../../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniComponent:/RjovZ2l0Q29kZS91bmlhcHAvYXBwb2ludG1lbnRfc3lzdGVtL25vZGVfbW9kdWxlcy91dmlldy1wbHVzL2NvbXBvbmVudHMvdS1zdGF0dXMtYmFyL3Utc3RhdHVzLWJhci52dWU"],"sourcesContent":["<template>\n\t<view\n\t :style=\"[style]\"\n\t class=\"u-status-bar\"\n\t\t:class=\"[isH5 && 'u-safe-area-inset-top']\"\n\t>\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\n\timport { props } from './props';\n\timport { mpMixin } from '../../libs/mixin/mpMixin';\n\timport { mixin } from '../../libs/mixin/mixin';\n\timport { addUnit, addStyle, deepMerge, getWindowInfo } from '../../libs/function/index';\n\t/**\n\t * StatbusBar 状态栏占位\n\t * @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。\n\t * @tutorial https://uview-plus.jiangruyi.com/components/statusBar.html\n\t * @property {String}\t\t\tbgColor\t\t\t背景色 (默认 'transparent' )\n\t * @property {String | Object}\tcustomStyle\t\t自定义样式 \n\t * @example <u-status-bar></u-status-bar>\n\t */\n\texport default {\n\t\tname: 'u-status-bar',\n\t\tmixins: [mpMixin, mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisH5: false\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\t// #ifdef H5\n\t\t\tthis.isH5 = true\n\t\t\t// #endif\n\t\t},\n\t\temits: ['update:height'],\n\t\tcomputed: {\n\t\t\tstyle() {\n\t\t\t\tconst style = {}\n\t\t\t\t// 状态栏高度由于某些安卓和微信开发工具无法识别css的顶部状态栏变量所以使用js获取的方式\n\t\t\t\tlet sheight = getWindowInfo().statusBarHeight\n\t\t\t\tthis.$emit('update:height', sheight)\n\t\t\t\tif (sheight == 0) {\n\t\t\t\t\tthis.isH5 = true\n\t\t\t\t} else {\n\t\t\t\t\tstyle.height = addUnit(sheight, 'px')\n\t\t\t\t}\n\t\t\t\tstyle.backgroundColor = this.bgColor\n\t\t\t\treturn deepMerge(style, addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t.u-status-bar {\n\t\t// nvue会默认100%如果nvue下显式写100%的话会导致宽度不为100%而异常\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: 100%;\n\t\t/* #endif */\n\t}\n</style>\n","import Component from 'F:/gitCode/uniapp/appointment_system/node_modules/uview-plus/components/u-status-bar/u-status-bar.vue'\nwx.createComponent(Component)"],"names":["mpMixin","mixin","props","getWindowInfo","addUnit","deepMerge","addStyle"],"mappings":";;AAuBC,MAAK,YAAU;AAAA,EACd,MAAM;AAAA,EACN,QAAQ,CAACA,cAAAA,SAASC,cAAK,OAAEC,qBAAK;AAAA,EAC9B,OAAO;AACN,WAAO;AAAA,MACN,MAAM;AAAA,IACP;AAAA,EACA;AAAA,EACD,UAAU;AAAA,EAIT;AAAA,EACD,OAAO,CAAC,eAAe;AAAA,EACvB,UAAU;AAAA,IACT,QAAQ;AACP,YAAM,QAAQ,CAAC;AAEf,UAAI,UAAUC,cAAa,cAAA,EAAG;AAC9B,WAAK,MAAM,iBAAiB,OAAO;AACnC,UAAI,WAAW,GAAG;AACjB,aAAK,OAAO;AAAA,aACN;AACN,cAAM,SAASC,sBAAQ,SAAS,IAAI;AAAA,MACrC;AACA,YAAM,kBAAkB,KAAK;AAC7B,aAAOC,cAAS,UAAC,OAAOC,cAAQ,SAAC,KAAK,WAAW,CAAC;AAAA,IACnD;AAAA,EACA;AACF;;;;;;;;ACnDD,GAAG,gBAAgB,SAAS;","x_google_ignoreList":[0]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"file":"contact-us-page.js","sources":["pages/me/contact-us-page.vue","../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvbWUvY29udGFjdC11cy1wYWdlLnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<!-- 自定义导航栏 -->\r\n\t\t<view class=\"navbar\">\r\n\t\t\t<view class=\"nav-back\" @click=\"goBack\">\r\n\t\t\t\t<image src=\"/static/ic_back.png\" class=\"back-icon\" mode=\"aspectFit\"></image>\r\n\t\t\t</view>\r\n\t\t\t<text class=\"nav-title\">{{ $t('me.contactUs') }}</text>\r\n\t\t</view>\r\n\t\t\r\n\t\t<!-- 二维码容器 -->\r\n\t\t<view class=\"qr-container\">\r\n\t\t\t<view class=\"qr-box\">\r\n\t\t\t\t<image src=\"/static/contact_qr.png\" class=\"qr-image\" mode=\"aspectFit\"></image>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\texport default {\r\n\t\tmethods: {\r\n\t\t\tgoBack() {\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.page {\r\n\t\tmin-height: 100vh;\r\n\t\tbackground-color: #F3F4F8;\r\n\t}\r\n\t\r\n\t.navbar {\r\n\t\twidth: 100%;\r\n\t\tpadding-top: 100rpx;\r\n\t\tpadding-bottom: 20rpx;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tposition: relative;\r\n\t\t\r\n\t\t.nav-back {\r\n\t\t\tposition: absolute;\r\n\t\t\tleft: 32rpx;\r\n\t\t\twidth: 80rpx;\r\n\t\t\theight: 50rpx;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\t\r\n\t\t\t.back-icon {\r\n\t\t\t\twidth: 48rpx;\r\n\t\t\t\theight: 48rpx;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t.nav-title {\r\n\t\t\tfont-size: 30rpx;\r\n\t\t\tfont-weight: 500;\r\n\t\t\tcolor: #333;\r\n\t\t}\r\n\t}\r\n\t\r\n\t.qr-container {\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: center;\r\n\t\tpadding: 60rpx 30rpx;\r\n\t\t\r\n\t\t.qr-box {\r\n\t\t\twidth: 600rpx;\r\n\t\t\theight: 600rpx;\r\n\t\t\tbackground-color: #fff;\r\n\t\t\tborder-radius: 20rpx;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tpadding: 40rpx;\r\n\t\t\tbox-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);\r\n\t\t\t\r\n\t\t\t.qr-image {\r\n\t\t\t\twidth: 100%;\r\n\t\t\t\theight: 100%;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n","import MiniProgramPage from 'F:/work/appointment_system/pages/me/contact-us-page.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni"],"mappings":";;;AAoBC,MAAK,YAAU;AAAA,EACd,SAAS;AAAA,IACR,SAAS;AACRA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;ACzBD,GAAG,WAAW,eAAe;"}
{"version":3,"file":"contact-us-page.js","sources":["pages/me/contact-us-page.vue","../../../Software/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvbWUvY29udGFjdC11cy1wYWdlLnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<!-- 自定义导航栏 -->\r\n\t\t<view class=\"navbar\">\r\n\t\t\t<view class=\"nav-back\" @click=\"goBack\">\r\n\t\t\t\t<image src=\"/static/ic_back.png\" class=\"back-icon\" mode=\"aspectFit\"></image>\r\n\t\t\t</view>\r\n\t\t\t<text class=\"nav-title\">{{ $t('me.contactUs') }}</text>\r\n\t\t</view>\r\n\t\t\r\n\t\t<!-- 二维码容器 -->\r\n\t\t<view class=\"qr-container\">\r\n\t\t\t<view class=\"qr-box\">\r\n\t\t\t\t<image src=\"/static/contact_qr.png\" class=\"qr-image\" mode=\"aspectFit\"></image>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\texport default {\r\n\t\tmethods: {\r\n\t\t\tgoBack() {\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.page {\r\n\t\tmin-height: 100vh;\r\n\t\tbackground-color: #F3F4F8;\r\n\t}\r\n\t\r\n\t.navbar {\r\n\t\twidth: 100%;\r\n\t\tpadding-top: 100rpx;\r\n\t\tpadding-bottom: 20rpx;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tposition: relative;\r\n\t\t\r\n\t\t.nav-back {\r\n\t\t\tposition: absolute;\r\n\t\t\tleft: 32rpx;\r\n\t\t\twidth: 80rpx;\r\n\t\t\theight: 50rpx;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\t\r\n\t\t\t.back-icon {\r\n\t\t\t\twidth: 48rpx;\r\n\t\t\t\theight: 48rpx;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t.nav-title {\r\n\t\t\tfont-size: 30rpx;\r\n\t\t\tfont-weight: 500;\r\n\t\t\tcolor: #333;\r\n\t\t}\r\n\t}\r\n\t\r\n\t.qr-container {\r\n\t\tdisplay: flex;\r\n\t\tjustify-content: center;\r\n\t\tpadding: 60rpx 30rpx;\r\n\t\t\r\n\t\t.qr-box {\r\n\t\t\twidth: 600rpx;\r\n\t\t\theight: 600rpx;\r\n\t\t\tbackground-color: #fff;\r\n\t\t\tborder-radius: 20rpx;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tpadding: 40rpx;\r\n\t\t\tbox-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);\r\n\t\t\t\r\n\t\t\t.qr-image {\r\n\t\t\t\twidth: 100%;\r\n\t\t\t\theight: 100%;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n","import MiniProgramPage from 'F:/gitCode/uniapp/appointment_system/pages/me/contact-us-page.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni"],"mappings":";;;AAoBC,MAAK,YAAU;AAAA,EACd,SAAS;AAAA,IACR,SAAS;AACRA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;ACzBD,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,10 @@
"use strict";
const _imports_0$1 = "/static/arrow_right.png";
const _imports_1$3 = "/static/default_avatar.png";
const _imports_2$1 = "/static/ic_general.png";
const _imports_1$2 = "/static/ic_notice.png";
const _imports_4$1 = "/static/arrow_right2.png";
const _imports_0$1 = "/static/ic_back.png";
const _imports_0 = "/static/arrow_right.png";
const _imports_1$4 = "/static/default_avatar.png";
const _imports_2$2 = "/static/ic_general.png";
const _imports_1$3 = "/static/ic_notice.png";
const _imports_2$1 = "/static/arrow_right2.png";
const _imports_5$1 = "/static/customer_service.png";
const _imports_6$1 = "/static/contact_us.png";
const _imports_7$1 = "/static/invite_new.png";
@ -13,7 +14,7 @@ const _imports_10 = "/static/ic_about.png";
const _imports_11 = "/static/ic_agreement2.png";
const _imports_12 = "/static/ic_agreement.png";
const _imports_13 = "/static/ic_exit.png";
const _imports_0 = "/static/ic_back.png";
const _imports_1$2 = "/static/arrow_down.png";
const _imports_1$1 = "/static/contact_qr.png";
const _imports_1 = "/static/new_bg1.png";
const _imports_2 = "/static/new_user.png";
@ -25,19 +26,20 @@ const _imports_7 = "/static/approved.png";
const _imports_8 = "/static/ic_colse.png";
exports._imports_0 = _imports_0$1;
exports._imports_0$1 = _imports_0;
exports._imports_1 = _imports_1$2;
exports._imports_1$1 = _imports_1$3;
exports._imports_1$2 = _imports_1$1;
exports._imports_1$3 = _imports_1;
exports._imports_1 = _imports_1$3;
exports._imports_1$1 = _imports_1$4;
exports._imports_1$2 = _imports_1$2;
exports._imports_1$3 = _imports_1$1;
exports._imports_1$4 = _imports_1;
exports._imports_10 = _imports_10;
exports._imports_11 = _imports_11;
exports._imports_12 = _imports_12;
exports._imports_13 = _imports_13;
exports._imports_2 = _imports_2$1;
exports._imports_2$1 = _imports_2;
exports._imports_2 = _imports_2$2;
exports._imports_2$1 = _imports_2$1;
exports._imports_2$2 = _imports_2;
exports._imports_3 = _imports_3;
exports._imports_4 = _imports_4$1;
exports._imports_4$1 = _imports_4;
exports._imports_4 = _imports_4;
exports._imports_5 = _imports_5$1;
exports._imports_5$1 = _imports_5;
exports._imports_6 = _imports_6$1;

File diff suppressed because it is too large Load Diff

View File

@ -66,7 +66,15 @@ const en = {
required: "Required",
pleaseEnterName: "Please enter your name",
hasData: "Data available",
selectCountry: "Select Country/Region"
selectCountry: "Select Country/Region",
remark: "Remark",
remarkPlaceholder: "Please enter remark",
serviceInfo: "Service Appointment Info",
departureDate: "Departure Date",
departureDatePlaceholder: "Please select departure date",
year: "",
month: "",
day: ""
},
me: {
title: "Profile",

View File

@ -66,7 +66,15 @@ const pt = {
required: "Obrigatório",
pleaseEnterName: "Por favor, insira seu nome",
hasData: "Dados disponíveis",
selectCountry: "Selecionar País/Região"
selectCountry: "Selecionar País/Região",
remark: "Observação",
remarkPlaceholder: "Por favor, insira observação",
serviceInfo: "Informações do Serviço",
departureDate: "Data de Partida",
departureDatePlaceholder: "Por favor, selecione a data de partida",
year: "",
month: "",
day: ""
},
me: {
title: "Perfil",

View File

@ -66,7 +66,15 @@ const zh = {
required: "必填",
pleaseEnterName: "请输入姓名",
hasData: "有数据",
selectCountry: "选择国家/地区"
selectCountry: "选择国家/地区",
remark: "备注",
remarkPlaceholder: "请输入备注信息",
serviceInfo: "服务预约信息",
departureDate: "出发日期",
departureDatePlaceholder: "请选择出发日期",
year: "年",
month: "月",
day: "日"
},
me: {
title: "个人中心",

View File

@ -225,9 +225,9 @@ AppServer.prototype.SetLanguage = async function(language) {
return data;
});
};
AppServer.prototype.GetCategories = async function() {
AppServer.prototype.GetCategories = async function(params = {}) {
var url = serverConfig.apiUrl_Service_GetCategories;
return this.getData(url).then((data) => {
return this.getData(url, params).then((data) => {
return data;
});
};
@ -358,15 +358,15 @@ AppServer.prototype.UploadImage = async function(filePath) {
success: (res) => {
try {
const data = JSON.parse(res.data);
common_vendor.index.__f__("log", "at modules/api/AppServer.js:575", "Upload image success:", data);
common_vendor.index.__f__("log", "at modules/api/AppServer.js:576", "Upload image success:", data);
resolve(data);
} catch (e) {
common_vendor.index.__f__("error", "at modules/api/AppServer.js:578", "Parse upload response error:", e);
common_vendor.index.__f__("error", "at modules/api/AppServer.js:579", "Parse upload response error:", e);
reject(e);
}
},
fail: (err) => {
common_vendor.index.__f__("error", "at modules/api/AppServer.js:583", "Upload image failed:", err);
common_vendor.index.__f__("error", "at modules/api/AppServer.js:584", "Upload image failed:", err);
reject(err);
}
});
@ -376,13 +376,13 @@ AppServer.prototype.GetPlatformIsAndroid = function() {
let port = common_vendor.index.getSystemInfoSync().platform;
switch (port) {
case "android":
common_vendor.index.__f__("log", "at modules/api/AppServer.js:609", "运行Android上", port);
common_vendor.index.__f__("log", "at modules/api/AppServer.js:610", "运行Android上", port);
return true;
case "ios":
common_vendor.index.__f__("log", "at modules/api/AppServer.js:613", "运行iOS上", port);
common_vendor.index.__f__("log", "at modules/api/AppServer.js:614", "运行iOS上", port);
return false;
default:
common_vendor.index.__f__("log", "at modules/api/AppServer.js:617", "运行在开发者工具上");
common_vendor.index.__f__("log", "at modules/api/AppServer.js:618", "运行在开发者工具上");
return false;
}
};

View File

@ -11,7 +11,7 @@ const _sfc_main = {
return {};
},
emits: ["click"],
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$4],
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$6],
computed: {
uClasses() {
let classes = [];

View File

@ -2,7 +2,7 @@
const common_vendor = require("../../../../common/vendor.js");
const _sfc_main = {
name: "u-overlay",
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$2],
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$4],
computed: {
overlayStyle() {
const style = {

View File

@ -2,7 +2,7 @@
const common_vendor = require("../../../../common/vendor.js");
const _sfc_main = {
name: "u-safe-bottom",
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$5],
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$7],
data() {
return {
safeAreaBottomHeight: 0,

View File

@ -2,7 +2,7 @@
const common_vendor = require("../../../../common/vendor.js");
const _sfc_main = {
name: "u-status-bar",
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$3],
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.props$5],
data() {
return {
isH5: false

View File

@ -33,7 +33,7 @@ const _sfc_main = {
}
},
// 将mixin挂在到组件中实际上为一个vue格式对象。
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.transitionMixin, common_vendor.props$6],
mixins: [common_vendor.mpMixin, common_vendor.mixin, common_vendor.transitionMixin, common_vendor.props$8],
watch: {
show: {
handler(newVal) {

View File

@ -26,7 +26,7 @@ const _sfc_main = {
async loadCategories() {
try {
const appserver = new modules_api_AppServer.AppServer();
const response = await appserver.GetCategories();
const response = await appserver.GetCategories({ language: this.currentLanguage });
common_vendor.index.__f__("log", "at pages/appointment/appointment-page.vue:100", "分类列表响应:", response);
if (response.code === 0 && response.data) {
this.categories = response.data.categories || response.data || [];

View File

@ -5,49 +5,263 @@ const _sfc_main = {
data() {
return {
userName: "",
isFlashing: false,
// 控制是否闪烁
selectedDialCode: "86"
userWechat: "",
userPhone: "",
userWhats: "",
remark: "",
departureDate: "",
returnDate: "",
departureCity: "",
arrivalCity: "",
luggageCount: "",
adultCount: 0,
childCount: 0,
infantCount: 0,
calendarType: "departure",
tripType: "single",
tripTypeIndex: 0,
tripTypeColumns: [
[
{
label: "单程",
value: "single"
},
{
label: "往返",
value: "round"
}
]
],
cabinType: "economy",
cabinTypeIndex: 0,
cabinTypeColumns: [
[
{
label: "经济舱",
value: "economy"
},
{
label: "超级经济舱",
value: "premium_economy"
},
{
label: "商务舱",
value: "business"
}
]
],
flashingField: "",
selectedDialCode: "86",
showCalendar: false,
showTripPicker: false,
showCabinPicker: false,
minDate: "",
maxDate: ""
};
},
computed: {
tripTypeText() {
const item = this.tripTypeColumns[0].find((t) => t.value === this.tripType);
return item ? item.label : "单程";
},
cabinTypeText() {
const item = this.cabinTypeColumns[0].find((t) => t.value === this.cabinType);
return item ? item.label : "经济舱";
}
},
onLoad() {
this.initDateRange();
},
methods: {
checkData() {
if (this.userName != "") {
common_vendor.index.showToast({
title: this.$t("infoEntry.hasData"),
icon: "none"
});
initDateRange() {
const now = /* @__PURE__ */ new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
this.minDate = `${year}-${month}-${day}`;
this.maxDate = `${year + 2}-12-31`;
},
openDepartureCalendar() {
common_vendor.index.__f__("log", "at pages/appointment/info-entry-page.vue:319", "打开出发日期选择器");
this.calendarType = "departure";
this.initDateRange();
this.$nextTick(() => {
this.showCalendar = true;
});
},
openReturnCalendar() {
common_vendor.index.__f__("log", "at pages/appointment/info-entry-page.vue:327", "打开返程日期选择器");
this.calendarType = "return";
if (this.departureDate) {
this.minDate = this.departureDate;
} else {
common_vendor.index.showToast({
title: this.$t("infoEntry.pleaseEnterName"),
icon: "none"
});
this.isFlashing = true;
setTimeout(() => {
this.isFlashing = false;
}, 1500);
this.initDateRange();
}
this.$nextTick(() => {
this.showCalendar = true;
});
},
closeCalendar() {
this.showCalendar = false;
this.initDateRange();
},
onCalendarConfirm(dates) {
common_vendor.index.__f__("log", "at pages/appointment/info-entry-page.vue:344", "日历确认:", dates);
if (dates && dates.length > 0) {
if (this.calendarType === "departure") {
this.departureDate = dates[0];
if (this.returnDate && this.returnDate < dates[0]) {
this.returnDate = "";
}
} else {
this.returnDate = dates[0];
}
}
this.showCalendar = false;
this.initDateRange();
},
onTripTypeConfirm(e) {
const selected = e.value[0];
this.tripType = selected.value;
this.tripTypeIndex = this.tripTypeColumns[0].findIndex((t) => t.value === selected.value);
this.showTripPicker = false;
},
onCabinTypeConfirm(e) {
const selected = e.value[0];
this.cabinType = selected.value;
this.cabinTypeIndex = this.cabinTypeColumns[0].findIndex((t) => t.value === selected.value);
this.showCabinPicker = false;
},
checkData() {
const validations = [
{
field: "userName",
selector: "#fieldUserName",
check: () => !this.userName.trim(),
message: "请输入真实姓名"
},
{
field: "contact",
selector: "#fieldContact",
check: () => !this.userWechat.trim() && !this.userPhone.trim() && !this.userWhats.trim(),
message: "请至少填写一种联系方式(微信号/手机号/WhatsApp"
},
{
field: "departureDate",
selector: "#fieldDepartureDate",
check: () => !this.departureDate,
message: "请选择出发日期"
},
{
field: "returnDate",
selector: "#fieldReturnDate",
check: () => this.tripType === "round" && !this.returnDate,
message: "请选择返程日期"
},
{
field: "departureCity",
selector: "#fieldDepartureCity",
check: () => !this.departureCity.trim(),
message: "请输入出发城市"
},
{
field: "arrivalCity",
selector: "#fieldArrivalCity",
check: () => !this.arrivalCity.trim(),
message: "请输入到达城市"
},
{
field: "personCount",
selector: "#fieldPersonCount",
check: () => this.adultCount === 0 && this.childCount === 0 && this.infantCount === 0,
message: "请至少选择一位乘客"
},
{
field: "luggageCount",
selector: "#fieldLuggageCount",
check: () => !this.luggageCount || this.luggageCount === "",
message: "请输入行李件数"
}
];
for (const validation of validations) {
if (validation.check()) {
common_vendor.index.showToast({
title: validation.message,
icon: "none"
});
this.scrollToElement(validation.selector);
this.flashingField = validation.field;
setTimeout(() => {
this.flashingField = "";
}, 1500);
return;
}
}
common_vendor.index.showToast({
title: this.$t("infoEntry.hasData"),
icon: "none"
});
},
scrollToElement(selector) {
const systemInfo = common_vendor.index.getSystemInfoSync();
const screenHeight = systemInfo.windowHeight;
const query = common_vendor.index.createSelectorQuery().in(this);
query.select(selector).boundingClientRect();
query.selectViewport().scrollOffset();
query.exec((res) => {
if (res[0] && res[1]) {
const rect = res[0];
const scrollInfo = res[1];
const targetScrollTop = scrollInfo.scrollTop + rect.top - screenHeight / 2 + rect.height / 2;
common_vendor.index.pageScrollTo({
scrollTop: Math.max(0, targetScrollTop),
duration: 300
});
}
});
},
back() {
common_vendor.index.navigateBack({
delta: 1
});
},
increaseCount(type) {
if (type === "adult") {
this.adultCount++;
} else if (type === "child") {
this.childCount++;
} else if (type === "infant") {
this.infantCount++;
}
},
decreaseCount(type) {
if (type === "adult" && this.adultCount > 0) {
this.adultCount--;
} else if (type === "child" && this.childCount > 0) {
this.childCount--;
} else if (type === "infant" && this.infantCount > 0) {
this.infantCount--;
}
}
}
};
if (!Array) {
const _easycom_up_input2 = common_vendor.resolveComponent("up-input");
const _easycom_aure_country_picker2 = common_vendor.resolveComponent("aure-country-picker");
(_easycom_up_input2 + _easycom_aure_country_picker2)();
const _easycom_up_picker2 = common_vendor.resolveComponent("up-picker");
const _easycom_up_calendar2 = common_vendor.resolveComponent("up-calendar");
(_easycom_up_input2 + _easycom_aure_country_picker2 + _easycom_up_picker2 + _easycom_up_calendar2)();
}
const _easycom_up_input = () => "../../node-modules/uview-plus/components/u-input/u-input.js";
const _easycom_aure_country_picker = () => "../../uni_modules/aure-country-picker/components/aure-country-picker/aure-country-picker.js";
const _easycom_up_picker = () => "../../node-modules/uview-plus/components/u-picker/u-picker.js";
const _easycom_up_calendar = () => "../../node-modules/uview-plus/components/u-calendar/u-calendar.js";
if (!Math) {
(_easycom_up_input + _easycom_aure_country_picker)();
(_easycom_up_input + _easycom_aure_country_picker + _easycom_up_picker + _easycom_up_calendar)();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_assets._imports_0$1,
return common_vendor.e({
a: common_assets._imports_0,
b: common_vendor.o((...args) => $options.back && $options.back(...args)),
c: common_vendor.t(_ctx.$t("infoEntry.title")),
d: common_vendor.t(_ctx.$t("infoEntry.personalInfo")),
@ -58,16 +272,16 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
border: "surround",
modelValue: $data.userName
}),
h: $data.isFlashing ? 1 : "",
h: $data.flashingField === "userName" ? 1 : "",
i: common_vendor.t(_ctx.$t("infoEntry.wechat")),
j: common_vendor.t(_ctx.$t("infoEntry.contactMethod")),
k: common_vendor.o(($event) => $data.userName = $event),
k: common_vendor.o(($event) => $data.userWechat = $event),
l: common_vendor.p({
placeholder: _ctx.$t("infoEntry.wechatPlaceholder"),
border: "surround",
modelValue: $data.userName
modelValue: $data.userWechat
}),
m: $data.isFlashing ? 1 : "",
m: $data.flashingField === "contact" ? 1 : "",
n: common_vendor.t(_ctx.$t("infoEntry.phone")),
o: common_vendor.t(_ctx.$t("infoEntry.contactMethod")),
p: common_vendor.o(($event) => $data.selectedDialCode = $event),
@ -82,25 +296,115 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
["mask-closable"]: true,
modelValue: $data.selectedDialCode
}),
r: common_vendor.o(($event) => $data.userName = $event),
r: common_vendor.o(($event) => $data.userPhone = $event),
s: common_vendor.p({
placeholder: _ctx.$t("infoEntry.phonePlaceholder"),
border: "surround",
modelValue: $data.userName
modelValue: $data.userPhone
}),
t: $data.isFlashing ? 1 : "",
t: $data.flashingField === "contact" ? 1 : "",
v: common_vendor.t(_ctx.$t("infoEntry.whatsapp")),
w: common_vendor.t(_ctx.$t("infoEntry.contactMethod")),
x: common_vendor.o(($event) => $data.userName = $event),
x: common_vendor.o(($event) => $data.userWhats = $event),
y: common_vendor.p({
placeholder: _ctx.$t("infoEntry.whatsappPlaceholder"),
border: "surround",
modelValue: $data.userName
modelValue: $data.userWhats
}),
z: $data.isFlashing ? 1 : "",
A: common_vendor.t(_ctx.$t("common.submit")),
B: common_vendor.o(($event) => $options.checkData())
};
z: $data.flashingField === "contact" ? 1 : "",
A: common_vendor.t(_ctx.$t("infoEntry.remark")),
B: common_vendor.o(($event) => $data.remark = $event),
C: common_vendor.p({
placeholder: _ctx.$t("infoEntry.remarkPlaceholder"),
border: "surround",
modelValue: $data.remark
}),
D: common_vendor.t(_ctx.$t("infoEntry.serviceInfo")),
E: common_vendor.t($options.tripTypeText),
F: common_assets._imports_1$2,
G: common_vendor.o(($event) => $data.showTripPicker = true),
H: common_vendor.t(_ctx.$t("infoEntry.departureDate")),
I: common_vendor.t($data.departureDate || _ctx.$t("infoEntry.departureDatePlaceholder")),
J: !$data.departureDate ? 1 : "",
K: common_assets._imports_2$1,
L: common_vendor.o((...args) => $options.openDepartureCalendar && $options.openDepartureCalendar(...args)),
M: $data.flashingField === "departureDate" ? 1 : "",
N: $data.tripType === "round"
}, $data.tripType === "round" ? {
O: common_vendor.t($data.returnDate || "请选择返程日期"),
P: !$data.returnDate ? 1 : "",
Q: common_assets._imports_2$1,
R: common_vendor.o((...args) => $options.openReturnCalendar && $options.openReturnCalendar(...args)),
S: $data.flashingField === "returnDate" ? 1 : ""
} : {}, {
T: $data.tripType === "round"
}, $data.tripType === "round" ? {} : {}, {
U: common_vendor.o(($event) => $data.departureCity = $event),
V: common_vendor.p({
placeholder: "请输入出发城市",
border: "surround",
modelValue: $data.departureCity
}),
W: $data.flashingField === "departureCity" ? 1 : "",
X: common_vendor.o(($event) => $data.arrivalCity = $event),
Y: common_vendor.p({
placeholder: "请输入到达城市",
border: "surround",
modelValue: $data.arrivalCity
}),
Z: $data.flashingField === "arrivalCity" ? 1 : "",
aa: common_vendor.o(($event) => $options.decreaseCount("adult")),
ab: common_vendor.t($data.adultCount),
ac: common_vendor.o(($event) => $options.increaseCount("adult")),
ad: common_vendor.o(($event) => $options.decreaseCount("child")),
ae: common_vendor.t($data.childCount),
af: common_vendor.o(($event) => $options.increaseCount("child")),
ag: common_vendor.o(($event) => $options.decreaseCount("infant")),
ah: common_vendor.t($data.infantCount),
ai: common_vendor.o(($event) => $options.increaseCount("infant")),
aj: $data.flashingField === "personCount" ? 1 : "",
ak: common_vendor.t($options.cabinTypeText),
al: common_assets._imports_1$2,
am: common_vendor.o(($event) => $data.showCabinPicker = true),
an: common_vendor.o(($event) => $data.luggageCount = $event),
ao: common_vendor.p({
placeholder: "请输入行李件数",
border: "surround",
type: "number",
modelValue: $data.luggageCount
}),
ap: $data.flashingField === "luggageCount" ? 1 : "",
aq: common_vendor.t(_ctx.$t("common.submit")),
ar: common_vendor.o(($event) => $options.checkData()),
as: common_vendor.o($options.onTripTypeConfirm),
at: common_vendor.o(($event) => $data.showTripPicker = false),
av: common_vendor.o(($event) => $data.showTripPicker = false),
aw: common_vendor.p({
show: $data.showTripPicker,
columns: $data.tripTypeColumns,
defaultIndex: [$data.tripTypeIndex],
keyName: "label"
}),
ax: common_vendor.o($options.onCabinTypeConfirm),
ay: common_vendor.o(($event) => $data.showCabinPicker = false),
az: common_vendor.o(($event) => $data.showCabinPicker = false),
aA: common_vendor.p({
show: $data.showCabinPicker,
columns: $data.cabinTypeColumns,
defaultIndex: [$data.cabinTypeIndex],
keyName: "label"
}),
aB: common_vendor.o($options.onCalendarConfirm),
aC: common_vendor.o($options.closeCalendar),
aD: common_vendor.p({
show: $data.showCalendar,
mode: "single",
minDate: $data.minDate,
maxDate: $data.maxDate,
confirmText: _ctx.$t("common.confirm"),
color: "#57C9DD"
})
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);

View File

@ -3,6 +3,8 @@
"navigationStyle": "custom",
"usingComponents": {
"up-input": "../../node-modules/uview-plus/components/u-input/u-input",
"aure-country-picker": "../../uni_modules/aure-country-picker/components/aure-country-picker/aure-country-picker"
"aure-country-picker": "../../uni_modules/aure-country-picker/components/aure-country-picker/aure-country-picker",
"up-picker": "../../node-modules/uview-plus/components/u-picker/u-picker",
"up-calendar": "../../node-modules/uview-plus/components/u-calendar/u-calendar"
}
}

File diff suppressed because one or more lines are too long

View File

@ -23,31 +23,114 @@
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.page {
min-height: 100vh;
background-color: #F3F3F3;
}
.header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
padding-top: 88rpx;
padding-bottom: 20rpx;
background-color: #F3F3F3;
position: fixed;
top: 0;
left: 0;
z-index: 100;
}
.scroll-content {
padding-top: 140rpx;
background-color: #F3F3F3;
min-height: 100vh;
}
.content {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
background-color: #F3F3F3;
min-height: 100%;
}
.date-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
height: 80rpx;
padding: 0 10rpx;
box-sizing: border-box;
}
.date-text {
font-size: 28rpx;
color: #333;
}
.date-placeholder {
color: #c0c4cc;
}
/* 闪烁动画关键帧 */
@keyframes flash {
0% {
background-color: #F3F3F3;
/* 原背景 */
}
50% {
background-color: #ff6666;
/* 高亮色(红色示例) */
}
100% {
background-color: #F3F3F3;
/* 恢复原背景 */
}
}
/* 动画类名(触发时添加) */
.flash-animation {
animation: flash 0.5s ease-in-out 3;
/* 0.3秒/次执行3次总时长0.9秒接近1秒 */
}
.person-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 20rpx 10rpx;
}
.person-info {
display: flex;
flex-direction: column;
}
.person-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.person-desc {
font-size: 24rpx;
color: #999;
margin-top: 6rpx;
}
.person-counter {
display: flex;
flex-direction: row;
align-items: center;
}
.counter-btn {
width: 50rpx;
height: 50rpx;
display: flex;
align-items: center;
justify-content: center;
background-color: #BFBFBF;
border-radius: 8rpx;
}
.counter-icon {
font-size: 28rpx;
color: #fff;
font-weight: bold;
line-height: 50rpx;
text-align: center;
color: #FFFFFF;
}
.counter-value {
font-size: 28rpx;
color: #333;
min-width: 80rpx;
text-align: center;
}

View File

@ -135,7 +135,7 @@ const _sfc_main = {
return;
}
common_vendor.index.navigateTo({
url: "/pages/index/reserve-details-page?title=" + item.name
url: "/pages/index/reserve-details-page?id=" + item.id + "&title=" + encodeURIComponent(item.name)
});
}
}

View File

@ -1,16 +1,61 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const modules_Config = require("../../modules/Config.js");
const common_assets = require("../../common/assets.js");
const _sfc_main = {
data() {
return {
title: ""
id: "",
title: "",
detailImage: "",
loading: true
};
},
onLoad(options) {
this.title = options.title;
this.id = options.id || "";
this.title = decodeURIComponent(options.title || "");
if (this.id) {
this.loadHotServiceDetail();
} else {
this.loading = false;
}
},
methods: {
/**
* 加载热门服务详情
*/
async loadHotServiceDetail() {
var _a;
try {
common_vendor.index.request({
url: modules_Config.Config.API_BASE_URL + "/api/v1/home/hot-services",
method: "GET",
header: {
"Accept-Language": ((_a = this.$i18n) == null ? void 0 : _a.locale) || "zh"
},
success: (res) => {
if (res.statusCode === 200 && res.data.code === 0) {
const service = res.data.data.find((item) => item.id == this.id);
if (service && service.detail_image) {
this.detailImage = modules_Config.Config.getImageUrl(service.detail_image);
}
}
this.loading = false;
},
fail: (error) => {
common_vendor.index.__f__("error", "at pages/index/reserve-details-page.vue:77", "加载热门服务详情失败:", error);
this.loading = false;
}
});
} catch (error) {
common_vendor.index.__f__("error", "at pages/index/reserve-details-page.vue:82", "加载热门服务详情失败:", error);
this.loading = false;
}
},
handleImageError(e) {
common_vendor.index.__f__("error", "at pages/index/reserve-details-page.vue:88", "详情图片加载失败:", e);
this.detailImage = "";
},
back() {
common_vendor.index.navigateBack({
delta: 1,
@ -18,21 +63,28 @@ const _sfc_main = {
animationDuration: 300
});
},
toEntry(id) {
toEntry() {
common_vendor.index.navigateTo({
url: "/pages/appointment/info-entry-page?id=" + id
url: "/pages/appointment/info-entry-page?id=" + this.id + "&title=" + encodeURIComponent(this.title)
});
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_assets._imports_0$1,
return common_vendor.e({
a: common_assets._imports_0,
b: common_vendor.o((...args) => $options.back && $options.back(...args)),
c: common_vendor.t($data.title),
d: common_vendor.t(_ctx.$t("reserveDetails.reserve")),
e: common_vendor.o(($event) => $options.toEntry())
};
d: $data.detailImage
}, $data.detailImage ? {
e: $data.detailImage,
f: common_vendor.o((...args) => $options.handleImageError && $options.handleImageError(...args))
} : {
g: common_vendor.t($data.loading ? _ctx.$t("common.loading") : "暂无详情图片")
}, {
h: common_vendor.t(_ctx.$t("reserveDetails.reserve")),
i: common_vendor.o(($event) => $options.toEntry())
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);

View File

@ -1 +1 @@
<view class="content"><view class="row" style="width:100%;margin-top:100rpx;align-items:center;justify-content:space-between"><view class="center" style="width:50rpx;height:50rpx;margin-left:32rpx"><image src="{{a}}" bindtap="{{b}}" style="width:48rpx;height:48rpx" mode=""></image></view><text style="font-size:30rpx">{{c}}</text><view style="width:50rpx;margin-right:32rpx"></view></view><view class="" style="width:100%;flex:1;background-color:antiquewhite;overflow-y:auto;margin-top:28rpx"><view class="" style="width:50rpx;height:1660rpx;background-color:aquamarine"></view></view><view class="center" bindtap="{{e}}" style="width:600rpx;height:92rpx;background-color:#1DB6D1;position:fixed;bottom:54rpx;border-radius:10rpx;font-size:32rpx;color:white">{{d}}</view></view>
<view class="content"><view class="row" style="width:100%;margin-top:100rpx;align-items:center;justify-content:space-between"><view class="center" style="width:50rpx;height:50rpx;margin-left:32rpx"><image src="{{a}}" bindtap="{{b}}" style="width:48rpx;height:48rpx" mode=""></image></view><text style="font-size:30rpx">{{c}}</text><view style="width:50rpx;margin-right:32rpx"></view></view><view class="" style="width:100%;flex:1;overflow-y:auto;margin-top:28rpx"><image wx:if="{{d}}" src="{{e}}" style="width:100%" mode="widthFix" binderror="{{f}}"></image><view wx:else class="center" style="width:100%;height:400rpx;background-color:#f5f5f5"><text style="color:#999">{{g}}</text></view></view><view class="center" bindtap="{{i}}" style="width:600rpx;height:92rpx;background-color:#1DB6D1;position:fixed;bottom:54rpx;border-radius:10rpx;font-size:32rpx;color:white">{{h}}</view></view>

View File

@ -2,6 +2,8 @@
const common_vendor = require("../../common/vendor.js");
const modules_api_AppServer = require("../../modules/api/AppServer.js");
const utils_auth = require("../../utils/auth.js");
const modules_Config = require("../../modules/Config.js");
const common_assets = require("../../common/assets.js");
const _sfc_main = {
data() {
return {
@ -11,10 +13,21 @@ const _sfc_main = {
showPrivacyModal: false,
userAgreementContent: this.getUserAgreementContent(),
privacyPolicyContent: this.getPrivacyPolicyContent(),
statusBarHeight: 0
statusBarHeight: 0,
appLogo: ""
};
},
onLoad() {
const systemInfo = common_vendor.index.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
this.loadConfig();
},
methods: {
back() {
common_vendor.index.navigateBack({
delta: 1
});
},
// Handle WeChat login
async handleWechatLogin() {
if (this.isLoading) {
@ -30,7 +43,7 @@ const _sfc_main = {
this.isLoading = true;
try {
const loginRes = await this.getWechatLoginCode();
common_vendor.index.__f__("log", "at pages/login/login-page.vue:121", "微信登录 code:", loginRes);
common_vendor.index.__f__("log", "at pages/login/login-page.vue:134", "微信登录 code:", loginRes);
if (!loginRes || !loginRes.code) {
common_vendor.index.showToast({
title: this.$t("login.wechatLoginFailed"),
@ -41,9 +54,9 @@ const _sfc_main = {
}
const appserver = new modules_api_AppServer.AppServer();
const data = await appserver.WechatLogin(loginRes.code);
common_vendor.index.__f__("log", "at pages/login/login-page.vue:136", "登录接口返回:", data);
common_vendor.index.__f__("log", "at pages/login/login-page.vue:149", "登录接口返回:", data);
if (!data) {
common_vendor.index.__f__("error", "at pages/login/login-page.vue:140", "登录接口无响应");
common_vendor.index.__f__("error", "at pages/login/login-page.vue:153", "登录接口无响应");
common_vendor.index.showToast({
title: this.$t("login.loginFailed"),
icon: "none"
@ -52,7 +65,7 @@ const _sfc_main = {
return;
}
if (data.code !== 0) {
common_vendor.index.__f__("error", "at pages/login/login-page.vue:151", "登录失败code:", data.code, "message:", data.message);
common_vendor.index.__f__("error", "at pages/login/login-page.vue:164", "登录失败code:", data.code, "message:", data.message);
common_vendor.index.showToast({
title: data.message || this.$t("login.loginFailed"),
icon: "none"
@ -61,7 +74,7 @@ const _sfc_main = {
return;
}
if (!data.data || !data.data.token) {
common_vendor.index.__f__("error", "at pages/login/login-page.vue:162", "Token 数据缺失:", data.data);
common_vendor.index.__f__("error", "at pages/login/login-page.vue:175", "Token 数据缺失:", data.data);
common_vendor.index.showToast({
title: this.$t("login.loginFailed"),
icon: "none"
@ -69,7 +82,7 @@ const _sfc_main = {
this.isLoading = false;
return;
}
common_vendor.index.__f__("log", "at pages/login/login-page.vue:171", "登录成功,保存认证信息");
common_vendor.index.__f__("log", "at pages/login/login-page.vue:184", "登录成功,保存认证信息");
const token = "Bearer " + data.data.token;
utils_auth.saveAuthData(token, data.data.refreshToken, data.data.user);
common_vendor.index.showToast({
@ -81,8 +94,8 @@ const _sfc_main = {
this.redirectToHome();
}, 500);
} catch (error) {
common_vendor.index.__f__("error", "at pages/login/login-page.vue:190", "登录异常:", error);
common_vendor.index.__f__("error", "at pages/login/login-page.vue:191", "错误堆栈:", error.stack);
common_vendor.index.__f__("error", "at pages/login/login-page.vue:203", "登录异常:", error);
common_vendor.index.__f__("error", "at pages/login/login-page.vue:204", "错误堆栈:", error.stack);
common_vendor.index.showToast({
title: this.$t("login.loginError") + ": " + (error.message || ""),
icon: "none",
@ -101,7 +114,7 @@ const _sfc_main = {
resolve(res);
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/login/login-page.vue:211", "uni.login 失败:", err);
common_vendor.index.__f__("error", "at pages/login/login-page.vue:224", "uni.login 失败:", err);
reject(err);
}
});
@ -117,10 +130,10 @@ const _sfc_main = {
common_vendor.index.switchTab({
url: "/pages/index/index",
success: () => {
common_vendor.index.__f__("log", "at pages/login/login-page.vue:231", "登录成功,已跳转到首页");
common_vendor.index.__f__("log", "at pages/login/login-page.vue:244", "登录成功,已跳转到首页");
},
fail: (err) => {
common_vendor.index.__f__("error", "at pages/login/login-page.vue:234", "跳转首页失败:", err);
common_vendor.index.__f__("error", "at pages/login/login-page.vue:247", "跳转首页失败:", err);
common_vendor.index.reLaunch({
url: "/pages/index/index"
});
@ -192,49 +205,62 @@ const _sfc_main = {
5. 联系我们
如有隐私问题请通过应用内的联系方式与我们联系
`;
},
// 加载配置
async loadConfig() {
try {
const config = await modules_Config.Config.getPublicConfig();
if (config.app_logo) {
this.appLogo = modules_Config.Config.getImageUrl(config.app_logo);
}
} catch (error) {
common_vendor.index.__f__("error", "at pages/login/login-page.vue:337", "加载配置失败:", error);
}
}
},
onLoad() {
const systemInfo = common_vendor.index.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.statusBarHeight + "px",
b: common_vendor.t(_ctx.$t("login.title")),
c: common_vendor.t(_ctx.$t("login.oneClickLogin")),
d: common_vendor.o((...args) => $options.handleWechatLogin && $options.handleWechatLogin(...args)),
e: $data.isLoading,
f: $data.agreeToTerms,
g: common_vendor.t(_ctx.$t("login.agreeToTerms")),
h: common_vendor.t(_ctx.$t("login.userAgreement")),
i: common_vendor.o((...args) => $options.showUserAgreement && $options.showUserAgreement(...args)),
j: common_vendor.t(_ctx.$t("login.and")),
k: common_vendor.t(_ctx.$t("login.privacyPolicy")),
l: common_vendor.o((...args) => $options.showPrivacyPolicy && $options.showPrivacyPolicy(...args)),
m: common_vendor.o((...args) => $options.handleAgreementChange && $options.handleAgreementChange(...args)),
n: $data.showAgreementModal
}, $data.showAgreementModal ? {
o: common_vendor.t(_ctx.$t("login.userAgreement")),
p: common_vendor.o((...args) => $options.closeUserAgreement && $options.closeUserAgreement(...args)),
q: common_vendor.t($data.userAgreementContent),
r: common_vendor.t(_ctx.$t("login.agree")),
s: common_vendor.o((...args) => $options.closeUserAgreement && $options.closeUserAgreement(...args)),
t: common_vendor.o(() => {
}),
v: common_vendor.o((...args) => $options.closeUserAgreement && $options.closeUserAgreement(...args))
a: common_assets._imports_0,
b: common_vendor.o((...args) => $options.back && $options.back(...args)),
c: common_vendor.t(_ctx.$t("login.title")),
d: common_vendor.o((...args) => _ctx.markAllRead && _ctx.markAllRead(...args)),
e: $data.appLogo
}, $data.appLogo ? {
f: $data.appLogo
} : {}, {
w: $data.showPrivacyModal
}, $data.showPrivacyModal ? {
x: common_vendor.t(_ctx.$t("login.privacyPolicy")),
y: common_vendor.o((...args) => $options.closePrivacyPolicy && $options.closePrivacyPolicy(...args)),
z: common_vendor.t($data.privacyPolicyContent),
A: common_vendor.t(_ctx.$t("login.agree")),
B: common_vendor.o((...args) => $options.closePrivacyPolicy && $options.closePrivacyPolicy(...args)),
C: common_vendor.o(() => {
g: common_vendor.t(_ctx.$t("login.oneClickLogin")),
h: common_vendor.o((...args) => $options.handleWechatLogin && $options.handleWechatLogin(...args)),
i: $data.isLoading,
j: $data.agreeToTerms,
k: common_vendor.t(_ctx.$t("login.agreeToTerms")),
l: common_vendor.t(_ctx.$t("login.userAgreement")),
m: common_vendor.o((...args) => $options.showUserAgreement && $options.showUserAgreement(...args)),
n: common_vendor.t(_ctx.$t("login.and")),
o: common_vendor.t(_ctx.$t("login.privacyPolicy")),
p: common_vendor.o((...args) => $options.showPrivacyPolicy && $options.showPrivacyPolicy(...args)),
q: common_vendor.o((...args) => $options.handleAgreementChange && $options.handleAgreementChange(...args)),
r: $data.showAgreementModal
}, $data.showAgreementModal ? {
s: common_vendor.t(_ctx.$t("login.userAgreement")),
t: common_vendor.o((...args) => $options.closeUserAgreement && $options.closeUserAgreement(...args)),
v: common_vendor.t($data.userAgreementContent),
w: common_vendor.t(_ctx.$t("login.agree")),
x: common_vendor.o((...args) => $options.closeUserAgreement && $options.closeUserAgreement(...args)),
y: common_vendor.o(() => {
}),
D: common_vendor.o((...args) => $options.closePrivacyPolicy && $options.closePrivacyPolicy(...args))
z: common_vendor.o((...args) => $options.closeUserAgreement && $options.closeUserAgreement(...args))
} : {}, {
A: $data.showPrivacyModal
}, $data.showPrivacyModal ? {
B: common_vendor.t(_ctx.$t("login.privacyPolicy")),
C: common_vendor.o((...args) => $options.closePrivacyPolicy && $options.closePrivacyPolicy(...args)),
D: common_vendor.t($data.privacyPolicyContent),
E: common_vendor.t(_ctx.$t("login.agree")),
F: common_vendor.o((...args) => $options.closePrivacyPolicy && $options.closePrivacyPolicy(...args)),
G: common_vendor.o(() => {
}),
H: common_vendor.o((...args) => $options.closePrivacyPolicy && $options.closePrivacyPolicy(...args))
} : {});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-6337d1cb"]]);

View File

@ -1 +1 @@
<view class="login-container data-v-6337d1cb"><view class="header data-v-6337d1cb"><view class="status-bar data-v-6337d1cb" style="{{'height:' + a}}"></view><view class="header-content data-v-6337d1cb"><navigator open-type="navigateBack" class="back-button data-v-6337d1cb"><text class="back-icon data-v-6337d1cb"></text></navigator><text class="header-title data-v-6337d1cb">{{b}}</text><view class="header-placeholder data-v-6337d1cb"></view></view></view><view class="logo-section data-v-6337d1cb"><view class="logo-box data-v-6337d1cb"><text class="logo-text data-v-6337d1cb">LOGO</text></view></view><view class="main-content data-v-6337d1cb"><button class="login-button data-v-6337d1cb" bindtap="{{d}}" loading="{{e}}">{{c}}</button><view class="agreement-section data-v-6337d1cb"><checkbox-group class="data-v-6337d1cb" bindchange="{{m}}"><label class="agreement-label data-v-6337d1cb"><checkbox class="data-v-6337d1cb" value="agree" checked="{{f}}"/><text class="agreement-text data-v-6337d1cb">{{g}} <text class="link-text data-v-6337d1cb" bindtap="{{i}}">{{h}}</text> {{j}} <text class="link-text data-v-6337d1cb" bindtap="{{l}}">{{k}}</text></text></label></checkbox-group></view></view><view wx:if="{{n}}" class="modal-overlay data-v-6337d1cb" bindtap="{{v}}"><view class="modal-content data-v-6337d1cb" catchtap="{{t}}"><view class="modal-header data-v-6337d1cb"><text class="modal-title data-v-6337d1cb">{{o}}</text><text class="modal-close data-v-6337d1cb" bindtap="{{p}}">×</text></view><scroll-view class="modal-body data-v-6337d1cb" scroll-y><text class="modal-text data-v-6337d1cb">{{q}}</text></scroll-view><view class="modal-footer data-v-6337d1cb"><button class="modal-button data-v-6337d1cb" bindtap="{{s}}">{{r}}</button></view></view></view><view wx:if="{{w}}" class="modal-overlay data-v-6337d1cb" bindtap="{{D}}"><view class="modal-content data-v-6337d1cb" catchtap="{{C}}"><view class="modal-header data-v-6337d1cb"><text class="modal-title data-v-6337d1cb">{{x}}</text><text class="modal-close data-v-6337d1cb" bindtap="{{y}}">×</text></view><scroll-view class="modal-body data-v-6337d1cb" scroll-y><text class="modal-text data-v-6337d1cb">{{z}}</text></scroll-view><view class="modal-footer data-v-6337d1cb"><button class="modal-button data-v-6337d1cb" bindtap="{{B}}">{{A}}</button></view></view></view></view>
<view class="login-container data-v-6337d1cb"><view class="header-row data-v-6337d1cb"><view class="back-button data-v-6337d1cb" bindtap="{{b}}"><image src="{{a}}" class="back-icon data-v-6337d1cb"></image></view><text class="title data-v-6337d1cb">{{c}}</text><view class="back-button data-v-6337d1cb" bindtap="{{d}}"></view></view><view class="logo-section data-v-6337d1cb"><view class="logo-box data-v-6337d1cb"><image wx:if="{{e}}" src="{{f}}" class="logo-image data-v-6337d1cb" mode="aspectFit"></image><text wx:else class="logo-text data-v-6337d1cb">LOGO</text></view></view><view class="main-content data-v-6337d1cb"><button class="login-button data-v-6337d1cb" bindtap="{{h}}" loading="{{i}}">{{g}}</button><view class="agreement-section data-v-6337d1cb"><checkbox-group class="data-v-6337d1cb" bindchange="{{q}}"><label class="agreement-label data-v-6337d1cb"><checkbox class="data-v-6337d1cb" value="agree" checked="{{j}}"/><text class="agreement-text data-v-6337d1cb">{{k}} <text class="link-text data-v-6337d1cb" bindtap="{{m}}">{{l}}</text> {{n}} <text class="link-text data-v-6337d1cb" bindtap="{{p}}">{{o}}</text></text></label></checkbox-group></view></view><view wx:if="{{r}}" class="modal-overlay data-v-6337d1cb" bindtap="{{z}}"><view class="modal-content data-v-6337d1cb" catchtap="{{y}}"><view class="modal-header data-v-6337d1cb"><text class="modal-title data-v-6337d1cb">{{s}}</text><text class="modal-close data-v-6337d1cb" bindtap="{{t}}">×</text></view><scroll-view class="modal-body data-v-6337d1cb" scroll-y><text class="modal-text data-v-6337d1cb">{{v}}</text></scroll-view><view class="modal-footer data-v-6337d1cb"><button class="modal-button data-v-6337d1cb" bindtap="{{x}}">{{w}}</button></view></view></view><view wx:if="{{A}}" class="modal-overlay data-v-6337d1cb" bindtap="{{H}}"><view class="modal-content data-v-6337d1cb" catchtap="{{G}}"><view class="modal-header data-v-6337d1cb"><text class="modal-title data-v-6337d1cb">{{B}}</text><text class="modal-close data-v-6337d1cb" bindtap="{{C}}">×</text></view><scroll-view class="modal-body data-v-6337d1cb" scroll-y><text class="modal-text data-v-6337d1cb">{{D}}</text></scroll-view><view class="modal-footer data-v-6337d1cb"><button class="modal-button data-v-6337d1cb" bindtap="{{F}}">{{E}}</button></view></view></view></view>

View File

@ -29,40 +29,26 @@
height: 100vh;
background-color: #f5f5f5;
}
.header.data-v-6337d1cb {
background-color: #fff;
border-bottom: 1px solid #eee;
}
.header .status-bar.data-v-6337d1cb {
.header-row.data-v-6337d1cb {
width: 100%;
background-color: #fff;
}
.header .header-content.data-v-6337d1cb {
margin-top: 100rpx;
padding-bottom: 20rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 16px;
}
.header .back-button.data-v-6337d1cb {
width: 40px;
height: 40px;
.back-button.data-v-6337d1cb {
width: 80rpx;
height: 50rpx;
margin-left: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.header .back-icon.data-v-6337d1cb {
font-size: 28px;
color: #333;
}
.header .header-title.data-v-6337d1cb {
font-size: 18px;
font-weight: 600;
color: #333;
flex: 1;
text-align: center;
}
.header .header-placeholder.data-v-6337d1cb {
width: 40px;
.back-icon.data-v-6337d1cb {
width: 48rpx;
height: 48rpx;
}
.logo-section.data-v-6337d1cb {
display: flex;
@ -80,6 +66,11 @@
justify-content: center;
background-color: #fff;
box-shadow: 0 2px 8px rgba(23, 162, 184, 0.1);
overflow: hidden;
}
.logo-section .logo-box .logo-image.data-v-6337d1cb {
width: 100%;
height: 100%;
}
.logo-section .logo-box .logo-text.data-v-6337d1cb {
font-size: 36px;

View File

@ -10,10 +10,10 @@ const _sfc_main = {
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_assets._imports_0$1,
a: common_assets._imports_0,
b: common_vendor.o((...args) => $options.goBack && $options.goBack(...args)),
c: common_vendor.t(_ctx.$t("me.contactUs")),
d: common_assets._imports_1$2
d: common_assets._imports_1$3
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-f03b1d9f"]]);

View File

@ -192,19 +192,19 @@ const _sfc_main = {
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: common_assets._imports_0$1,
a: common_assets._imports_0,
b: common_vendor.o((...args) => $options.goBack && $options.goBack(...args)),
c: common_vendor.t(_ctx.$t("invite.title")),
d: $data.navbarBgColor,
e: common_assets._imports_1$3,
e: common_assets._imports_1$4,
f: common_vendor.t(_ctx.$t("invite.rewardTitle")),
g: common_vendor.t(_ctx.$t("invite.rewardDesc")),
h: common_vendor.t(_ctx.$t("invite.stepsTitle")),
i: common_assets._imports_2$1,
i: common_assets._imports_2$2,
j: common_vendor.t(_ctx.$t("invite.step1")),
k: common_assets._imports_3,
l: common_vendor.t(_ctx.$t("invite.step2")),
m: common_assets._imports_4$1,
m: common_assets._imports_4,
n: common_vendor.t(_ctx.$t("invite.step3")),
o: common_vendor.t(_ctx.$t("invite.viewDetail")),
p: common_vendor.o((...args) => $options.showDetail && $options.showDetail(...args)),

View File

@ -62,16 +62,23 @@ const _sfc_main = {
return current ? current.name : "中文";
},
changeLanguage(langCode) {
if (langCode === this.$i18n.locale) {
this.showLanguagePicker = false;
return;
}
this.$i18n.locale = langCode;
common_vendor.index.setStorageSync("language", langCode);
this.showLanguagePicker = false;
this.$nextTick(() => {
utils_tabbarI18n.updateTabBarI18n(this);
});
common_vendor.index.showToast({
title: this.$t("common.success"),
icon: "success"
icon: "success",
duration: 1e3
});
setTimeout(() => {
common_vendor.index.reLaunch({
url: "/pages/index/index"
});
}, 1e3);
},
/**
* 跳转到个人资料编辑页面需要登录
@ -151,11 +158,11 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
b: $data.user.avatar || "/static/default_avatar.png",
c: common_vendor.t($data.user.nickname || "用户"),
d: common_vendor.t($data.user.uid || ""),
e: common_assets._imports_0,
e: common_assets._imports_0$1,
f: common_vendor.o((...args) => $options.goToProfileEdit && $options.goToProfileEdit(...args))
} : {
g: common_assets._imports_1$1,
h: common_assets._imports_0,
h: common_assets._imports_0$1,
i: common_vendor.o((...args) => $options.handleLogout && $options.handleLogout(...args))
}, {
j: common_vendor.t($data.isLogin ? "0" : "-"),
@ -167,37 +174,37 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
p: common_assets._imports_2,
q: common_assets._imports_1,
r: common_vendor.t(_ctx.$t("me.notification")),
s: common_assets._imports_4,
s: common_assets._imports_2$1,
t: common_vendor.o((...args) => $options.goToNotification && $options.goToNotification(...args)),
v: common_assets._imports_5,
w: common_vendor.t(_ctx.$t("me.customerService")),
x: common_assets._imports_4,
x: common_assets._imports_2$1,
y: common_assets._imports_6,
z: common_vendor.t(_ctx.$t("me.contactUs")),
A: common_assets._imports_4,
A: common_assets._imports_2$1,
B: common_vendor.o((...args) => $options.goToContactUs && $options.goToContactUs(...args)),
C: common_assets._imports_7,
D: common_vendor.t(_ctx.$t("me.inviteReward")),
E: common_assets._imports_4,
E: common_assets._imports_2$1,
F: common_vendor.o((...args) => $options.goToInviteReward && $options.goToInviteReward(...args)),
G: common_assets._imports_8,
H: common_assets._imports_9,
I: common_vendor.t(_ctx.$t("me.language")),
J: common_vendor.t($options.getCurrentLanguageName()),
K: common_assets._imports_4,
K: common_assets._imports_2$1,
L: common_vendor.o(($event) => $data.showLanguagePicker = true),
M: common_assets._imports_10,
N: common_vendor.t(_ctx.$t("me.about")),
O: common_assets._imports_4,
O: common_assets._imports_2$1,
P: common_assets._imports_11,
Q: common_vendor.t(_ctx.$t("me.userAgreement")),
R: common_assets._imports_4,
R: common_assets._imports_2$1,
S: common_assets._imports_12,
T: common_vendor.t(_ctx.$t("me.privacyPolicy")),
U: common_assets._imports_4,
U: common_assets._imports_2$1,
V: common_assets._imports_13,
W: common_vendor.t($data.isLogin ? _ctx.$t("me.logout") : "登录"),
X: common_assets._imports_4,
X: common_assets._imports_2$1,
Y: common_vendor.o((...args) => $options.handleLogout && $options.handleLogout(...args)),
Z: common_vendor.t(_ctx.$t("me.language")),
aa: common_vendor.o(($event) => $data.showLanguagePicker = false),

File diff suppressed because one or more lines are too long

View File

@ -122,7 +122,7 @@ const _sfc_main = {
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: common_assets._imports_0$1,
a: common_assets._imports_0,
b: common_vendor.o((...args) => $options.back && $options.back(...args)),
c: common_vendor.t(_ctx.$t("me.notification")),
d: common_vendor.t(_ctx.$t("notification.markAllRead")),

View File

@ -51,7 +51,7 @@
height: 48rpx;
}
.title {
font-size: 30rpx;
font-size: 36rpx;
font-weight: 500;
color: #333;
}