Commit cc058e1b authored by zhangsan's avatar zhangsan

1

parent 3153acf2
......@@ -14,4 +14,4 @@ VITE_DEV_TOOLS=true
VITE_BUILD_OUTPUT_DIR=dist
# 接口域名
VITE_API_BASE_URL=http://xxx.xxx.com
\ No newline at end of file
VITE_API_BASE_URL=/api
\ No newline at end of file
......@@ -3,4 +3,4 @@ VITE_APP_ENV=development
# 开发代理
# VITE_DEV_PROXY_PATH=/api
# VITE_DEV_PROXY_TARGET=http://xxx.xxx.com
\ No newline at end of file
# VITE_DEV_PROXY_TARGET=https://web.yidaiyilu2025.com/api
\ No newline at end of file
......@@ -8,4 +8,4 @@ VITE_DEV_TOOLS=false
VITE_BUILD_OUTPUT_DIR=dist
# 接口域名
VITE_API_BASE_URL=http://xxx.xxx.com
\ No newline at end of file
VITE_API_BASE_URL=/api
\ No newline at end of file
......@@ -5,4 +5,4 @@ VITE_APP_ENV=test
VITE_BUILD_OUTPUT_DIR=dist-test
# 接口域名
# VITE_API_BASE_URL=http://xxx.xxx.com
\ No newline at end of file
VITE_API_BASE_URL=/api
\ No newline at end of file
......@@ -60,7 +60,8 @@
"unocss",
"vant",
"Windi",
"zhangsanplus"
"zhangsanplus",
"varlet"
],
"npm.packageManager": "pnpm",
......
......@@ -13,6 +13,7 @@ import { FileSystemIconLoader } from 'unplugin-icons/loaders'
import IconsResolver from 'unplugin-icons/resolver'
import Icons from 'unplugin-icons/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
import { VarletImportResolver } from '@varlet/import-resolver'
import Components from 'unplugin-vue-components/vite'
import eruda from 'vite-plugin-eruda'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
......@@ -35,12 +36,15 @@ export function createVitePlugins(viteEnv: ImportMetaEnv, _isBuild: boolean) {
// 自动导入 Vue 相关函数,如:ref, reactive, toRef 等
imports: ['vue', '@vueuse/core'],
dts: pathResolve('types/auto-imports.d.ts'),
resolvers: [VarletImportResolver({ autoImport: true })],
}),
Components({
dirs: [pathResolve('src/components')],
resolvers: [
// 自动导入 vant 组件
VantResolver({ importStyle: false }),
// 自动导入 varlet 组件
VarletImportResolver({ autoImport: true }),
// 自动注册图标组件
IconsResolver({
customCollections: ['custom'],
......
......@@ -39,6 +39,7 @@
"new": "node ./scripts/generate.cjs"
},
"dependencies": {
"@varlet/ui": "^3.8.5",
"@vueuse/core": "^10.9.0",
"axios": "^1.6.7",
"dayjs": "^1.11.10",
......@@ -56,6 +57,7 @@
"@types/node": "20.9.0",
"@unocss/eslint-plugin": "^0.58.6",
"@unocss/preset-rem-to-px": "^0.58.6",
"@varlet/import-resolver": "^3.8.5",
"@vitejs/plugin-legacy": "^5.3.2",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
......
......@@ -4,11 +4,31 @@
"title": "404-页面未找到"
},
{
"path": "demo",
"title": "测试页面"
"path": "guquan",
"title": "股权"
},
{
"path": "index",
"title": "首页"
},
{
"path": "login",
"title": "登录"
},
{
"path": "register",
"title": "注册"
},
{
"path": "product",
"title": "产品"
},
{
"path": "user",
"title": "用户"
},
{
"path": "product/detail",
"title": "产品详情"
}
]
This diff is collapsed.
......@@ -19,10 +19,10 @@ body {
function renderMainFile() {
return `import 'virtual:uno.css'
import '@/styles/index.scss'
import Varlet from '@varlet/ui'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')`
createApp(App).use(Varlet).mount('#app')`
}
function renderHtmlFile(pagePath, pageName) {
......@@ -33,7 +33,7 @@ function renderHtmlFile(pagePath, pageName) {
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="ares-mobile是一个基于Vant4和Vue3的H5多页面前端模板" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>${pageName}</title>
......
/* 全局 TabBar 样式 */
.tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background-color: #fff;
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-around;
align-items: center;
z-index: 1000;
}
/* TabBar 项目样式 */
.tabbar-item {
flex: 1;
text-align: center;
padding: 10px;
color: #333;
}
/* 二级页面顶部返回按钮样式 */
.back-button {
position: fixed;
top: 10px;
left: 10px;
background-color: #fff;
border: none;
padding: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
\ No newline at end of file
<template>
<Teleport to="body">
<Transition name="fade">
<div v-if="visible" class="guide-modal-overlay" @click.self="handleClose">
<Transition :name="transitionName" mode="out-in" @before-leave="beforeLeave" @after-leave="afterLeave">
<div class="guide-modal" :key="currentIndex">
<!-- 使用插槽或v-html展示内容 -->
<div class="guide-content">
<slot :index="currentIndex">
<div v-html="guideList[currentIndex]?.noticeContent"></div>
</slot>
</div>
<div class="guide-footer">
<button class="guide-button" @click="handleAction">
{{ isLastPage ? '我知道了' : '下一页' }}
</button>
</div>
</div>
</Transition>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
interface Props {
guideList: string[]
modelValue: boolean
}
const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const currentIndex = ref(0)
const slideDirection = ref('next')
const transitionName = computed(() => `slide-${slideDirection.value}`)
const visible = computed({
get: () => props.modelValue,
set: (value) => emit('update:modelValue', value)
})
const isLastPage = computed(() => currentIndex.value === props.guideList.length - 1)
const handleAction = () => {
if (isLastPage.value) {
handleClose()
} else {
slideDirection.value = 'next'
currentIndex.value++
}
}
const handleClose = () => {
visible.value = false
// 重置状态
setTimeout(() => {
currentIndex.value = 0
slideDirection.value = 'next'
}, 300)
}
const beforeLeave = () => {
document.body.style.overflow = 'hidden'
}
const afterLeave = () => {
document.body.style.overflow = ''
}
</script>
<style lang="scss" scoped>
.guide-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.guide-modal {
background: #fff;
border-radius: 5px;
width: 100%; // 调整宽度为90%
height: 80vh; // 设置高度为视口高度的70%
position: relative;
display: flex;
flex-direction: column;
overflow: hidden; // 防止内容溢出
}
.guide-content {
flex: 1;
overflow-y: auto; // 内容过多时可滚动
padding: 20px;
// 允许v-html内容继承样式
:deep(*) {
max-width: 100%;
height: auto;
}
}
.guide-footer {
background: #fff; // 确保底部背景色
border-top: 1px solid #eee;
}
.guide-button {
width: 100%; // 按钮宽度设为90%
margin: 0 auto;
display: block;
background: red;
color: #fff;
border: none;
padding: 12px 0;
font-size: 16px;
cursor: pointer;
transition: opacity 0.3s;
}
// 淡入淡出动画
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
// 滑动动画
.slide-next-enter-active,
.slide-next-leave-active,
.slide-prev-enter-active,
.slide-prev-leave-active {
transition: all 0.3s ease-out;
}
.slide-next-enter-from {
transform: translateX(100%);
opacity: 0;
}
.slide-next-leave-to {
transform: translateX(-100%);
opacity: 0;
}
.slide-prev-enter-from {
transform: translateX(-100%);
opacity: 0;
}
.slide-prev-leave-to {
transform: translateX(100%);
opacity: 0;
}
</style>
\ No newline at end of file
<template>
<div>
<canvas id="myChart" width="400" height="400"></canvas>
</div>
</template>
\ No newline at end of file
<script setup lang="ts">
import { ref, computed } from 'vue'
import tab1Icon from '@/static/tabbar/tab1.png'
import tab1ActiveIcon from '@/static/tabbar/tab1-active.png'
import tab2Icon from '@/static/tabbar/tab2.png'
import tab2ActiveIcon from '@/static/tabbar/tab2-active.png'
import tab3Icon from '@/static/tabbar/tab3.png'
import tab3ActiveIcon from '@/static/tabbar/tab3-active.png'
import tab4Icon from '@/static/tabbar/tab4.png'
import tab4ActiveIcon from '@/static/tabbar/tab4-active.png'
import tab5Icon from '@/static/tabbar/tab5.png'
import tab5ActiveIcon from '@/static/tabbar/tab5-active.png'
interface TabItem {
id: number
name: string
label: string
icon: string
activeIcon: string
path: string
show: boolean
}
const tabs: TabItem[] = [
{
id: 1,
name: 'home',
label: '首页',
icon: tab1Icon,
activeIcon: tab1ActiveIcon,
path: '/index.html',
show: true
},
{
id: 2,
name: 'guquan',
label: '股权',
icon: tab2Icon,
activeIcon: tab2ActiveIcon,
path: '/guquan.html',
show: true
},
{
id: 3,
name: 'product',
label: '项目',
icon: tab3Icon,
activeIcon: tab3ActiveIcon,
path: '/product.html',
show: true
},
{
id: 5,
name: 'yaoqing',
label: '邀请奖励',
icon: tab5Icon,
activeIcon: tab5ActiveIcon,
path: '/user/yaoqing.html',
show: true
},
{
id: 4,
name: 'my',
label: '我的',
icon: tab4Icon,
activeIcon: tab4ActiveIcon,
path: '/my.html',
show: true
}
]
const activeTab = ref<string>(window.location.pathname)
const isVisible = computed(() => {
const currentRoute = window.location.pathname
activeTab.value = currentRoute
return tabs.some(tab => tab.path === currentRoute)
})
const handleTabClick = (tab: TabItem): void => {
window.location.href = window.location.origin + tab.path
}
</script>
<template>
<div v-if="isVisible" class="footer-wrap">
<div v-for="tab in tabs" :key="tab.id" class="footer-item" :class="{ active: activeTab === tab.path }"
@click="handleTabClick(tab)">
<div class="footer-item-content">
<div class="icon-wrap">
<img :src="activeTab === tab.name ? tab.activeIcon : tab.icon" :alt="tab.label">
</div>
<div class="label">{{ tab.label }}</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.footer-wrap {
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
align-items: center;
height: 55px;
background-color: #fff;
box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.06);
}
.footer-item {
flex: 1;
height: 100%;
cursor: pointer;
background: #af010b;
&-content {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 6px 0;
}
.icon-wrap {
display: flex;
justify-content: center;
align-items: center;
padding: 5px 0;
img {
width: 50%;
height: auto;
object-fit: contain;
}
}
.label {
font-size: 14px;
color: #fff;
}
&.active {
.label {
color: #fddc6b;
}
}
}
</style>
......@@ -2,7 +2,7 @@ export enum ResponseEnum {
/**
* 成功
*/
SUCCESS = 0,
SUCCESS = 200,
/**
* 登录过期
*/
......
<template>
<main class="flex flex-col min-h-svh">
<div style="margin-bottom: 50px;">
<slot />
</div>
<tabbar />
</main>
</template>
<script setup>
import tabbar from '@/components/tabbar.vue'
</script>
<template>
<main class="flex flex-col min-h-svh">
<!-- <AppHeader class="h-[var(--van-nav-bar-height)]"/> -->
<div style="margin-top: 54px;">
<slot />
</div>
</main>
</template>
<script setup>
// import AppHeader from '@/components/AppHeader.vue'
</script>
\ No newline at end of file
import { createApp } from 'vue';
import App from './App.vue';
import Varlet from '@varlet/ui'
import router from './router';
import './assets/global.css'; // 引入全局样式
'
const app = createApp(App);
app.use(router);
app.use(Varlet);
app.mount('#app');
\ No newline at end of file
<template>
<div class="container bg-slate-100 text-xs overflow-hidden">
<section class="bg-white p-4 m-4 rounded-1 ">
<h1 text-center text-xl color-primary>
Ares Mobile
</h1>
<p text-center text-gray>
基于 Vant4 和 Vue3 的 H5 移动端模板
</p>
</section>
<section class="bg-white p-4 m-4 rounded-1 ">
<h3 mt-2 mb-4>
✨ 特性
</h3>
<p>1. <a href="https://github.com/zhangsanplus/ares-mobile" class="underline">单页面SPA</a><a href="https://github.com/zhangsanplus/ares-mobile/tree/mpa" class="underline">多页面MPA</a>模板适用于不同的需求</p>
<p>2. 移动端组件库 - <strong>Vant</strong></p>
<p>3. 原子化 CSS - <strong>UnoCSS</strong></p>
<p>4. 应用程序级 JavaScript 的语言 - <strong>TypeScript</strong></p>
<p>
5. 实用的字体图标库 <i-carbon-worship-muslim text-primary />、自定义字体图标 <i-custom-github />
</p>
<p>6. 移动端调试插件 - <strong>eruda</strong></p>
<p>7. 移动端适配插件 - <strong>postcss-px-to-viewport</strong></p>
<p>8. 前端规范配置 - <strong>eslint</strong><strong>stylelint</strong><strong>commitlint</strong></p>
</section>
<section class="bg-white p-4 m-4 rounded-1">
<h3 mt-2 mb-4>
🔥 组件封装
</h3>
<div flex gap-2>
<button
class="btn"
@click="visible = true"
>
自定义组件
</button>
<button
class="btn"
@click="onClick"
>
函数调用
</button>
<a
class="btn"
href="./index.html"
target="_self"
>
返回首页
</a>
</div>
</section>
<section class="bg-white p-2 m-4 rounded-1">
<van-tabs v-model:active="active">
<van-tab title="幸运抽奖">
<luck-draw />
</van-tab>
<van-tab title="无限加载">
<scroll-list />
</van-tab>
</van-tabs>
</section>
<x-modal
:visible="visible"
title="我是标题"
content="我是内容"
@ok="visible = false"
@close="visible = false"
/>
</div>
</template>
<script setup lang="ts">
import Modal from '@/components/x-modal'
import XModal from '@/components/x-modal/x-modal.vue'
import LuckDraw from './components/luck-draw.vue'
import ScrollList from './components/scroll-list.vue'
const visible = ref(false)
const active = ref(0)
function onClick() {
Modal.open({
title: 'Ares Moblie',
content: '基于 Vue3 和 Vant4 的H5多页面模板,让开发变得更简单。',
})
}
</script>
<template>
<div class="luckdraw-content">
<!-- 滚动号码 -->
<ul class="luckdraw-scroll">
<li v-for="(item, i) in list" :key="i" :class="{ anim: animate && i === 0 }">
<span class="lkq-name">{{ item.phone }}</span>
</li>
</ul>
<!-- 抽奖弹窗 -->
<div class="turntable">
<!-- 跑马灯 -->
<svg class="bulb" viewBox="-6 -6 316 316" fill="currentColor" fill-rule="evenodd">
<g class="bulb-1">
<circle cx="10" cy="10" r="4" />
<circle cx="78" cy="4" r="4" />
<circle cx="152" cy="4" r="4" />
<circle cx="226" cy="4" r="4" />
<circle cx="294" cy="10" r="4" />
<circle cx="4" cy="89" r="4" />
<circle cx="4" cy="173" r="4" />
<circle cx="4" cy="258" r="4" />
<circle cx="41" cy="300" r="4" />
<circle cx="115" cy="300" r="4" />
<circle cx="189" cy="300" r="4" />
<circle cx="263" cy="300" r="4" />
<circle cx="300" cy="258" r="4" />
<circle cx="300" cy="173" r="4" />
<circle cx="300" cy="89" r="4" />
</g>
<g class="bulb-2 ">
<circle cx="41" cy="4" r="4" />
<circle cx="115" cy="4" r="4" />
<circle cx="189" cy="4" r="4" />
<circle cx="263" cy="4" r="4" />
<circle cx="4" cy="46" r="4" />
<circle cx="4" cy="131" r="4" />
<circle cx="4" cy="215" r="4" />
<circle cx="10" cy="294" r="4" />
<circle cx="294" cy="294" r="4" />
<circle cx="300" cy="215" r="4" />
<circle cx="300" cy="131" r="4" />
<circle cx="300" cy="46" r="4" />
<circle cx="78" cy="300" r="4" />
<circle cx="152" cy="300" r="4" />
<circle cx="226" cy="300" r="4" />
</g>
</svg>
<!-- 奖品 -->
<ul class="awards-list">
<li
v-for="(item, key) in awardList" :key="item.id" class="awards-item"
:class="{ 'awards-item-draw': key === 4, 'run-item': item.runId === current }"
>
<div v-if="key === 4" class="draw-btn" @click="handleStart">
<span class="draw-btn-text">点击抽奖</span>
</div>
<div v-else>
{{ item.name }}
</div>
</li>
</ul>
</div>
</div>
</template>
<script lang="ts" setup>
import { showToast } from 'vant'
// https://github.com/vincentzyc/vue3-demo/blob/main/src/views/LuckDraw.vue
interface AwardTypes {
id: number
runId: number
name: string
}
const initSpeed = 200 // 初始速度
const diff = 20 // 速度变化的值,值越大,变化地越快
const minRotateTime = 2.5 // 抽奖动画最少转动时间
const rotateTime = 5 // 抽奖动画转动时间
let speed = 0 // 变化速度
let time = 0 // 记录开始抽奖的时间
let isRuningLucky = false // 是否正在抽奖
let award: AwardTypes // 抽中的奖品
const animate = ref(false) // 中奖名单滚动动画控制
const current = ref(0) // 当前转动的位置
const list = reactive([
{ phone: '186****2336抽中0元话费' },
{ phone: '166****2336抽中1元话费' },
{ phone: '156****2336抽中2元话费' },
])
const awards = reactive([
{ id: 1, runId: 0, name: '潘多拉音箱' },
{ id: 2, runId: 1, name: '小酷M1耳机' },
{ id: 3, runId: 2, name: '酷狗VIP会员' },
{ id: 4, runId: 7, name: '8元话费' },
{ id: 5, runId: 3, name: '12元话费' },
{ id: 6, runId: 6, name: '谢谢参与1' },
{ id: 7, runId: 5, name: '4元话费' },
{ id: 8, runId: 4, name: '谢谢参与2' },
])
// 添加"开始抽奖"按钮到奖品列表中
const awardList = computed(() => {
const newArr = JSON.parse(JSON.stringify(awards))
newArr.splice(4, 0, { name: 'drawBtn' })
return newArr
})
// 中奖名单滚动
function scroll() {
animate.value = true
setTimeout(() => {
list.push(list[0])
list.shift()
animate.value = false
}, 500)
}
function move() {
const timer = setTimeout(() => {
current.value++
if (current.value > 7) current.value = 0
// 若抽中的奖品id存在,并且转动时间大于2.5秒后,则开始减速转动
if (award?.id && (Date.now() - time) / 1000 > minRotateTime) {
console.log('奖品出来了')
speed += diff // 转动减速
// 若转动时间超过5秒,等到当前格子是对应奖品id数组,则停下来
if (
(Date.now() - time) / 1000 > rotateTime
&& award.runId === current.value
) {
clearTimeout(timer)
setTimeout(() => {
isRuningLucky = false
// 这里写停下来要执行的操作(弹出奖品框之类的)
const getAward = awards.find(v => v.id === award.id)
if (getAward) {
showToast(`您抽中的奖品是${getAward.name},奖品id是${getAward.id}`)
}
}, 400)
return
}
// 若抽中的奖品不存在,则加速转动
} else {
if (speed >= 50) speed -= diff // 转动加速
}
move()
}, speed)
}
// 请求接口,模拟一个抽奖数据(假设请求时间为2s)
function drawAward() {
setTimeout(() => {
const awardId = Math.ceil(Math.random() * 8) // 随机奖品
const getAward = awards.find(v => v.id === awardId)
if (getAward) award = getAward
console.log('返回的抽奖结果是', award)
}, 2000)
move()
}
// 开始抽奖
function handleStart() {
if (isRuningLucky) {
console.log('正在抽奖中...')
return
}
if (Number.isNaN(Number(initSpeed))) {
return false
}
speed = initSpeed
isRuningLucky = true
time = Date.now()
drawAward()
console.log('开始抽奖')
}
onMounted(() => {
setInterval(scroll, 2000)
})
</script>
<style lang="scss" scoped>
.luckdraw-scroll {
position: relative;
height: 40px;
margin: 30px auto;
padding: 0;
overflow: hidden;
font-size: 16px;
line-height: 40px;
text-align: center;
list-style: none;
background: #bcbcc1;
border-radius: 20px;
.lkq-name {
margin: 0 auto;
color: #fff;
}
.anim {
margin-top: -40px;
transition: all 0.5s linear;
}
}
.luckdraw-content {
// 转盘
.turntable {
position: relative;
width: 320px;
height: 320px;
margin: 30px auto;
padding: 20px;
background: #fed479;
border-radius: 20px;
.awards-list {
display: grid;
grid-template-rows: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr);
gap: 3%;
height: 100%;
}
.awards-item {
display: flex;
align-items: center;
justify-content: center;
color: #d65c23;
font-size: 12px;
background-color: #fff;
border: 3px solid #fff;
border-color: #fff;
border-radius: 25%;
transition: border-color 0.1s;
}
.run-item {
border-color: #ff7051;
}
.awards-item-draw {
overflow: hidden;
background: #963434;
border: none;
}
.draw-btn {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
color: #fff;
background: #ff7051;
border: 3px solid #ff7051;
border-radius: 25%;
animation: draw-btn-jump 0.5s infinite;
}
}
}
.bulb {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.bulb .bulb-1 {
animation: bulb-animation 0.5s -0.25s infinite;
}
.bulb .bulb-2 {
animation: bulb-animation 0.5s infinite;
}
@keyframes bulb-animation {
0% {
color: #FFF;
}
50% {
color: #FFE37F;
}
100% {
color: #FFF;
}
}
@keyframes draw-btn-jump {
0% {
top: -6px;
}
50% {
top: 0;
}
100% {
top: -6px;
}
}
</style>
<template>
<van-list
v-model:loading="loading"
:finished="finished"
finished-text="没有更多了"
@load="onLoad"
>
<van-cell v-for="item in list" :key="item.id" :title="item.title" />
</van-list>
</template>
<script setup lang="ts">
import { getArticleList } from '@/api/article'
const list = ref<ArticleType.ListItem[]>([])
const loading = ref(false)
const finished = ref(false)
const form = reactive({
title: '',
pageNum: 0,
pageSize: 10,
})
function onLoad() {
++form.pageNum
getList()
}
async function getList() {
loading.value = true
try {
const { data } = await getArticleList(form)
list.value = form.pageNum === 1 ? data.list : [...list.value, ...data.list]
finished.value = list.value.length >= data.count
} catch (error) {
finished.value = true
console.error(error)
}
loading.value = false
}
</script>
......@@ -5,16 +5,15 @@
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="ares-mobile是一个基于Vant4和Vue3的H5多页面前端模板" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<link rel="icon" href="./favicon.ico">
<title>测试页面</title>
<title>项目详情</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./demo/main.ts"></script>
<script type="module" src="./detail/main.ts"></script>
</body>
</html>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>股权</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./guquan/main.ts"></script>
</body>
</html>
\ No newline at end of file
<script setup>
import { ref } from 'vue'
import KLineChart from '@/components/KLineChart.vue'
import defaultLayout from '@/layout/default.vue'
import request from '@/utils/request';
import { showSuccessToast, showFailToast } from 'vant';
const total = ref(0)
const total1 = ref(0)
const showPopup = ref(false)
function handleConfirm() {
showPopup.value = false
total.value = total1.value
}
const chartData = ref([])
async function getGuquan() {
const res = await request.get('/yw1/listZxt')
chartData.value = res.data
}
getGuquan()
function getLastFifthValue() {
if (chartData.value.length > 0) {
return chartData.value[chartData.value.length - 1][4]
}
return 0
}
function handleSell() {
if (total1.value <= 0) {
showFailToast('请输入抛售数量')
return
}
request.post('/system/user/gqps', { balance: total.value, remark: '', gj: getLastFifthValue() }).then((res) => {
if (res.code == 200) {
showSuccessToast('抛售成功')
}
else {
showFailToast('抛售失败')
}
}).finally(() => {
total.value = 0
})
showFailToast('还未开放抛售,请等待官方通知')
}
</script>
<template>
<defaultLayout>
<div class="container">
<div class="imgwarp">
<img class="topbox" src="@/static/guquan/topbox.png" alt="">
</div>
<div class="card">
<div class="topcard">
<div class="line" />
<div class="title">
当前预估股权
</div>
<!-- <div class="timer">2025-01-23 10:00:00</div> -->
</div>
<div class="chart-container">
<KLineChart />
</div>
<!-- <img class="kline" src="@/static/guquan/kline.png" alt=""> -->
<div v-ripple class="buynow" @click="showPopup = true">
请输入股权数:{{ total }}
</div>
<div v-ripple class="buynow" @click="handleSell">
立即抛售
</div>
<div class="tips">
*股价是根据大盘的走势价格决定当日成交价*
</div>
</div>
<van-popup v-model:show="showPopup" position="bottom" round>
<div class="popup-content">
<div class="popup-title">
请输入股权数
</div>
<div class="popup-title">
当前抛售股权总价值
<span style="color: red;">{{ total1 * getLastFifthValue() }}</span>
</div>
<div class="input-wrapper">
<input v-model="total1" type="number" placeholder="请输入股权数">
<span class="unit"></span>
</div>
<div class="popup-buttons">
<div class="cancel-btn" @click="showPopup = false">
取消
</div>
<div class="confirm-btn" @click="handleConfirm">
确定
</div>
</div>
</div>
</van-popup>
</div>
</defaultLayout>
</template>
<style lang="scss" scoped>
.container {
background: url('@/static/guquan/bg.png') no-repeat;
background-size: 100% 100%;
min-height: 100vh;
padding-top: 40px;
padding-bottom: 40px;
.imgwarp {
display: flex;
justify-content: center;
align-items: center;
}
.topbox {
width: 210px;
height: 44px;
margin: 0 auto 262px;
}
.card {
background: #fff;
border-radius: 12px;
padding: 20px;
margin: 0 20px;
.topcard {
display: flex;
justify-content: center;
align-items: center;
.line {
width: 16px;
height: 1px;
background: #d51200;
}
.title {
margin-left: 4px;
font-weight: 400;
font-size: 10px;
color: #d61200;
line-height: 13px;
}
.timer {
margin-left: 28px;
font-weight: 400;
font-size: 8px;
color: #353535;
line-height: 11px;
}
div {
display: inline-block;
}
}
.tips {
font-weight: 400;
font-size: 12px;
color: #afafaf;
line-height: 17px;
text-align: center;
}
}
.buynow {
width: 291px;
height: 63px;
background: url('@/static/common/btn.png') no-repeat;
background-size: 100% 100%;
font-weight: 500;
margin: 0 auto;
margin-top: 10px;
font-size: 21px;
color: #fff7e9;
text-align: center;
line-height: 46px;
input {
display: inline-block;
margin-left: 12px;
background-color: rgba(255, 255, 255, 0) !important; // 强制背景色为白色
border: none;
outline: none;
font-weight: bold;
font-size: 18px;
color: #ffffff;
line-height: 26px;
// 修改placeholder的样式
&::placeholder {
color: #999;
font-size: 14px;
}
// 移除自动填充时的背景色
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus,
&:-webkit-autofill:active {
-webkit-box-shadow: 0 0 0 30px white inset !important;
-webkit-text-fill-color: #460a0b !important;
}
&:disabled {
background-color: #fff !important;
color: #460a0b;
opacity: 1;
-webkit-text-fill-color: #460a0b;
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
&[type='number'] {
-moz-appearance: textfield;
}
}
}
.popup-content {
padding: 20px;
min-height: 300px;
.popup-title {
text-align: center;
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
color: #460a0b;
}
.input-wrapper {
display: flex;
align-items: center;
background: #f5f5f5;
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
input {
flex: 1;
border: none;
background: transparent;
font-size: 18px;
font-weight: bold;
color: #460a0b;
padding: 0 10px;
text-align: center;
&::placeholder {
color: #999;
font-size: 14px;
}
&:focus {
outline: none;
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
&[type='number'] {
-moz-appearance: textfield;
}
}
.unit {
color: #460a0b;
font-weight: bold;
padding-right: 10px;
}
}
.popup-buttons {
display: flex;
gap: 15px;
padding: 0 20px;
.cancel-btn,
.confirm-btn {
flex: 1;
text-align: center;
padding: 12px 0;
border-radius: 8px;
font-size: 16px;
font-weight: bold;
}
.cancel-btn {
background: #f5f5f5;
color: #460a0b;
}
.confirm-btn {
background: #460a0b;
color: #ffffff;
}
}
}
}
</style>
import 'virtual:uno.css'
import '@/styles/index.scss'
import Varlet from '@varlet/ui'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
createApp(App).use(Varlet).mount('#app')
\ No newline at end of file
......@@ -5,7 +5,7 @@
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="ares-mobile是一个基于Vant4和Vue3的H5多页面前端模板" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<link rel="icon" href="./favicon.ico">
......
<template>
<div class="container">
<img src="@/assets/logo.png">
<h2>Ares Mobile</h2>
<p>基于 Vant4 和 Vue3 的 H5 多页面前端模板</p>
<div class="nav">
<van-button type="primary" url="https://github.com/zhangsanplus/ares-mobile/tree/mpa">
github
</van-button>
<van-button type="primary" url="./__toc__.html">
页面目录
</van-button>
<defaultLayout>
<div class="container">
<GuideModal :show="showGuide" :guide-list="content" @close="showGuide = false" />
<div class="imgwarp">
<img src="@/static/common/login.png" />
</div>
<div class="bankcardwarp">
<img src="@/static/home/bankcard.png" />
<view class="cardnum" v-html="formatBankCardNumber(cardinfo?.bankNum)" />
</div>
<div class="buynow" @click="handleGetCard">
{{ !cardinfo?.bankNum ? '启用此卡' : '已启用此卡' }}
</div>
<div class="warpcard">
<div class="title">
<span class="span1">银行储蓄金:</span>
<span class="span2">{{ userData.q1 || '0.00' }}</span>
</div>
<div class="btnwarp">
<van-button class="btn1" @click="gotx(userData.q1, '1', '提现到卡')">提现到卡</van-button>
<van-button class="btn2" @click="gomx(1)">明细</van-button>
</div>
</div>
<div class="footer">
<div class="tips">
网站标识码:bm18000002 京ICP备10036469号
</div>
<div class="tips">
"一带一路"世界银行 版权所有,如需转载,请注明来源
</div>
<div class="tips">
<img src="@/static/my/b1.png" />
<img src="@/static/my/b2.png" />
</div>
</div>
</div>
<p style="color: #aaa;">
PC端建议打开 <strong style="color: #a0a0a0;">F12</strong> 在移动端模式下访问
</p>
</div>
</defaultLayout>
</template>
<script setup lang="ts">
console.log('drop console test')
<script setup>
import { ref } from 'vue'
import GuideModal from '@/components/GuideModal.vue'
import request from '@/utils/request';
import defaultLayout from '@/layout/default.vue'
import { showSuccessToast, showFailToast } from 'vant';
const showGuide = ref(false)
const content = ref([])
const userData = ref({})
const cardinfo = ref([])
async function getUserInfo() {
try {
const res = await request.get('/system/user/');
userData.value = res;
} catch (error) {
console.error('Failed to fetch user info:', error);
} finally {
}
}
async function getCardList() {
try {
const res = await request.get('/ops/bankcard/list?cardtype=2');
if (res.rows?.length > 0) {
cardinfo.value = res.rows[0];
}
} catch (error) {
console.error('Failed to fetch card list:', error);
}
}
async function getNotices() {
try {
const res = await request.get('/system/notice/list', {
noticeType: '',
})
content.value = res.rows.filter(item => item.noticeType == 0 || item.noticeType == 1 || item.noticeType == 2)
if (content.value.length > 0) {
showGuide.value = true
}
} catch (error) {
console.error('Failed to fetch notices:', error)
}
}
function formatBankCardNumber(cardNumber) {
if (!cardNumber) {
return Array.from({ length: 4 }).fill('****').map(part => `<view style='letter-spacing: 1.5px;margin-right: 10px;'>${part}</view>`).join(' ')
}
const cleaned = cardNumber.replace(/\D/g, '')
const cardParts = cleaned.match(/.{1,4}/g) || []
const formattedParts = cardParts.map(part => `<view style='letter-spacing: 1.5px;margin-right: 10px;'>${part}</view>`).join(' ')
return formattedParts
}
const navigateTo = (path) => {
window.location.href = window.location.origin + path
}
async function handleGetCard() {
if (userData.value.data !== 'true') {
showFailToast('请先实名注册后启用此卡');
navigateTo('/user/shiming.html');
return;
}
if (userData.value.smCount < 3) {
showFailToast('需邀请3位用户参与一带一路即可启用此卡');
navigateTo('/user/invite.html');
return;
}
if (cardinfo.value?.bankNum) {
showFailToast('已启用此卡');
return;
}
try {
const res = await post('/ops/bankcard/add', { cardtype: 2 });
if (res.code === 200) {
showSuccessToast('启用成功');
await getCardList(); // 刷新卡片列表
}
} catch (error) {
console.error('Failed to enable card:', error);
}
}
function gotx(balance, type, title) {
if (userData.value.data != 'true') {
showFailToast('请先实名注册后启用此卡')
navigateTo('/user/shiming.html')
return
}
if (userData.value.smCount < 3) {
showFailToast('需邀请3位用户参与一带一路即可启用此卡')
navigateTo('/user/invite.html')
return
}
if (balance && balance > 0) {
navigateTo(`/user/tixian?balance=${balance}&type=${type}&title=${title}`)
} else {
showFailToast('余额不足')
}
}
function gomx(tab) {
window.location.href = window.location.origin + `/user/zjmx.html?tab=${tab}`
}
// Fetch data on component mount
getUserInfo()
getCardList()
getNotices()
</script>
<style lang="scss">
<style lang="scss" scoped>
.container {
padding-top: 15vh;
text-align: center;
min-height: 100vh;
background: linear-gradient(to bottom, #fff3e6, #feeac9);
padding: 20px 0 18px;
.imgwarp {
display: flex;
justify-content: center;
align-items: center;
img {
width: 176px;
height: 105px;
margin: 0 auto 17px;
}
}
.warpcard {
margin: 10px 20px 30px;
border-radius: 12px;
padding: 20px;
background: #fff3e6;
text-align: center;
.title {
font-weight: bold;
font-size: 25px;
color: #323232;
line-height: 35px;
.span2 {
margin-left: 12px;
}
}
.btnwarp {
margin-top: 15px;
display: flex;
justify-content: center;
align-items: center;
img {
width: 80px;
span {
width: 120px;
height: 32px;
border-radius: 4px;
line-height: 32px;
text-align: center;
}
.btn1 {
background: #ffd128;
color: #323232;
}
.btn2 {
margin-left: 25px;
background: #e52c32;
color: #fff;
}
}
}
.bankcardwarp {
border-radius: 10px;
height: 243px;
position: relative;
img {
width: calc(100% - 40px);
display: block;
margin: 20px auto;
height: 243px;
}
.cardnum {
position: absolute;
color: #000;
left: 60px;
top: 130px;
font-size: 24px;
}
}
.nav {
margin: 30px auto;
.buynow {
width: 291px;
height: 63px;
background: url('@/static/common/btn.png') no-repeat;
background-size: 100% 100%;
font-weight: 500;
margin: 0 auto;
margin-top: 6px;
font-size: 21px;
color: #fff7e9;
text-align: center;
line-height: 46px;
}
.van-button + .van-button {
margin-left: 10px;
.footer {
padding: 0 40px;
font-size: 12px;
.tips {
display: flex;
img {
margin-top: 10px;
width: 24px;
margin-right: 12px;
}
}
}
.tabbar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 60px;
background-color: #fff;
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-around;
align-items: center;
z-index: 1000;
.tabbar-item {
flex: 1;
text-align: center;
padding: 10px;
color: #333;
}
}
}
</style>
import 'virtual:uno.css'
import '@/styles/index.scss'
import Varlet from '@varlet/ui'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
createApp(App).use(Varlet).mount('#app')
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>登录</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./login/main.ts"></script>
</body>
</html>
\ No newline at end of file
<template>
<div class="auth-container">
<img class="logo" src="@/static/common/login.png" alt="">
<div class="warpinput">
<div style="display: flex;align-items: center;">
<img style="width: 12px;height: 18px;" src="@/static/common/phone.png" alt="">
<input v-model="formData.username" type="text" placeholder="请输入用户名/手机号">
</div>
</div>
<div class="warpinput">
<div style="display: flex;align-items: center;">
<img style="width: 16px;height: 16px;" src="@/static/common/password.png" alt="">
<input v-model="formData.password" :type="isShow ? 'text' : 'password'" placeholder="请输入密码">
</div>
<van-icon v-if="!isShow" name="closed-eye" @click="isShow = !isShow" />
<van-icon v-if="isShow" name="eye-o" @click="isShow = !isShow" />
</div>
<div class="buynow" @click="handleSubmit">
立即登录
</div>
<div class="infowarp">
<div class="register" @click="navigateTo('/register.html')">
去注册
</div>
<div class="line">
|
</div>
<div class="forpass" @click="navigateTo('/forgot.html')">
忘记密码?
</div>
</div>
<div class="footer">
<van-checkbox v-model="checked" icon-size="16px" checked-color="#ee0a24" />
<span style="margin-left: 5px;" class="tips">我已阅读并同意</span>
<span class="xieyi">《用户协议》</span>
<span></span>
<span class="xieyi">《隐私协议》</span>
</div>
</div>
</template>
<script setup lang="ts">
const navigateTo = (path: string) => {
window.location.href = window.location.origin + path
}
import { ref, reactive } from 'vue'
import request from '@/utils/request'
import { showFailToast, showSuccessToast } from 'vant'
import { setToken } from '@/utils/auth'
interface LoginForm {
username: string
password: string
}
const checked = ref(true)
const isShow = ref(false)
const loading = ref(false)
const formData = reactive<LoginForm>({
username: '',
password: '',
})
function validateForm(): boolean {
if (!checked.value) {
showFailToast('请阅读并同意用户协议')
return false
}
if (!formData.username) {
showFailToast('请输入用户名/手机号')
return false
}
if (!formData.password) {
showFailToast('请输入密码')
return false
}
return true
}
async function handleSubmit() {
if (!validateForm()) return
if (loading.value) return
try {
loading.value = true
const res = await request.post('/login', {
username: formData.username,
password: formData.password,
type: 'app'
})
if (res.code === 200) {
showSuccessToast('登录成功')
setToken(res.token)
setTimeout(() => {
navigateTo('/index.html')
}, 1000)
} else {
showFailToast(res.msg || '登录失败')
}
} catch (error) {
console.error('Login error:', error)
showFailToast('登录失败,请稍后重试')
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
.auth-container {
padding: 20px 20px 49px;
background: url('@/static/common/commonbg.png') no-repeat;
background-size: 100% 100%;
min-height: 100vh;
.logo {
width: 176px;
height: 105px;
margin: 113px auto 73px;
display: block;
}
.warpinput {
display: flex;
background: #fff;
border-radius: 10px;
margin-bottom: 40px;
align-items: center;
justify-content: space-between;
padding: 15px;
input {
margin-left: 12px;
background-color: #fff !important;
border: none;
outline: none;
color: #460a0b;
font-size: 14px;
width: 0;
flex: 1;
&::placeholder {
color: #999;
font-size: 14px;
}
}
div {
flex: 1;
width: 0;
display: flex;
align-items: center;
}
}
.infowarp {
margin-top: 24px;
display: flex;
font-size: 14px;
color: #460a0b;
line-height: 14px;
justify-content: center;
.register {
text-decoration-line: underline;
}
.line {
margin: 0 5px;
}
}
.footer {
margin-top: 108px;
display: flex;
align-items: center;
justify-content: center;
span {
font-size: 12px;
color: #460a0b;
line-height: 14px;
}
.xieyi {
color: #d82828;
}
}
.buynow {
width: 291px;
height: 63px;
background: url('@/static/common/btn.png') no-repeat;
background-size: 100% 100%;
font-weight: 500;
margin: 0 auto;
margin-top: 60px;
font-size: 21px;
color: #fff7e9;
text-align: center;
line-height: 46px;
}
}
</style>
\ No newline at end of file
import 'virtual:uno.css'
import '@/styles/index.scss'
import Varlet from '@varlet/ui'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).use(Varlet).mount('#app')
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>项目</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./product/main.ts"></script>
</body>
</html>
\ No newline at end of file
<script setup>
import { ref } from 'vue'
import request from '@/utils/request'
import { showFailToast } from 'vant';
import defaultLayout from '@/layout/default.vue'
import card1 from '@/static/pages/product/card1.png'
import card2 from '@/static/pages/product/card2.png'
import card3 from '@/static/pages/product/card3.png'
import card4 from '@/static/pages/product/card4.png'
const pngArr1 = ref([card1, card2, card3, card4])
const userInfo = ref({})
const companyList = ref([])
const loading = ref(true)
const fetchData = async () => {
try {
loading.value = true
const [companyRes, userRes] = await Promise.all([
request.get("/ops/product/data/list", {}),
request.get('/system/user/')
])
if (companyRes?.code === 200) {
companyList.value = companyRes.rows ?? []
}
userInfo.value = userRes ?? {}
} catch (error) {
console.error('获取数据失败:', error)
showFailToast('加载失败,请重试')
} finally {
loading.value = false
}
}
const handleJump = (item) => {
window.location.href = window.location.origin + `/product/detail.html?id=${item.productId}`
}
fetchData()
</script>
<template>
<defaultLayout>
<div class="container">
<var-loading description="加载中..." :loading="loading">
<div v-for="(item, index) in companyList" :key="item.id" class="proitem" @click="handleJump(item)">
<div class="title">{{ item.productName }}</div>
<div class="content">
<img :src="pngArr1[item.productId - 1]" alt="">
<div class="rightbox">
<div class="subwarp">
<div class="subtitle">产品价格:</div>
<div class="subtext">{{ item.price }}</div>
</div>
<div class="tips">更多内容请点击详情<var-icon name="arrow-right" /></div>
</div>
</div>
<div class="footer">
<div class="item">
<div class="itemtop">{{ item.productImg }}</div>
<div class="itemtext">原始股</div>
</div>
<div class="item">
<div class="itemtop">{{ item.productKey }}</div>
<div class="itemtext">周期</div>
</div>
<div class="item">
<div class="itemtop">{{ item.productType }}元/天</div>
<div class="itemtext">日收益</div>
</div>
</div>
</div>
</var-loading>
</div>
</defaultLayout>
</template>
<style lang="scss" scoped>
.container {
background-color: #f5f7fa;
min-height: 100vh;
padding-top: 386px;
padding-bottom: 20px;
background: url('@/static/pages/product/z1.png') no-repeat;
background-size: 100% 100%;
.proitem {
background: url('@/static/pages/product/z2.png') no-repeat;
background-size: 100% 100%;
padding: 10px;
margin-bottom: 18px;
margin: 0 20px 18px;
.title {
font-weight: bold;
font-size: 17px;
color: #FFF9F2;
line-height: 23px;
text-align: center;
}
.content {
margin-top: 20px;
display: flex;
align-items: center;
img {
width: 122px;
margin-right: 11px;
height: 70px;
}
.rightbox {
padding-top: 10px;
flex: 1;
border-bottom: 1px dashed #333;
}
.subwarp {
display: flex;
align-items: flex-end;
.subtext {
font-weight: bold;
font-size: 19px;
color: #E72226;
line-height: 24px;
}
.subtitle {
font-weight: 400;
font-size: 12px;
color: #9B3500;
line-height: 16px;
}
}
.tips {
font-weight: 400;
font-size: 11px;
color: #000000;
line-height: 15px;
margin: 15px 0;
}
}
.footer {
display: flex;
margin-top: 21px;
justify-content: space-evenly;
text-align: center;
.itemtop {
font-weight: 500;
font-size: 14px;
color: #9B3500;
line-height: 19px;
}
.itemtext {
font-weight: 400;
font-size: 10px;
color: #000000;
line-height: 13px;
margin-top: 6px;
margin-bottom: 9px;
}
}
}
}
</style>
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>项目详情</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./product/detail/main.ts"></script>
</body>
</html>
\ No newline at end of file
<script setup>
import { ref } from 'vue'
import request from '@/utils/request'
import card1 from '@/static/pages/product/card1.png'
import card2 from '@/static/pages/product/card2.png'
import card3 from '@/static/pages/product/card3.png'
import card4 from '@/static/pages/product/card4.png'
const pngArr1 = ref([card1, card2, card3, card4])
const id = useRoute().params.id
const productArr = ref([])
const loading = ref(true)
const activeCard = ref(null)
const fetchData = async () => {
try {
loading.value = true
const res = await request.get(`/ops/product/data/list?productKey=${id}`)
if (res?.code === 200) {
productArr.value = res.rows[0] ?? []
}
} catch (error) {
console.error('获取数据失败:', error)
showFailToast('加载失败,请重试')
} finally {
loading.value = false
}
}
const handleDetail = (item) => {
activeCard.value = item.productId
setTimeout(() => {
navigateTo(`/product/buypro/${item.productId}`)
}, 300)
}
fetchData()
</script>
<template>
<div class="container">
<var-loading description="加载中..." :loading="loading">
<div class="card">
<div class="topbox">
<img :src="pngArr1[productArr.productId - 1]" />
<div class="right">
<div class="title">{{ productArr.productName }}</div>
<div class="price">{{ productArr.price }}<span></span></div>
</div>
</div>
<div class="infowarp">
<div class="item">
<div class="itemtop">{{ productArr.productImg }}<span></span></div>
<div class="itemtext">原始股</div>
</div>
<div class="item">
<div class="itemtop">{{ productArr.productKey }}<span></span></div>
<div class="itemtext">周期</div>
</div>
<div class="item sp">
<div class="itemtop">{{ productArr.productType }}<span>元/天</span></div>
<div class="itemtext">日收益</div>
</div>
</div>
<div class="other">
<span class="sub">股权定期分红:</span>
<span class="title">{{ productArr.productDescribe }}</span>
</div>
<var-divider dashed style="width: calc(100% - 15px);" />
<div class="tipswarp">
<div class="tipsbtn">认购说明</div>
<div class="tipsinfo">{{productArr.productTitle}}</div>
<div class="tipsbtn">产品介绍</div>
<div class="tipsinfo">{{productArr.data}}
</div>
</div>
</div>
<div class="buynow" @click="handleDetail(productArr)">立即购买</div>
</var-loading>
</div>
</template>
<style lang="scss" scoped>
.container {
padding-bottom: 50px;
background: #f5f7fa;
padding-top: 124px;
font-family: pingfang, sans-serif;
min-height: 100vh;
background: url('@/static/common/commonbg.png') no-repeat;
background-size: 100% 100%;
.card {
margin: 0 20px 0;
background: #fff;
border-radius: 10px;
padding: 15px 0 15px 15px;
.topbox {
display: flex;
img {
width: 74px;
height: 54px;
border-radius: 5px;
}
.right {
margin-left: 30px;
flex: 1;
.title {
font-weight: bold;
font-size: 15px;
color: #191919;
line-height: 30px;
background: linear-gradient(to right, #fefcfa, #fcece1);
}
.price {
margin-top: 5px;
font-weight: bold;
font-size: 22px;
color: #FF2510;
line-height: 28px;
span {
font-size: 12px;
}
}
}
}
.infowarp {
margin-top: 10px;
display: flex;
text-align: center;
.item {
flex: 1;
.itemtop {
font-weight: bold;
font-size: 17px;
color: #232323;
line-height: 27px;
span {
font-size: 12px;
}
}
.itemtext {
font-weight: 400;
font-size: 12px;
color: #797979;
line-height: 20px;
}
}
.sp {
.itemtop {
color: #FF2510;
}
}
}
.other {
padding-left: 15px;
margin-top: 14px;
display: flex;
align-items: flex-end;
.sub {
font-weight: 400;
font-size: 12px;
color: #797979;
}
.title {
margin-left: 9px;
font-weight: bold;
font-size: 13px;
color: #232323;
}
}
.tipswarp {
padding: 0 15px;
box-sizing: border-box;
width: 100%;
.tipsbtn {
margin-top: 14px;
display: inline-block;
font-weight: bold;
font-size: 14px;
color: #FFFEFE;
line-height: 15px;
padding: 5px 15px;
background: #ff2510;
border-radius: 13px;
}
.tipsinfo {
margin-top: 6px;
font-weight: 400;
font-size: 12px;
color: #5A5A5A;
line-height: 16px;
}
}
}
.buynow {
width: 291px;
height: 63px;
background: url('@/static/common/btn.png') no-repeat;
background-size: 100% 100%;
font-weight: 500;
margin: 0 auto;
margin-top: 37px;
font-size: 21px;
color: #FFF7E9;
text-align: center;
line-height: 46px;
}
}
</style>
\ No newline at end of file
import 'virtual:uno.css'
import '@/styles/index.scss'
import Varlet from '@varlet/ui'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).use(Varlet).mount('#app')
\ No newline at end of file
import 'virtual:uno.css'
import '@/styles/index.scss'
import Varlet from '@varlet/ui'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).use(Varlet).mount('#app')
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="description" content="一带一路" />
<meta name="keywords" content="ares-admin,ares-mobile,ares admin,ares mobile,ares,mpa,vue,h5,template">
<meta name="format-detection" content="telephone=no" />
<title>注册</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./register/main.ts"></script>
</body>
</html>
\ No newline at end of file
<template>
<div class="auth-container">
<img class="logo" src="@/static/common/login.png" alt="">
<img class="logo1" src="@/static/common/reg1.png" alt="">
<div class="warpinput">
<div style="display: flex;align-items: center;">
<img style="width: 12px;height: 18px;" src="@/static/common/phone.png" alt="">
<input v-model="formData.username" type="text" placeholder="请输入手机号">
</div>
</div>
<div class="warpinput">
<div style="display: flex;align-items: center;">
<img style="width: 16px;height: 16px;" src="@/static/common/password.png" alt="">
<input v-model="formData.password" :type="isShow ? 'text' : 'password'" placeholder="设置登录密码">
</div>
<van-icon v-if="!isShow" name="closed-eye" @click="isShow = !isShow" />
<van-icon v-if="isShow" name="eye-o" @click="isShow = !isShow" />
</div>
<div class="warpinput">
<div style="display: flex;align-items: center;">
<img style="width: 16px;height: 16px;" src="@/static/common/password.png" alt="">
<input v-model="formData.confirmPassword" :type="isShow1 ? 'text' : 'password'" placeholder="确认登录密码">
</div>
<van-icon v-if="!isShow1" name="closed-eye" @click="isShow1 = !isShow1" />
<van-icon v-if="isShow1" name="eye-o" @click="isShow1 = !isShow1" />
</div>
<div class="warpinput">
<div style="display: flex;align-items: center;">
<img style="width: 15px;height: 19px;" src="@/static/common/jiaoyi.png" alt="">
<input v-model="formData.extend3" type="password" placeholder="设置交易密码">
</div>
</div>
<div class="warpinput">
<div style="display: flex;align-items: center;">
<img style="width: 17px;height: 17px;" src="@/static/common/yq.png" alt="">
<input v-model="formData.inviteCode" type="text" placeholder="请输入邀请码">
</div>
</div>
<div class="buynow" @click="handleSubmit">
立即注册
</div>
<div class="infowarp">
<div class="register">
联系我们
</div>
<div class="line">
|
</div>
<div class="forpass" @click="navigateTo('/login.html')">
已有账号?<span style="text-decoration: underline;">去登录</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const navigateTo = (path: string) => {
window.location.href = path
}
import { ref, reactive } from 'vue'
import request from '@/utils/request'
import { showFailToast, showSuccessToast } from 'vant'
interface FormData {
username: string
password: string
confirmPassword: string
inviteCode: string
extend3: string
}
const isShow = ref(false)
const isShow1 = ref(false)
const loading = ref(false)
const formData = reactive<FormData>({
username: '',
password: '',
confirmPassword: '',
inviteCode: '',
extend3: '',
})
function validateForm(): boolean {
if (!formData.username) {
showFailToast('请输入手机号')
return false
}
if (!formData.password) {
showFailToast('请设置登录密码')
return false
}
if (formData.password !== formData.confirmPassword) {
showFailToast('两次输入的密码不一致')
return false
}
if (!formData.extend3) {
showFailToast('请设置交易密码')
return false
}
if (!formData.inviteCode) {
showFailToast('请输入邀请码')
return false
}
return true
}
async function handleSubmit() {
if (!validateForm()) return
if (loading.value) return
try {
loading.value = true
const res = await request.post('/register', {
username: formData.username,
password: formData.password,
code: '',
type: 'app',
yqm: formData.inviteCode,
extend3: formData.extend3,
})
if (res.code === 200) {
showSuccessToast('注册成功')
setTimeout(() => {
navigateTo('/')
}, 1000)
} else {
showFailToast(res.msg || '注册失败')
}
} catch (error) {
console.error('Register error:', error)
showFailToast('注册失败,请稍后重试')
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
.auth-container {
padding: 20px 20px 49px;
background: url('@/static/common/reg.png') no-repeat;
background-size: 100% 100%;
min-height: 100vh;
.logo {
width: 176px;
height: 105px;
margin: 40px auto 28px;
display: block;
}
.logo1 {
width: 310px;
height: 59px;
margin: 0 auto 105px;
display: block;
}
.warpinput {
display: flex;
background: #fff;
border-radius: 10px;
margin-bottom: 10px;
align-items: center;
justify-content: space-between;
padding: 15px;
input {
margin-left: 12px;
background-color: #fff !important;
border: none;
outline: none;
color: #460a0b;
font-size: 14px;
width: 0;
flex: 1;
&::placeholder {
color: #999;
font-size: 14px;
}
}
div {
flex: 1;
width: 0;
display: flex;
align-items: center;
}
}
.infowarp {
margin-top: 24px;
display: flex;
font-size: 14px;
color: #460a0b;
line-height: 14px;
justify-content: center;
.register {
text-decoration-line: underline;
}
.line {
margin: 0 5px;
}
}
.buynow {
width: 291px;
height: 63px;
background: url('@/static/common/btn.png') no-repeat;
background-size: 100% 100%;
font-weight: 500;
margin: 0 auto;
margin-top: 20px;
font-size: 21px;
color: #fff7e9;
text-align: center;
line-height: 46px;
}
}
</style>
\ No newline at end of file
import 'virtual:uno.css'
import '@/styles/index.scss'
import Varlet from '@varlet/ui'
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).use(Varlet).mount('#app')
\ No newline at end of file
This diff is collapsed.
<svg xmlns="http://www.w3.org/2000/svg" width="39" height="39" viewBox="0 0 39 39">
<g id="组_124315" data-name="组 124315" transform="translate(-167 -179)">
<path id="直线_4727" data-name="直线 4727" d="M36,1.5H0A1.5,1.5,0,0,1-1.5,0,1.5,1.5,0,0,1,0-1.5H36A1.5,1.5,0,0,1,37.5,0,1.5,1.5,0,0,1,36,1.5Z" transform="translate(168.5 198.5)" fill="#777"/>
<path id="直线_4728" data-name="直线 4728" d="M36,1.5H0A1.5,1.5,0,0,1-1.5,0,1.5,1.5,0,0,1,0-1.5H36A1.5,1.5,0,0,1,37.5,0,1.5,1.5,0,0,1,36,1.5Z" transform="translate(186.5 180.5) rotate(90)" fill="#777"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9">
<path id="多边形_2" data-name="多边形 2" d="M6.211,1.015a1,1,0,0,1,1.579,0l4.955,6.371A1,1,0,0,1,11.955,9H2.045a1,1,0,0,1-.789-1.614Z" fill="#333"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9">
<path id="多边形_4" data-name="多边形 4" d="M6.211,1.015a1,1,0,0,1,1.579,0l4.955,6.371A1,1,0,0,1,11.955,9H2.045a1,1,0,0,1-.789-1.614Z" transform="translate(14 9) rotate(180)" fill="#777"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="10.588" viewBox="0 0 12 10.588">
<path id="选择" d="M132.1,198.745l-5.032-5.242,1.29-1.059,2.91,2.337a32.472,32.472,0,0,1,7.493-6.623l.307.727a31.754,31.754,0,0,0-6.968,9.861Zm0,0" transform="translate(-127.068 -188.158)" fill="#e60012"/>
</svg>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment