小程序提交

This commit is contained in:
Ghost
2026-01-07 11:00:09 +08:00
parent c15bce23a4
commit 0e79aca04e
54 changed files with 7324 additions and 75 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.cursorindexingignore
.specstory/
node_modules/
uni_modules/
unpackage/

16
.hbuilderx/launch.json Normal file
View File

@@ -0,0 +1,16 @@
{ // launch.json 配置了启动调试时相关设置configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数remote代表前端连云端云函数
"version": "0.0",
"configurations": [{
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

34
App.vue Normal file
View File

@@ -0,0 +1,34 @@
<script>
export default {
onLaunch: function() {
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style lang="scss">
@import "uview-ui/index.scss";
/*每个页面公共css */
@import '@/uni_modules/uni-scss/index.scss';
/* #ifndef APP-NVUE */
@import '@/static/customicons.css';
// 设置整个项目的背景色
page {
background-color: #f5f5f5;
}
/* #endif */
.example-info {
font-size: 14px;
color: #333;
padding: 10px;
}
</style>

View File

@@ -1,36 +0,0 @@
# 存客宝小程序
#### Description
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

View File

@@ -1,39 +0,0 @@
# 存客宝小程序
#### 介绍
{**以下是 Gitee 平台说明,您可以替换此简介**
Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN。专为开发者提供稳定、高效、安全的云端软件开发协作平台
无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
#### 软件架构
软件架构说明
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

7
config.js Normal file
View File

@@ -0,0 +1,7 @@
// config.js
export default {
apiUrl: 'https://api.ckb.quwanzhi.com',
apiUrl2: 'https://ckbapi.quwanzhi.com',
//apiUrl2: 'http://www.yishi.com',
// 其他全局配置项
}

20
index.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

42
main.js Normal file
View File

@@ -0,0 +1,42 @@
// #ifndef VUE3
import Vue from 'vue'
import App from './App'
import config from './config.js'
import authMixin from './mixins/auth-mixin';
Vue.mixin(authMixin);
Vue.config.productionTip = false
Vue.prototype.$config = config
//兼容旧版
Vue.prototype.$wxAppId = 'wx789850448e26c91d';
Vue.prototype.$wxApi = 'https://api.quwanzhi.com';
Vue.prototype.$settingData = [];
Vue.prototype.$userInfo = {};
import uView from "uview-ui";
Vue.use(uView);
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
createSSRApp
} from 'vue'
import App from './App.vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

63
manifest.json Normal file
View File

@@ -0,0 +1,63 @@
{
"name" : "存客宝",
"appid" : "__UNI__505D381",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
/* 5+App */
"usingComponents" : true,
"nvueCompiler" : "uni-app",
"nvueStyleCompiler" : "uni-app",
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {},
/* */
"distribute" : {
/* */
"android" : {
/* android */
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
"ios" : {},
/* ios */
"sdkConfigs" : {}
}
},
/* SDK */
"quickapp" : {},
/* */
"mp-weixin" : {
/* */
"appid" : "wx789850448e26c91d",
"setting" : {
"urlCheck" : false,
"minified" : true,
"postcss" : true,
"es6" : true
},
"usingComponents" : true
},
"vueVersion" : "2"
}

69
mixins/auth-mixin.js Normal file
View File

@@ -0,0 +1,69 @@
export default {
methods: {
checkLogin() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
let fullPath = '';
let queryString = '';
// #ifdef H5
fullPath = window.location.hash ? window.location.hash.substring(1) : window.location.pathname;
queryString = window.location.search;
// #endif
// #ifdef MP
fullPath = `/${currentPage.route}`; // 在小程序中使用route
// Combine existing options with the scene parameter if it exists
const options = {
...currentPage.options
};
queryString = this.objectToQueryString(options);
if (this.$root.$mp.query.scene) {
queryString += "&" + decodeURIComponent(this.$root.$mp.query.scene)
}
// #endif
const redirectUrl = fullPath + "?" + queryString;
// 如果当前已经在登录页,则不进行跳转
if (
fullPath.includes('pages/login/login') ||
fullPath.includes('pages/index/index') ||
fullPath.includes('pages/index/site') ||
fullPath.includes('pages/poster/index') ||
fullPath.includes('pages/form/input')||
fullPath.includes('pages/login/privatememo')||
fullPath.includes('pages/login/usermemo') ||
fullPath.includes('pages/channel/add')||
fullPath.includes('pages/channel/login')||
fullPath.includes('pages/channel/statistics')
) {
return;
}
// 未登录则跳转到登录页
else if (!this.isLoggedIn()) {
uni.redirectTo({
url: '/pages/login/login?redirect=' + encodeURIComponent(redirectUrl),
});
}
},
isLoggedIn() {
const token = uni.getStorageSync('token');
return !!token;
},
// 将对象转换为查询字符串
objectToQueryString(obj) {
return Object.keys(obj).map(key => `${key}=${encodeURIComponent(obj[key])}`).join('&');
},
},
onLoad() {
this.checkLogin();
},
};

27
package-lock.json generated Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "ckb_mini",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"dependencies": {
"uview-ui": "^2.0.36"
}
},
"node_modules/uview-ui": {
"version": "2.0.36",
"resolved": "https://registry.npmjs.org/uview-ui/-/uview-ui-2.0.36.tgz",
"integrity": "sha512-ASSZT6M8w3GTO1eFPbsgEFV0U5UujK+8pTNr+MSUbRNcRMC1u63DDTLJVeArV91kWM0bfAexK3SK9pnTqF9TtA==",
"engines": {
"HBuilderX": "^3.1.0"
}
}
},
"dependencies": {
"uview-ui": {
"version": "2.0.36",
"resolved": "https://registry.npmjs.org/uview-ui/-/uview-ui-2.0.36.tgz",
"integrity": "sha512-ASSZT6M8w3GTO1eFPbsgEFV0U5UujK+8pTNr+MSUbRNcRMC1u63DDTLJVeArV91kWM0bfAexK3SK9pnTqF9TtA=="
}
}
}

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"uview-ui": "^2.0.36"
}
}

181
pages.json Normal file
View File

@@ -0,0 +1,181 @@
{
"pages": [
{
"path": "pages/index/site",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "",
"enableShareAppMessage": true
}
},
{
"path": "pages/poster/index",
"style": {
"navigationBarTitleText": "",
"enableShareAppMessage": true
}
},
{
"path": "pages/poster/index2",
"style": {
"navigationBarTitleText": "",
"enableShareAppMessage": true
}
},
{
"path": "pages/about/about",
"style": {
"navigationBarTitleText": "",
"enableShareAppMessage": true
}
}, {
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}, {
"path": "pages/home/home",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}, {
"path": "pages/ucenter/ucenter",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}, {
"path": "pages/form/input",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "pages/form/input2",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "pages/form/list",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "pages/channel/list",
"style": {
"navigationBarTitleText": "渠道管理",
"enablePullDownRefresh": true
}
},
{
"path": "pages/channel/add",
"style": {
"navigationBarTitleText": "新增渠道",
"enablePullDownRefresh": false
}
},
{
"path": "pages/channel/login",
"style": {
"navigationBarTitleText": "渠道登录",
"enablePullDownRefresh": false
}
},
{
"path": "pages/channel/project-select",
"style": {
"navigationBarTitleText": "选择项目",
"enablePullDownRefresh": true
}
},
{
"path": "pages/channel/statistics",
"style": {
"navigationBarTitleText": "分销获客统计",
"enablePullDownRefresh": true
}
}
],
// "tabBar": {
// "color": "#7A7E83",
// "selectedColor": "#007AFF",
// "borderStyle": "black",
// "backgroundColor": "#FFFFFF",
// "list": [{
// "pagePath": "pages/home/home",
// "iconPath": "static/tabbar/list.png",
// "selectedIconPath": "static/tabbar/list_active.png",
// "text": "主页"
// }, {
// "pagePath": "pages/ucenter/ucenter",
// "iconPath": "static/tabbar/me.png",
// "selectedIconPath": "static/tabbar/me_active.png",
// "text": "我的"
// }]
// },
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8",
"app-plus": {
"background": "#efeff4"
}
},
"easycom": {
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
},
"condition": { //模式配置,仅开发期间生效
"current": 1, //当前激活的模式(list 的索引项)
"list": [{
"name": "首页", //模式名称
"path": "pages/index/index", //启动页面,必选
"query": "" //启动参数在页面的onLoad函数里面得到
},
{
"name": "webview", //模式名称
"path": "pages/index/site", //启动页面,必选
"query": "" //启动参数在页面的onLoad函数里面得到
},
{
"name": "旧版海报页", //模式名称
"path": "pages/about/about", //启动页面,必选
"query": "taskId=491" //启动参数在页面的onLoad函数里面得到
},
{
"name": "新版海报页", //模式名称
"path": "pages/poster/index", //启动页面,必选
"query": "" //启动参数在页面的onLoad函数里面得到
},
{
"name": "登录页", //模式名称
"path": "pages/login/login", //启动页面,必选
"query": "" //启动参数在页面的onLoad函数里面得到
},
{
"name": "我的", //模式名称
"path": "pages/ucenter/ucenter", //启动页面,必选
"query": "" //启动参数在页面的onLoad函数里面得到
},
{
"name": "表单", //模式名称
"path": "pages/form/input", //启动页面,必选
"query": "id=505" //启动参数在页面的onLoad函数里面得到
}
]
}
}

119
pages/about/about.vue Normal file
View File

@@ -0,0 +1,119 @@
<template>
<view class="page">
<button style="padding: 0;margin-left: 0; text-align: left;" class="nullBtn" open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber">
<image class="posterImg" :src="poster.sUrl" mode="widthFix"></image>
</button>
</view>
</template>
<script>
export default {
data() {
return {
taskid: '',
token: "test",
task: null,
poster: null
}
},
onLoad(options) {
let scene = options.scene;
let taskId = options.taskId;
if (scene != undefined) {
this.taskid = scene;
} else if (taskId != undefined) {
this.taskid = taskId;
}
this.getPosterData()
},
methods: {
getPosterData() {
let that = this;
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl + '/v1/frontend/business/poster/getone',
data: {
id: that.taskid,
},
success: (res) => {
if (res.data.code == 10000) {
that.poster = res.data.data.poster
that.task = res.data.data.task
//修改标题
uni.setNavigationBarTitle({
title: that.task.sName
});
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
},
// 获取用户手机号
getPhoneNumber(res) {
let that = this;
if (res.detail.errMsg == "getPhoneNumber:ok") {
uni.showLoading({
title: '请求中...',
mask: true
})
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl + '/v1/frontend/business/poster/decryptphone',
data: {
id: that.taskid,
code: res.detail.code
},
success: (res) => {
uni.hideLoading();
if (res.data.code == 10000) {
uni.showModal({
title: '提示',
content: that.task.sTip,
success: function(res) {
console.log(res)
}
})
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
}
},
}
}
</script>
<style>
.page {
width: 100vw;
height: 100vh;
}
.posterImg {
width: 100%;
}
</style>

801
pages/channel/add.vue Normal file
View File

@@ -0,0 +1,801 @@
<template>
<view class="page-container">
<!-- 验证中提示 -->
<view v-if="validating" class="validating-container">
<view class="validating-content">
<uni-icons type="spinner-cycle" size="40" color="#6366f1"></uni-icons>
<text class="validating-text">正在验证...</text>
</view>
</view>
<!-- 错误提示页面 -->
<view v-if="!validating && !tokenValid && errorMessage" class="error-container">
<view class="error-content">
<view class="error-icon-wrapper">
<uni-icons type="closeempty" size="80" color="#ef4444"></uni-icons>
</view>
<text class="error-title">验证失败</text>
<text class="error-message">{{ errorMessage }}</text>
<button class="error-button" @click="goBack">返回</button>
</view>
</view>
<!-- 创建方式选择 -->
<view class="method-selector" v-if="tokenValid && !validating && !errorMessage">
<view class="selector-item" :class="{'active': createMethod === 'manual'}" @click="createMethod = 'manual'">
<view class="selector-dot" v-if="createMethod === 'manual'"></view>
<text class="selector-text">手动创建</text>
</view>
</view>
<!-- 表单内容 -->
<view class="form-container" v-if="tokenValid && !validating && !errorMessage">
<view class="form-header">
<text class="form-title">填写渠道信息</text>
<text class="form-subtitle">请完善下方信息以手动添加新的分销渠道</text>
</view>
<view class="form-body">
<!-- 渠道名称 -->
<view class="form-item">
<view class="form-label">
<uni-icons type="shop" size="18" color="#6366f1"></uni-icons>
<text class="label-text">渠道名称</text>
<text class="required-mark">必填</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="form.channelName"
placeholder="请输入渠道名称"
maxlength="50"
:focus="focusChannelName"
@blur="focusChannelName = false"
/>
</view>
</view>
<!-- 联系电话 -->
<view class="form-item">
<view class="form-label">
<uni-icons type="phone" size="18" color="#6366f1"></uni-icons>
<text class="label-text">手机号</text>
<text class="required-mark">必填</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="form.contactPhone"
placeholder="请输入手机号"
type="number"
maxlength="11"
:focus="focusContactPhone"
@blur="focusContactPhone = false"
/>
</view>
</view>
<!-- 微信号 -->
<view class="form-item">
<view class="form-label">
<uni-icons type="chat" size="18" color="#6366f1"></uni-icons>
<text class="label-text">微信号</text>
</view>
<view class="input-wrapper">
<input
class="form-input"
v-model="form.wechatId"
placeholder="请输入微信号"
maxlength="50"
:focus="focusWechatId"
@blur="focusWechatId = false"
/>
</view>
</view>
<!-- 备注 -->
<view class="form-item">
<view class="form-label">
<uni-icons type="info" size="18" color="#6366f1"></uni-icons>
<text class="label-text">备注</text>
</view>
<view class="input-wrapper">
<textarea
class="form-textarea"
v-model="form.remark"
placeholder="请输入备注信息 (选填)"
maxlength="200"
:auto-height="true"
:focus="focusRemark"
@blur="focusRemark = false"
/>
</view>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="button-container" v-if="tokenValid && !validating && !errorMessage">
<button class="cancel-button" @click="cancel">取消</button>
<button class="submit-button" :class="{'loading': isSubmitting}" @click="submit" :disabled="isSubmitting || !tokenValid">
<uni-icons v-if="!isSubmitting" type="checkmarkempty" size="20" color="#fff"></uni-icons>
<text class="button-text">{{ isSubmitting ? '提交中...' : '创建' }}</text>
</button>
</view>
</view>
</template>
<script>
import config from '@/config';
export default {
data() {
return {
token: '',
createMethod: 'manual',
form: {
channelName: '',
contactPhone: '',
wechatId: '',
remark: ''
},
focusChannelName: false,
focusContactPhone: false,
focusWechatId: false,
focusRemark: false,
isSubmitting: false,
tokenValid: false,
validating: true,
errorMessage: ''
}
},
onLoad(options) {
// 获取参数
let scene = options.scene;
let token = options.token;
console.log('页面参数:', { scene, token });
// 小程序端解析 scene 参数
// #ifdef MP-WEIXIN
if (scene != undefined) {
// 先对 scene 进行 URL 解码(处理 %2C 等编码字符)
try {
scene = decodeURIComponent(scene);
} catch (e) {
console.warn('scene 解码失败,使用原始值:', e);
}
// scene 中可能包含 token或者直接就是 token
// 如果 scene 包含逗号,可能需要分割(根据实际业务需求调整)
if (scene.includes(',')) {
// 如果 scene 包含逗号,取第一部分作为 token
const parts = scene.split(',');
this.token = parts[0] || '';
} else {
// 如果不包含逗号scene 本身就是 token
this.token = scene;
}
} else if (token != undefined) {
// 兼容直接传 token 参数
this.token = token;
} else {
// 没有参数,显示错误
this.token = '';
}
// #endif
// H5 端兼容处理
// #ifdef H5
if (token != undefined) {
// H5 端优先使用 token 参数
this.token = token;
} else if (scene != undefined) {
// H5 也可能有 scene 参数(从分享链接等)
// 先对 scene 进行 URL 解码
try {
scene = decodeURIComponent(scene);
} catch (e) {
console.warn('scene 解码失败,使用原始值:', e);
}
if (scene.includes(',')) {
const parts = scene.split(',');
this.token = parts[0] || '';
} else {
this.token = scene;
}
} else {
// 没有参数,显示错误
this.token = '';
}
// #endif
console.log('解析结果:', { token: this.token });
if (!this.token) {
this.validating = false;
this.tokenValid = false;
this.errorMessage = '缺少token参数请重新扫码';
return;
}
uni.setNavigationBarTitle({
title: '新增渠道'
});
// 验证token有效性
this.validateToken();
// H5和小程序兼容处理
// #ifdef H5
// H5端回车键提交
document.onkeydown = (event) => {
const e = event || window.event;
if (e && e.keyCode === 13) {
this.submit();
}
};
// #endif
},
methods: {
// 验证token有效性
validateToken() {
this.validating = true;
uni.showLoading({
title: '验证中...',
mask: true
});
uni.request({
method: 'GET',
url: config.apiUrl2 + '/v1/frontend/distribution/channel/register',
data: {
token: this.token
},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: (response) => {
uni.hideLoading();
this.validating = false;
if (response.data.code === 10000 || response.data.code === 200) {
this.tokenValid = true;
this.errorMessage = '';
// token验证成功可以填写表单
} else {
this.tokenValid = false;
this.errorMessage = response.data.message || 'token无效或已过期请重新扫码';
}
},
fail: (error) => {
uni.hideLoading();
this.validating = false;
this.tokenValid = false;
console.error('验证token失败:', error);
this.errorMessage = '网络错误,请检查网络连接后重试';
}
});
},
// 表单验证
validateForm() {
if (!this.form.channelName || !this.form.channelName.trim()) {
this.focusChannelName = true;
uni.showToast({
title: '请输入渠道名称',
icon: 'none',
duration: 2000
});
return false;
}
// 手机号必填验证
if (!this.form.contactPhone || !this.form.contactPhone.trim()) {
this.focusContactPhone = true;
uni.showToast({
title: '请输入手机号',
icon: 'none',
duration: 2000
});
return false;
}
// 验证手机号格式
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(this.form.contactPhone.trim())) {
this.focusContactPhone = true;
uni.showToast({
title: '请输入正确的手机号',
icon: 'none',
duration: 2000
});
return false;
}
return true;
},
// 提交表单
submit() {
if (this.isSubmitting) {
return;
}
if (!this.validateForm()) {
return;
}
if (!this.tokenValid) {
uni.showToast({
title: 'token无效请重新扫码',
icon: 'none',
duration: 2000
});
return;
}
this.isSubmitting = true;
const data = {
token: this.token,
name: this.form.channelName.trim(),
phone: this.form.contactPhone.trim(),
wechatId: this.form.wechatId ? this.form.wechatId.trim() : '',
remarks: this.form.remark ? this.form.remark.trim() : ''
};
uni.request({
method: 'POST',
url: config.apiUrl2 + '/v1/frontend/distribution/channel/register',
data: data,
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: (response) => {
this.isSubmitting = false;
if (response.data.code === 10000 || response.data.code === 200) {
// 获取返回的companyId
const responseData = response.data.data || {};
const companyId = responseData.companyId || '';
console.log('渠道创建成功,返回数据:', responseData);
console.log('获取到的companyId:', companyId);
uni.showToast({
title: '提交成功',
icon: 'success',
duration: 2000
});
setTimeout(() => {
// 如果有companyId跳转到渠道登录页
if (companyId) {
uni.redirectTo({
url: `/pages/channel/login?companyId=${companyId}`
});
} else {
// 如果没有companyId返回上一页
uni.navigateBack();
}
}, 1500);
} else {
// 显示后端返回的错误信息
const errorMsg = response.data.message || response.data.msg || response.data.error || '提交失败,请重试';
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 3000
});
}
},
fail: (error) => {
this.isSubmitting = false;
console.error('提交失败:', error);
uni.showToast({
title: '网络错误,请重试',
icon: 'none',
duration: 2000
});
}
});
},
// 取消
cancel() {
uni.navigateBack();
},
// 返回
goBack() {
uni.navigateBack();
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 160rpx;
}
/* 创建方式选择器 */
.method-selector {
padding: 30rpx;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
}
.selector-item {
display: flex;
align-items: center;
padding: 20rpx;
background-color: #f9fafb;
border-radius: 12rpx;
position: relative;
}
.selector-item.active {
background-color: #f0f4ff;
}
.selector-dot {
width: 24rpx;
height: 24rpx;
background-color: #6366f1;
border-radius: 50%;
margin-right: 16rpx;
}
.selector-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
/* 表单容器 */
.form-container {
background-color: #fff;
margin: 20rpx;
border-radius: 16rpx;
padding: 40rpx 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.form-header {
margin-bottom: 40rpx;
}
.form-title {
display: block;
font-size: 36rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.form-subtitle {
display: block;
font-size: 26rpx;
color: #999;
line-height: 1.6;
}
.form-item {
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
}
.form-label {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.label-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
margin-left: 8rpx;
}
.required-mark {
font-size: 24rpx;
color: #ef4444;
margin-left: auto;
}
.input-wrapper {
position: relative;
}
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
background-color: #f9fafb;
border: 2rpx solid #e5e7eb;
border-radius: 12rpx;
box-sizing: border-box;
transition: all 0.3s ease;
/* #ifdef H5 */
&:focus {
outline: none;
border-color: #6366f1;
background-color: #fff;
box-shadow: 0 0 0 4rpx rgba(99, 102, 241, 0.1);
}
/* #endif */
/* #ifdef MP-WEIXIN */
&:focus {
border-color: #6366f1;
background-color: #fff;
}
/* #endif */
}
.form-input::placeholder {
color: #999;
}
.form-textarea {
width: 100%;
min-height: 160rpx;
padding: 24rpx;
font-size: 28rpx;
color: #333;
background-color: #f9fafb;
border: 2rpx solid #e5e7eb;
border-radius: 12rpx;
box-sizing: border-box;
line-height: 1.6;
transition: all 0.3s ease;
/* #ifdef H5 */
&:focus {
outline: none;
border-color: #6366f1;
background-color: #fff;
box-shadow: 0 0 0 4rpx rgba(99, 102, 241, 0.1);
}
/* #endif */
/* #ifdef MP-WEIXIN */
&:focus {
border-color: #6366f1;
background-color: #fff;
}
/* #endif */
}
.form-textarea::placeholder {
color: #999;
}
/* 底部按钮容器 */
.button-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx;
background-color: #fff;
border-top: 1rpx solid #f0f0f0;
display: flex;
gap: 20rpx;
/* #ifdef H5 */
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
/* #endif */
z-index: 100;
}
.cancel-button {
flex: 1;
height: 88rpx;
background-color: #f5f5f5;
border: none;
border-radius: 12rpx;
color: #666;
font-size: 32rpx;
font-weight: 500;
/* #ifdef H5 */
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background-color: #e5e5e5;
}
/* #endif */
}
.submit-button {
flex: 2;
height: 88rpx;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border: none;
border-radius: 12rpx;
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.3);
/* #ifdef H5 */
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(:disabled) {
transform: translateY(-2rpx);
box-shadow: 0 12rpx 32rpx rgba(99, 102, 241, 0.4);
}
&:active:not(:disabled) {
transform: translateY(0);
}
/* #endif */
}
.submit-button.loading {
opacity: 0.7;
pointer-events: none;
}
.submit-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.button-text {
margin-left: 8rpx;
}
/* H5适配 */
/* #ifdef H5 */
.page-container {
padding-bottom: 180rpx;
}
.button-container {
padding: 30rpx 40rpx;
}
/* #endif */
/* 小程序适配 */
/* #ifdef MP-WEIXIN */
.button-container {
padding-bottom: env(safe-area-inset-bottom);
}
/* #endif */
/* 验证中容器 */
.validating-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.validating-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 24rpx;
}
.validating-text {
font-size: 28rpx;
color: #666;
}
/* 错误容器 */
.error-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
padding: 40rpx;
}
.error-content {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
max-width: 600rpx;
width: 100%;
}
.error-icon-wrapper {
width: 160rpx;
height: 160rpx;
border-radius: 50%;
background-color: #fef2f2;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
border: 4rpx solid #fee2e2;
}
.error-title {
font-size: 36rpx;
font-weight: 600;
color: #1f2937;
margin-bottom: 24rpx;
}
.error-message {
font-size: 28rpx;
color: #6b7280;
line-height: 1.6;
margin-bottom: 60rpx;
word-break: break-all;
}
.error-button {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border: none;
border-radius: 12rpx;
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.3);
/* #ifdef H5 */
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2rpx);
box-shadow: 0 12rpx 32rpx rgba(99, 102, 241, 0.4);
}
&:active {
transform: translateY(0);
}
/* #endif */
}
/* 响应式适配 */
@media (max-width: 375px) {
.form-container {
padding: 30rpx 20rpx;
}
.form-item {
margin-bottom: 32rpx;
}
}
</style>

319
pages/channel/list.vue Normal file
View File

@@ -0,0 +1,319 @@
<template>
<view class="page-container">
<!-- 列表内容 -->
<view class="list-container">
<u-list @scrolltolower="scrolltolower">
<u-list-item v-for="(item, index) in channelList" :key="index" class="list-item">
<view class="item-content" @click="editChannel(item)">
<view class="item-header">
<view class="item-title">{{ item.channelName || '未命名渠道' }}</view>
<view class="item-status" :class="{'status-active': item.status === 1}">
{{ item.status === 1 ? '启用' : '禁用' }}
</view>
</view>
<view class="item-info">
<view class="info-row" v-if="item.contactPhone">
<uni-icons type="phone" size="16" color="#999"></uni-icons>
<text class="info-text">{{ item.contactPhone }}</text>
</view>
<view class="info-row" v-if="item.wechatId">
<uni-icons type="chat" size="16" color="#999"></uni-icons>
<text class="info-text">{{ item.wechatId }}</text>
</view>
<view class="info-row" v-if="item.remark">
<uni-icons type="info" size="16" color="#999"></uni-icons>
<text class="info-text">{{ item.remark }}</text>
</view>
</view>
<view class="item-footer">
<text class="item-time">{{ formatTime(item.createTime) }}</text>
</view>
</view>
</u-list-item>
<u-loadmore :status="loadStatus" />
</u-list>
</view>
<!-- 底部添加按钮 -->
<view class="fab-container">
<button class="fab-button" @click="addChannel">
<uni-icons type="plus" size="24" color="#fff"></uni-icons>
<text class="fab-text">新增渠道</text>
</button>
</view>
</view>
</template>
<script>
import {
request
} from '@/utils/request';
export default {
data() {
return {
channelList: [],
page: 1,
pageSize: 20,
loadStatus: 'loadmore', // loadmore | loading | nomore
total: 0
}
},
onLoad() {
uni.setNavigationBarTitle({
title: '渠道管理'
});
this.loadChannelList();
},
onShow() {
// 从新增/编辑页返回时刷新列表
if (this.page === 1) {
this.loadChannelList();
}
},
onPullDownRefresh() {
// #ifdef MP-WEIXIN
this.page = 1;
this.channelList = [];
this.loadChannelList();
// #endif
},
methods: {
// 加载渠道列表
loadChannelList() {
this.loadStatus = 'loading';
request({
url: '/v1/frontend/business/channel/list',
data: {
page: this.page,
limit: this.pageSize
}
}).then((response) => {
// #ifdef MP-WEIXIN
uni.stopPullDownRefresh();
// #endif
if (response.data.code === 10000 || response.data.code === 200) {
const list = response.data.data.list || response.data.data.records || [];
if (this.page === 1) {
this.channelList = list;
} else {
this.channelList = [...this.channelList, ...list];
}
this.total = response.data.data.total || 0;
if (this.channelList.length >= this.total) {
this.loadStatus = 'nomore';
} else {
this.loadStatus = 'loadmore';
}
} else {
uni.showToast({
title: response.data.message || '加载失败',
icon: 'none',
duration: 2000
});
this.loadStatus = 'loadmore';
}
}).catch((error) => {
// #ifdef MP-WEIXIN
uni.stopPullDownRefresh();
// #endif
console.error('加载渠道列表失败:', error);
uni.showToast({
title: '网络错误,请重试',
icon: 'none',
duration: 2000
});
this.loadStatus = 'loadmore';
});
},
// 滚动到底部加载更多
scrolltolower() {
if (this.loadStatus === 'loadmore') {
this.page++;
this.loadChannelList();
}
},
// 新增渠道
addChannel() {
uni.navigateTo({
url: '/pages/channel/add'
});
},
// 编辑渠道
editChannel(item) {
uni.navigateTo({
url: `/pages/channel/add?id=${item.id || item.lId}`
});
},
// 格式化时间
formatTime(time) {
if (!time) return '';
const date = new Date(time);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 100rpx;
}
.list-container {
padding: 20rpx;
}
.list-item {
margin-bottom: 20rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.item-content {
padding: 30rpx;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.item-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
flex: 1;
}
.item-status {
font-size: 24rpx;
color: #999;
padding: 8rpx 16rpx;
background-color: #f5f5f5;
border-radius: 8rpx;
}
.status-active {
color: #22c55e;
background-color: #f0fdf4;
}
.item-info {
margin-bottom: 20rpx;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
}
.info-text {
font-size: 28rpx;
color: #666;
margin-left: 12rpx;
flex: 1;
}
.item-footer {
display: flex;
justify-content: flex-end;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.item-time {
font-size: 24rpx;
color: #999;
}
/* 底部悬浮按钮 */
.fab-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 20rpx;
background-color: #fff;
border-top: 1rpx solid #f0f0f0;
/* #ifdef H5 */
box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
/* #endif */
z-index: 100;
}
.fab-button {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border: none;
border-radius: 12rpx;
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.3);
/* #ifdef H5 */
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2rpx);
box-shadow: 0 12rpx 32rpx rgba(99, 102, 241, 0.4);
}
&:active {
transform: translateY(0);
}
/* #endif */
}
.fab-text {
margin-left: 12rpx;
}
/* H5适配 */
/* #ifdef H5 */
.page-container {
padding-bottom: 120rpx;
}
.fab-container {
padding: 30rpx 40rpx;
}
/* #endif */
/* 小程序适配 */
/* #ifdef MP-WEIXIN */
.fab-container {
padding-bottom: env(safe-area-inset-bottom);
}
/* #endif */
</style>

336
pages/channel/login.vue Normal file
View File

@@ -0,0 +1,336 @@
<template>
<view class="page-container">
<view class="hero">
<view class="hero-title">渠道登录</view>
<view class="hero-subtitle">登录后进入数据面板</view>
</view>
<!-- 账号密码登录H5 + 小程序通用 -->
<view class="card">
<view class="card-title">账号密码登录</view>
<uni-forms ref="h5Form" :modelValue="form">
<uni-forms-item name="phone">
<uni-easyinput
v-model="form.phone"
placeholder="请输入手机号"
type="number"
maxlength="11"
:inputBorder="false"
class="input"
/>
</uni-forms-item>
<uni-forms-item name="password">
<uni-easyinput
v-model="form.password"
placeholder="请输入密码"
:inputBorder="false"
type="password"
class="input"
/>
</uni-forms-item>
</uni-forms>
<view class="login-tip">
<text>默认初始密码为 </text>
<text class="login-tip-strong">123456</text>
<text>登录后请及时修改</text>
</view>
<button class="primary-btn" :disabled="isSubmitting" @click="handleH5Login">
<text>{{ isSubmitting ? '登录中...' : '登录' }}</text>
</button>
</view>
</view>
</template>
<script>
import config from '@/config'
export default {
data() {
return {
form: {
phone: '',
password: ''
},
isSubmitting: false,
companyId: ''
}
},
onLoad(options) {
// 获取参数
let scene = options.scene;
let companyId = options.companyId;
console.log('页面参数:', { scene, companyId });
// 小程序端解析 scene 参数
// #ifdef MP-WEIXIN
if (scene != undefined) {
// 先对 scene 进行 URL 解码(处理 %2C 等编码字符)
try {
scene = decodeURIComponent(scene);
} catch (e) {
console.warn('scene 解码失败,使用原始值:', e);
}
// scene 中可能包含 companyId或者直接就是 companyId
// 如果 scene 包含逗号,可能需要分割(根据实际业务需求调整)
if (scene.includes(',')) {
// 如果 scene 包含逗号,取第一部分作为 companyId
const parts = scene.split(',');
this.companyId = parts[0] || '';
} else {
// 如果不包含逗号scene 本身就是 companyId
this.companyId = scene;
}
} else if (companyId != undefined) {
// 兼容直接传 companyId 参数
this.companyId = companyId;
} else {
// 尝试从本地存储获取(之前可能保存过)
const storedCompanyId = uni.getStorageSync('channelCompanyId');
if (storedCompanyId) {
this.companyId = storedCompanyId;
console.log('从本地存储获取companyId:', storedCompanyId);
} else {
this.companyId = '';
}
}
// #endif
// H5 端兼容处理
// #ifdef H5
if (companyId != undefined) {
// H5 端优先使用 companyId 参数
this.companyId = companyId;
} else if (scene != undefined) {
// H5 也可能有 scene 参数(从分享链接等)
// 先对 scene 进行 URL 解码
try {
scene = decodeURIComponent(scene);
} catch (e) {
console.warn('scene 解码失败,使用原始值:', e);
}
if (scene.includes(',')) {
const parts = scene.split(',');
this.companyId = parts[0] || '';
} else {
this.companyId = scene;
}
} else {
// 尝试从本地存储获取
const storedCompanyId = uni.getStorageSync('channelCompanyId');
if (storedCompanyId) {
this.companyId = storedCompanyId;
console.log('从本地存储获取companyId:', storedCompanyId);
} else {
this.companyId = '';
}
}
// #endif
console.log('解析结果:', { companyId: this.companyId });
},
methods: {
// 账号密码登录H5 + 小程序)
handleH5Login() {
if (this.isSubmitting) return
if (!this.form.phone || !this.form.password) {
uni.showToast({ title: '请输入手机号和密码', icon: 'none', duration: 2000 })
return
}
if (!this.companyId) {
uni.showToast({ title: '缺少companyId参数', icon: 'none', duration: 2000 })
return
}
this.isSubmitting = true
// 使用和列表接口一样的请求方式和参数格式
uni.request({
method: 'POST',
url: config.apiUrl2 + '/v1/frontend/distribution/user/login',
data: {
companyId: this.companyId,
phone: this.form.phone,
password: this.form.password
},
header: {
'content-type': 'application/x-www-form-urlencoded'
},
success: (res) => {
this.isSubmitting = false
console.log('登录接口完整返回:', res)
console.log('登录接口返回数据:', res.data)
if (res.data.code === 10000 || res.data.code === 200) {
const data = res.data.data || {}
const token = data.token || ''
const channelInfo = data.channelInfo || {}
console.log('解析后的data:', data)
console.log('解析后的token:', token)
console.log('解析后的channelInfo:', channelInfo)
// 保存token
if (token) {
uni.setStorageSync('channelToken', token)
console.log('已保存token到本地存储')
} else {
console.error('token为空无法保存')
}
// 保存完整的登录数据(备用)
uni.setStorageSync('channelLoginData', JSON.stringify(data))
console.log('已保存完整登录数据到本地存储')
// 保存渠道信息 - 从channelInfo中获取
if (channelInfo.channelCode) {
uni.setStorageSync('channelCode', channelInfo.channelCode)
console.log('已保存channelCode:', channelInfo.channelCode)
} else {
console.warn('channelInfo.channelCode为空无法保存')
}
if (channelInfo.channelName) {
uni.setStorageSync('channelName', channelInfo.channelName)
console.log('已保存channelName:', channelInfo.channelName)
} else {
console.warn('channelInfo.channelName为空无法保存')
}
// 验证保存是否成功
const savedCode = uni.getStorageSync('channelCode')
const savedName = uni.getStorageSync('channelName')
const savedToken = uni.getStorageSync('channelToken')
console.log('验证保存结果:', { savedCode, savedName, savedToken })
if (!savedCode) {
console.error('警告channelCode保存失败')
uni.showToast({
title: '渠道编码保存失败,请重试',
icon: 'none',
duration: 3000
})
return
}
this.toStatistics()
} else {
const msg = res.data.message || res.data.msg || '登录失败'
console.error('登录失败:', msg)
uni.showToast({ title: msg, icon: 'none', duration: 3000 })
}
},
fail: (err) => {
console.error('登录失败', err)
this.isSubmitting = false
uni.showToast({ title: '网络错误,请重试', icon: 'none', duration: 2000 })
}
})
},
toStatistics() {
uni.redirectTo({
url: '/pages/channel/statistics'
})
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: linear-gradient(180deg, #f5f7ff 0%, #f8f8f8 100%);
padding: 40rpx 30rpx 80rpx;
box-sizing: border-box;
}
.hero {
margin-bottom: 30rpx;
}
.hero-title {
font-size: 44rpx;
font-weight: 700;
color: #1f2937;
}
.hero-subtitle {
margin-top: 8rpx;
font-size: 26rpx;
color: #6b7280;
}
.card {
background: #fff;
border-radius: 16rpx;
padding: 32rpx 28rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
margin-bottom: 24rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #111827;
margin-bottom: 24rpx;
}
.input {
background: #f5f6fa;
border-radius: 12rpx;
padding: 20rpx;
}
.login-tip {
margin-top: 16rpx;
margin-bottom: 8rpx;
font-size: 24rpx;
color: #6b7280;
line-height: 1.6;
}
.login-tip-strong {
font-weight: 600;
color: #ef4444;
}
.primary-btn {
width: 100%;
height: 90rpx;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
border: none;
border-radius: 12rpx;
color: #fff;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
margin-top: 12rpx;
box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.25);
}
.tips {
margin-top: 16rpx;
font-size: 24rpx;
color: #6b7280;
text-align: center;
}
/* #ifdef H5 */
.primary-btn {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-2rpx);
box-shadow: 0 10rpx 28rpx rgba(99, 102, 241, 0.3);
}
&:active {
transform: translateY(0);
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,177 @@
<template>
<view class="page-container">
<view class="header">
<view class="title">选择项目</view>
<view class="subtitle">请选择要查看的渠道项目</view>
</view>
<view v-if="loading" class="loading">
<uni-icons type="spinner-cycle" size="32" color="#6366f1"></uni-icons>
<text class="loading-text">加载中...</text>
</view>
<view v-else-if="projects.length === 0" class="empty">
<uni-icons type="info" size="40" color="#ccc"></uni-icons>
<text class="empty-text">暂无可选项目</text>
</view>
<view v-else class="list">
<view
v-for="(item, index) in projects"
:key="index"
class="project-card"
@click="selectProject(item)"
>
<view class="project-main">
<view class="project-name">{{ item.name || '未命名项目' }}</view>
<view class="project-code">编码{{ item.code || '--' }}</view>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
</view>
</view>
</template>
<script>
import config from '@/config'
export default {
data() {
return {
loading: false,
projects: []
}
},
onShow() {
this.fetchProjects()
},
methods: {
fetchProjects() {
const token = uni.getStorageSync('channelToken')
if (!token) {
uni.showToast({ title: '请先登录', icon: 'none', duration: 2000 })
uni.redirectTo({ url: '/pages/channel/login' })
return
}
this.loading = true
uni.request({
method: 'GET',
url: config.apiUrl2 + '/v1/frontend/distribution/channel/projects',
header: {
'content-type': 'application/x-www-form-urlencoded',
token
},
success: (res) => {
this.loading = false
if (res.data.code === 10000 || res.data.code === 200) {
this.projects = res.data.data?.list || res.data.data || []
} else {
const msg = res.data.message || res.data.msg || '加载失败'
uni.showToast({ title: msg, icon: 'none', duration: 3000 })
}
},
fail: (err) => {
console.error('加载项目失败', err)
this.loading = false
uni.showToast({ title: '网络错误,请重试', icon: 'none', duration: 2000 })
}
})
},
selectProject(item) {
const name = encodeURIComponent(item.name || '')
const code = encodeURIComponent(item.code || '')
uni.navigateTo({
url: `/pages/channel/statistics?name=${name}&code=${code}`
})
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: #f5f6fb;
padding: 32rpx 28rpx 60rpx;
box-sizing: border-box;
}
.header {
margin-bottom: 24rpx;
}
.title {
font-size: 38rpx;
font-weight: 700;
color: #111827;
}
.subtitle {
margin-top: 8rpx;
font-size: 26rpx;
color: #6b7280;
}
.loading,
.empty {
margin-top: 120rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
color: #999;
}
.loading-text,
.empty-text {
font-size: 28rpx;
color: #666;
}
.list {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-top: 8rpx;
}
.project-card {
background: #fff;
border-radius: 14rpx;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 4rpx 14rpx rgba(0, 0, 0, 0.06);
}
.project-main {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.project-name {
font-size: 32rpx;
font-weight: 600;
color: #111827;
}
.project-code {
font-size: 26rpx;
color: #6b7280;
}
/* #ifdef H5 */
.project-card {
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-2rpx);
box-shadow: 0 8rpx 18rpx rgba(0, 0, 0, 0.08);
}
}
/* #endif */
</style>

2183
pages/channel/statistics.vue Normal file

File diff suppressed because it is too large Load Diff

421
pages/form/input.vue Normal file
View File

@@ -0,0 +1,421 @@
<template>
<view class="page-container">
<!-- 表单内容 -->
<view class="form-container">
<!-- 计划名称 -->
<view class="form-group">
<text class="form-label">计划名称</text>
<view class="form-value">
<text class="value-text">{{taskname}}</text>
</view>
</view>
<!-- 手机号输入 -->
<view class="form-group">
<text class="form-label required">手机号/微信号</text>
<view class="input-wrapper">
<textarea
class="form-input"
v-model="form.phone"
:placeholder="phonePlaceholder"
:maxlength="500"
:auto-height="true"
></textarea>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-wrapper">
<button
class="submit-button"
:class="{loading: isSubmitting, success: submitSuccess}"
@click="submit"
:disabled="isSubmitting"
>
<text class="button-text">
{{submitSuccess ? '提交成功' : (isSubmitting ? '提交中...' : '提交')}}
</text>
</button>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
form: {
phone: ""
},
// 多行 placeholder 需要放在 data 中,避免模板字符串里直接换行导致小程序编译错误
phonePlaceholder: '请输入手机号或微信号,如有备注请用逗号隔开,多个用换行隔开,格式如下:18888888888,张三',
taskid: '',
taskname: '',
cid: '',
isSubmitting: false,
submitSuccess: false
}
},
onLoad(options) {
let taskId = options.id;
let cid = options.channelId;
//h5
if (taskId != undefined) {
this.taskid = taskId;
this.cid = cid;
} else {
let paramsArr = decodeURIComponent(options.scene).split('&');
this.taskid = paramsArr[0].split('=')[1];
this.cid = paramsArr[1].split('=')[1];
}
//修改标题
uni.setNavigationBarTitle({
title: "表单录入"
});
this.getPosterData();
},
methods: {
getPosterData() {
let that = this;
uni.request({
method: 'POST',
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/getone',
data: {
oldId: this.taskid
},
success: (res) => {
console.log(res)
if (res.data.code == 200) {
that.taskname = res.data.data.name
that.taskid = res.data.data.task.id
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
},
submit() {
let that = this;
// 防止重复提交
if (that.isSubmitting) {
return;
}
if (!that.form.phone.length) {
return uni.showToast({
title: '请输入手机号或微信号',
icon: 'none',
duration: 3000
});
}
// 开始提交
that.isSubmitting = true;
let data = {
id: that.taskid,
cid: that.cid,
phone: that.form.phone,
}
uni.request({
method: 'POST',
url: that.$config.apiUrl2 + '/v1/frontend/business/form/importsave',
data: data,
success: (res) => {
console.log(res)
if (res.data.code == 200) {
// 处理响应数据
that.submitSuccess = true;
that.form.phone = ""
uni.showToast({
title: res.data.message,
icon: 'success',
duration: 2000
});
// 3秒后重置成功状态
setTimeout(() => {
that.submitSuccess = false;
}, 3000);
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
},
fail: (error) => {
console.error('提交失败:', error);
uni.showToast({
title: '网络错误,请重试',
icon: 'none',
duration: 2000
});
},
complete: () => {
// 无论成功失败都重置提交状态
that.isSubmitting = false;
}
});
},
}
}
</script>
<style scoped>
/* 页面容器 */
.page-container {
min-height: 100vh;
background: #fff;
padding: 0;
}
/* 表单容器 */
.form-container {
padding: 40px 24px;
width: 100%;
height: 100vh;
background: #fff;
box-sizing: border-box;
}
/* 表单项 */
.form-group {
margin-bottom: 28px;
}
.form-label {
display: block;
font-size: 16px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 12px;
letter-spacing: 0.3px;
}
.form-label.required {
color: #e74c3c;
position: relative;
}
.form-label.required::after {
content: '';
position: absolute;
right: -8px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
background: #e74c3c;
border-radius: 50%;
}
/* 计划名称显示 */
.form-value {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px solid #e9ecef;
border-radius: 12px;
padding: 16px 20px;
min-height: 52px;
display: flex;
align-items: center;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.form-value::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.value-text {
font-size: 16px;
color: #2c3e50;
font-weight: 600;
letter-spacing: 0.2px;
}
/* 输入框包装器 */
.input-wrapper {
position: relative;
}
.form-input {
width: 100%;
min-height: 120px;
padding: 20px;
font-size: 16px;
color: #2c3e50;
background: #fff;
border: 2px solid #e9ecef;
border-radius: 12px;
outline: none;
resize: none;
line-height: 1.6;
transition: all 0.3s ease;
font-family: inherit;
box-sizing: border-box;
position: relative;
}
.form-input:focus {
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
background: #fafbfc;
}
.form-input::placeholder {
color: #95a5a6;
font-size: 14px;
font-weight: 400;
letter-spacing: 0.2px;
}
/* 提交按钮区域 */
.submit-wrapper {
margin-top: 40px;
}
.submit-button {
width: 100%;
height: 56px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 16px;
color: #fff;
font-size: 18px;
font-weight: 600;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: hidden;
letter-spacing: 0.5px;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
}
.submit-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.submit-button:hover:not(:disabled) {
transform: translateY(-3px);
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.4);
}
.submit-button:active:not(:disabled) {
transform: translateY(-1px);
}
.submit-button:active::before {
left: 100%;
}
.submit-button.loading {
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
pointer-events: none;
box-shadow: 0 8px 24px rgba(149, 165, 166, 0.3);
}
.submit-button.success {
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
box-shadow: 0 8px 24px rgba(39, 174, 96, 0.3);
}
.submit-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.button-text {
color: #fff;
font-size: 18px;
font-weight: 600;
letter-spacing: 0.5px;
}
/* 响应式设计 */
@media (max-width: 375px) {
.form-container {
padding: 32px 20px;
}
.form-group {
margin-bottom: 24px;
}
.submit-wrapper {
margin-top: 32px;
}
.submit-button {
height: 50px;
font-size: 16px;
}
}
/* 优雅的动画效果 */
.form-container {
animation: slideInUp 0.6s ease-out;
}
.form-group {
animation: fadeInUp 0.5s ease-out;
}
.form-group:nth-child(1) {
animation-delay: 0.2s;
}
.form-group:nth-child(2) {
animation-delay: 0.4s;
}
.submit-wrapper {
animation: fadeInUp 0.5s ease-out 0.6s both;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

424
pages/form/input2.vue Normal file
View File

@@ -0,0 +1,424 @@
<template>
<view class="page-container">
<!-- 表单内容 -->
<view class="form-container">
<!-- 计划名称 -->
<view class="form-group">
<text class="form-label">计划名称</text>
<view class="form-value">
<text class="value-text">{{taskname}}</text>
</view>
</view>
<!-- 手机号输入 -->
<view class="form-group">
<text class="form-label required">手机号/微信号</text>
<view class="input-wrapper">
<textarea
class="form-input"
v-model="form.phone"
:placeholder="phonePlaceholder"
:maxlength="500"
:auto-height="true"
></textarea>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-wrapper">
<button
class="submit-button"
:class="{loading: isSubmitting, success: submitSuccess}"
@click="submit"
:disabled="isSubmitting"
>
<text class="button-text">
{{submitSuccess ? '提交成功' : (isSubmitting ? '提交中...' : '提交')}}
</text>
</button>
</view>
</view>
</view>
</template>
<script>
import {
request
} from '@/utils/request';
export default {
data() {
return {
form: {
phone: ""
},
// 多行 placeholder 需要放在 data 中,避免模板字符串里直接换行导致小程序编译错误
phonePlaceholder: '请输入手机号或微信号,如有备注请用逗号隔开,多个用换行隔开,格式如下:18888888888,张三',
token: "test",
taskid: '',
taskname: '',
cid: '',
isSubmitting: false,
submitSuccess: false
}
},
onLoad(options) {
let taskId = options.id;
let cid = options.channelId;
//h5
if (taskId != undefined) {
this.taskid = taskId;
this.cid = cid;
} else {
let paramsArr = decodeURIComponent(options.scene).split('&');
this.taskid = paramsArr[0].split('=')[1];
this.cid = paramsArr[1].split('=')[1];
}
//修改标题
uni.setNavigationBarTitle({
title: "表单录入"
});
this.getPosterData();
},
methods: {
getPosterData() {
let that = this;
uni.request({
method: 'POST',
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/getone',
data: {
id: that.taskid,
},
success: (res) => {
console.log(res)
if (res.data.code == 200) {
that.taskname = res.data.data.name
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
},
submit() {
let that = this;
// 防止重复提交
if (that.isSubmitting) {
return;
}
if (!that.form.phone.length) {
return uni.showToast({
title: '请输入手机号或微信号',
icon: 'none',
duration: 3000
});
}
// 开始提交
that.isSubmitting = true;
let data = {
id: that.taskid,
cid: that.cid,
phone: that.form.phone,
}
uni.request({
method: 'POST',
url: that.$config.apiUrl2 + '/v1/frontend/business/form/importsave',
data: data,
success: (res) => {
console.log(res)
if (res.data.code == 200) {
// 处理响应数据
that.submitSuccess = true;
that.form.phone = ""
uni.showToast({
title: res.data.message,
icon: 'success',
duration: 2000
});
// 3秒后重置成功状态
setTimeout(() => {
that.submitSuccess = false;
}, 3000);
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
},
fail: (error) => {
console.error('提交失败:', error);
uni.showToast({
title: '网络错误,请重试',
icon: 'none',
duration: 2000
});
},
complete: () => {
// 无论成功失败都重置提交状态
that.isSubmitting = false;
}
});
},
}
}
</script>
<style scoped>
/* 页面容器 */
.page-container {
min-height: 100vh;
background: #fff;
padding: 0;
}
/* 表单容器 */
.form-container {
padding: 40px 24px;
width: 100%;
height: 100vh;
background: #fff;
box-sizing: border-box;
}
/* 表单项 */
.form-group {
margin-bottom: 28px;
}
.form-label {
display: block;
font-size: 16px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 12px;
letter-spacing: 0.3px;
}
.form-label.required {
color: #e74c3c;
position: relative;
}
.form-label.required::after {
content: '';
position: absolute;
right: -8px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
background: #e74c3c;
border-radius: 50%;
}
/* 计划名称显示 */
.form-value {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border: 2px solid #e9ecef;
border-radius: 12px;
padding: 16px 20px;
min-height: 52px;
display: flex;
align-items: center;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.form-value::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.value-text {
font-size: 16px;
color: #2c3e50;
font-weight: 600;
letter-spacing: 0.2px;
}
/* 输入框包装器 */
.input-wrapper {
position: relative;
}
.form-input {
width: 100%;
min-height: 120px;
padding: 20px;
font-size: 16px;
color: #2c3e50;
background: #fff;
border: 2px solid #e9ecef;
border-radius: 12px;
outline: none;
resize: none;
line-height: 1.6;
transition: all 0.3s ease;
font-family: inherit;
box-sizing: border-box;
position: relative;
}
.form-input:focus {
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
transform: translateY(-2px);
background: #fafbfc;
}
.form-input::placeholder {
color: #95a5a6;
font-size: 14px;
font-weight: 400;
letter-spacing: 0.2px;
}
/* 提交按钮区域 */
.submit-wrapper {
margin-top: 40px;
}
.submit-button {
width: 100%;
height: 56px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 16px;
color: #fff;
font-size: 18px;
font-weight: 600;
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: hidden;
letter-spacing: 0.5px;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3);
}
.submit-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.submit-button:hover:not(:disabled) {
transform: translateY(-3px);
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.4);
}
.submit-button:active:not(:disabled) {
transform: translateY(-1px);
}
.submit-button:active::before {
left: 100%;
}
.submit-button.loading {
background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
pointer-events: none;
box-shadow: 0 8px 24px rgba(149, 165, 166, 0.3);
}
.submit-button.success {
background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%);
box-shadow: 0 8px 24px rgba(39, 174, 96, 0.3);
}
.submit-button:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.button-text {
color: #fff;
font-size: 18px;
font-weight: 600;
letter-spacing: 0.5px;
}
/* 响应式设计 */
@media (max-width: 375px) {
.form-container {
padding: 32px 20px;
}
.form-group {
margin-bottom: 24px;
}
.submit-wrapper {
margin-top: 32px;
}
.submit-button {
height: 50px;
font-size: 16px;
}
}
/* 优雅的动画效果 */
.form-container {
animation: slideInUp 0.6s ease-out;
}
.form-group {
animation: fadeInUp 0.5s ease-out;
}
.form-group:nth-child(1) {
animation-delay: 0.2s;
}
.form-group:nth-child(2) {
animation-delay: 0.4s;
}
.submit-wrapper {
animation: fadeInUp 0.5s ease-out 0.6s both;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

113
pages/form/list.vue Normal file
View File

@@ -0,0 +1,113 @@
<template>
<view class="u-page">
<u-list @scrolltolower="scrolltolower">
<u-list-item v-for="(item, index) in dataList" :key="index" class="custom-list-item">
<view class="list-item-content">
<u-cell :title="item.sPhoneOrWechatId" class="cell-item" :border="false" />
<view class="additional-info">
<text class="item-time">{{ item.dNewTime }}</text>
<text class="item-status">{{ item.status }}</text>
</view>
</view>
</u-list-item>
<u-loadmore :status="status" />
</u-list>
</view>
</template>
<script>
import {
request
} from '@/utils/request';
export default {
data() {
return {
status: "",
page: 1,
limit: 20,
dataList: []
}
},
methods: {
scrolltolower() {
this.page++
this.loadmore()
},
loadmore() {
this.status = "loading"
request({
url: '/v1/frontend/business/form/listdata',
data: {
page: this.page,
limit: this.limit
},
// ...
}).then((response) => {
for (let i = 0; i < response.data.data.list.length; i++) {
let data = response.data.data.list[i]
data.status = "333"
this.dataList.push(data)
}
if (response.data.data.list.length == 0) {
this.status = "nomore"
} else {
this.status = ""
}
}).catch((error) => {
// 处理错误
});
}
},
onLoad() {
uni.setNavigationBarTitle({
title: "我的表单"
});
this.loadmore()
}
}
</script>
<style>
.custom-list-item {
border-bottom: 1px solid #e5e5e5;
}
.list-item-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
}
.cell-item {
border-bottom: none;
font-size: 16px;
/* 标题字体大小 */
color: #333;
/* 标题字体颜色 */
}
.additional-info {
display: flex;
flex-direction: column;
align-items: flex-end;
}
.item-time {
color: #666;
/* 时间的颜色 */
font-size: 12px;
/* 时间的字体大小 */
}
.item-status {
color: #1989fa;
/* 状态的颜色,可根据实际需求调整 */
font-size: 14px;
/* 状态的字体大小 */
}
</style>

22
pages/home/home.vue Normal file
View File

@@ -0,0 +1,22 @@
<template>
<view>
</view>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
</style>

912
pages/index/index.vue Normal file
View File

@@ -0,0 +1,912 @@
<template>
<view class="container">
<!-- Header Navigation -->
<view class="header-nav">
<view class="nav-content">
<view class="nav-title">存客宝</view>
<view class="nav-avatar" @click="goToLogin">
<image class="avatar-img" src="/static/tabbar/me.png" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- Hero Section -->
<view class="hero-section">
<view class="hero-content">
<view class="badge">场景化客户留存 SaaS 工具</view>
<view class="hero-title">
<text class="title-main">存客宝</text>
<text class="title-sub">私域流量运营专家</text>
</view>
<view class="hero-desc">
专注于场景化客户留存的私域流量运营工具帮助中小企业将多平台公域流量高效转化为私域客户资产通过数据驱动的精细化运营提升客户粘性和生命周期价值
</view>
</view>
</view>
<!-- 立即体验按钮 -->
<view class="cta-button-section">
<button class="cta-button" @click="showDialogBtn">
<text class="button-text">立即体验</text>
<text class="arrow"></text>
</button>
</view>
<!-- Core Values Section -->
<view class="section values-section">
<view class="section-header">
<view class="section-title">核心价值与优势</view>
<view class="section-desc">通过统一管理智能触达和数据驱动全面提升企业私域运营效率</view>
</view>
<view class="values-grid">
<view class="value-card">
<view class="card-icon users-icon">👥</view>
<view class="card-title">统一管理 (存客)</view>
<view class="card-desc">一站式平台统一存储管理和追踪来自各渠道的客户数据及关联的微信号状态告别数据分散管理困难</view>
</view>
<view class="value-card">
<view class="card-icon target-icon">🎯</view>
<view class="card-title">智能触达 (获客)</view>
<view class="card-desc">场景化获客配置打通线上线下流量入口利用AI智能内容库和多平台工作台高效触达转化客户</view>
</view>
<view class="value-card">
<view class="card-icon chart-icon">📊</view>
<view class="card-title">数据驱动 ()</view>
<view class="card-desc">通过数据统计与分析量化运营效果优化营销策略实现营销活动的标准化和数据化决策</view>
</view>
</view>
</view>
<!-- Functional Modules Section -->
<view class="section modules-section">
<view class="section-header">
<view class="section-title">五大核心功能模块</view>
<view class="section-desc">从数据概览到客户获取从内容管理到多平台运营全方位覆盖私域运营需求</view>
</view>
<view class="modules-grid">
<view class="module-card primary-border">
<view class="module-header">
<view class="module-icon primary-bg">📊</view>
<view class="module-title">数据概览</view>
</view>
<view class="module-desc">实时展示微信号总数健康度新增好友数等关键指标提供运营全局概览和数据可视化分析</view>
</view>
<view class="module-card secondary-border">
<view class="module-header">
<view class="module-icon secondary-bg">🌐</view>
<view class="module-title">场景化获客</view>
</view>
<view class="module-desc">标准化多渠道获客入口配置支持抖音小红书公众号海报付款码等多种获客场景</view>
</view>
<view class="module-card accent-border">
<view class="module-header">
<view class="module-icon accent-bg">🧠</view>
<view class="module-title">智能内容库</view>
</view>
<view class="module-desc">多源内容采集智能分类标签AI分析改写提供内容集中管理和API接口开放能力</view>
</view>
<view class="module-card orange-border">
<view class="module-header">
<view class="module-icon orange-bg"></view>
<view class="module-title">运营工作台</view>
</view>
<view class="module-desc">社交矩阵运营操作中心支持多渠道内容推送跨设备同步智能优化建议和任务调度</view>
</view>
<view class="module-card red-border">
<view class="module-header">
<view class="module-icon red-bg"></view>
<view class="module-title">设备监控</view>
</view>
<view class="module-desc">实时监控设备状态微信号健康度支持微信号替换后的数据继承和异常预警机制</view>
</view>
</view>
</view>
<!-- Scenario Examples Section -->
<view class="section scenarios-section">
<view class="section-header">
<view class="section-title">应用场景示例</view>
<view class="section-desc">不同行业和业务场景下的实际应用案例</view>
</view>
<view class="scenarios-grid">
<view class="scenario-card">
<view class="scenario-badge">电商零售</view>
<view class="scenario-title">多平台流量整合</view>
<view class="scenario-desc">某服装品牌通过存客宝整合抖音小红书微信公众号等多平台流量统一管理客户数据通过场景化获客配置提升转化率30%客户复购率提升45%</view>
<view class="scenario-tags">
<text class="tag">抖音获客</text>
<text class="tag">内容分发</text>
<text class="tag">客户留存</text>
</view>
</view>
<view class="scenario-card">
<view class="scenario-badge">教育培训</view>
<view class="scenario-title">智能内容运营</view>
<view class="scenario-desc">某在线教育机构利用AI内容改写功能将课程内容智能适配不同平台特点内容生产效率提升60%学员互动率提升35%课程转化率显著提升</view>
<view class="scenario-tags">
<text class="tag">AI改写</text>
<text class="tag">多平台推送</text>
<text class="tag">数据分析</text>
</view>
</view>
</view>
</view>
<!-- CTA Section -->
<view class="cta-section">
<view class="cta-content">
<view class="cta-title">开始您的私域运营之旅</view>
<view class="cta-desc">加入数千家企业的选择用存客宝提升您的客户留存和运营效率</view>
<button class="cta-main-button" @click="showDialogBtn">
<text class="button-text" style='color:#333;'>立即体验</text>
<text class="arrow" style='color:#333;'></text>
</button>
<button class="cta-secondary-button" @click="goToSite">
<text class="button-text">访问老游条电竞学院</text>
<text class="arrow"></text>
</button>
<view class="stats-grid">
<view class="stat-item">
<view class="stat-number">1000+</view>
<view class="stat-label">企业用户</view>
</view>
<view class="stat-item">
<view class="stat-number">50+</view>
<view class="stat-label">管理客户</view>
</view>
<view class="stat-item">
<view class="stat-number">30%</view>
<view class="stat-label">转化提升</view>
</view>
<view class="stat-item">
<view class="stat-number">24/7</view>
<view class="stat-label">技术支持</view>
</view>
</view>
</view>
</view>
<!-- Footer -->
<view class="footer">
<view class="footer-text">© {{currentYear}} 存客宝. 保留所有权利 | 遵循"事事有回应,件件有结果,日日精进"的服务理念</view>
</view>
<!-- 弹窗遮罩 -->
<view v-if="showDialog" class="dialog-overlay" @click="hideDialog">
<view class="dialog-content" @click.stop>
<view class="dialog-header">
<view class="dialog-title">申请体验存客宝</view>
<view class="dialog-desc">我们的客服团队将在24小时内为您创建专属体验账号并提供一对一的产品演示和使用指导</view>
</view>
<view class="dialog-form">
<view class="form-item">
<view class="form-label">手机号</view>
<input class="form-input" v-model="phoneNumber" placeholder="请输入手机号" type="number" maxlength="11" />
</view>
</view>
<view class="dialog-buttons">
<button class="dialog-button cancel-button" @click="hideDialog">取消</button>
<button class="dialog-button confirm-button" @click="confirmBtn">确认</button>
</view>
</view>
</view>
<!-- 成功提示 -->
<view v-if="showSuccessMessage" class="success-toast">
<text class="success-text">申请已提交</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
showDialog: false,
showSuccessMessage: false,
taskid: 127,
currentYear: new Date().getFullYear(),
phoneNumber: ''
}
},
methods: {
showDialogBtn() {
this.showDialog = true
},
hideDialog() {
this.showDialog = false
},
goToLogin() {
uni.navigateTo({
url: '/pages/login/login'
})
},
goToSite() {
uni.navigateTo({
url: '/pages/index/site'
})
},
confirmBtn() {
let that = this;
if (!this.phoneNumber) {
uni.showToast({
title: '请输入手机号',
icon: 'none',
duration: 2000
})
return
}else{
uni.showLoading({
title: '请求中...',
mask: true
})
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/decryptphones',
data: {
id: that.taskid,
phone: this.phoneNumber
},
success: (res) => {
uni.hideLoading();
if (res.data.code == 200) {
this.showDialog = false
uni.showModal({
title: '提示',
content: '申请成功',
success: function(res) {
console.log(res)
}
})
this.showDialog = false
} else {
uni.showToast({
title: '申请失败',
icon: 'none',
duration: 2000
});
}
}
});
}
},
// 获取用户手机号
getPhoneNumber(res) {
let that = this;
if (res.detail.errMsg == "getPhoneNumber:ok") {
uni.showLoading({
title: '请求中...',
mask: true
})
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/decryptphone',
data: {
id: that.taskid,
code: res.detail.code
},
success: (res) => {
uni.hideLoading();
if (res.data.code == 10000) {
uni.showModal({
title: '提示',
content: that.task.sTip,
success: function(res) {
console.log(res)
}
})
this.showDialog = false
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
}
},
}
}
</script>
<style lang="scss">
.container {
min-height: 100vh;
background-color: #ffffff;
}
/* Header Navigation */
.header-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10rpx);
border-bottom: 1rpx solid rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx 32rpx;
height: 88rpx;
}
.nav-title {
font-size: 36rpx;
font-weight: bold;
color: #6366f1;
}
.nav-avatar {
width: 64rpx;
height: 64rpx;
border-radius: 50%;
overflow: hidden;
border: 2rpx solid #e5e7eb;
cursor: pointer;
}
.avatar-img {
width: 100%;
height: 100%;
}
/* Hero Section */
.hero-section {
margin-top: calc(88rpx + var(--status-bar-height));
background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, #ffffff 50%, rgba(168, 85, 247, 0.05) 100%);
padding: 80rpx 32rpx 120rpx;
text-align: center;
}
.hero-content {
max-width: 1200rpx;
margin: 0 auto;
}
.badge {
display: inline-block;
background-color: rgba(99, 102, 241, 0.1);
color: #6366f1;
padding: 12rpx 24rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
margin-bottom: 32rpx;
}
.hero-title {
margin-bottom: 32rpx;
}
.title-main {
display: block;
font-size: 80rpx;
font-weight: bold;
color: #1f2937;
line-height: 1.2;
}
.title-sub {
display: block;
font-size: 64rpx;
font-weight: bold;
color: #6366f1;
margin-top: 16rpx;
}
.hero-desc {
font-size: 32rpx;
color: #6b7280;
line-height: 1.6;
max-width: 1000rpx;
margin: 0 auto;
}
/* CTA Button Section */
.cta-button-section {
padding: 64rpx 32rpx;
text-align: center;
background-color: #ffffff;
}
.cta-button {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
border: none;
border-radius: 12rpx;
padding: 24rpx 64rpx;
font-size: 32rpx;
font-weight: 600;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 32rpx rgba(99, 102, 241, 0.3);
}
.button-text {
color: white;
}
.arrow {
margin-left: 16rpx;
color: white;
}
/* Section Styles */
.section {
padding: 120rpx 32rpx;
}
.values-section {
background-color: rgba(249, 250, 251, 0.5);
}
.section-header {
text-align: center;
margin-bottom: 80rpx;
}
.section-title {
font-size: 64rpx;
font-weight: bold;
color: #1f2937;
margin-bottom: 24rpx;
}
.section-desc {
font-size: 32rpx;
color: #6b7280;
line-height: 1.6;
max-width: 800rpx;
margin: 0 auto;
}
/* Values Grid */
.values-grid {
display: flex;
flex-direction: column;
gap: 48rpx;
}
.value-card {
background: white;
border-radius: 16rpx;
padding: 48rpx 32rpx;
text-align: center;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
}
.card-icon {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24rpx;
font-size: 48rpx;
}
.users-icon {
background-color: rgba(99, 102, 241, 0.1);
}
.target-icon {
background-color: rgba(168, 85, 247, 0.1);
}
.chart-icon {
background-color: rgba(34, 197, 94, 0.1);
}
.card-title {
font-size: 36rpx;
font-weight: bold;
color: #1f2937;
margin-bottom: 16rpx;
}
.card-desc {
font-size: 28rpx;
color: #6b7280;
line-height: 1.6;
}
/* Modules Grid */
.modules-grid {
display: flex;
flex-direction: column;
gap: 32rpx;
}
.module-card {
background: white;
border-radius: 12rpx;
padding: 32rpx;
border-left: 8rpx solid;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
}
.primary-border { border-left-color: #6366f1; }
.secondary-border { border-left-color: #8b5cf6; }
.accent-border { border-left-color: #22c55e; }
.orange-border { border-left-color: #f97316; }
.red-border { border-left-color: #ef4444; }
.module-header {
display: flex;
align-items: center;
gap: 24rpx;
margin-bottom: 16rpx;
}
.module-icon {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
}
.primary-bg { background-color: rgba(99, 102, 241, 0.1); }
.secondary-bg { background-color: rgba(139, 92, 246, 0.1); }
.accent-bg { background-color: rgba(34, 197, 94, 0.1); }
.orange-bg { background-color: rgba(249, 115, 22, 0.1); }
.red-bg { background-color: rgba(239, 68, 68, 0.1); }
.module-title {
font-size: 32rpx;
font-weight: 600;
color: #1f2937;
}
.module-desc {
font-size: 26rpx;
color: #6b7280;
line-height: 1.6;
}
/* Scenarios Section */
.scenarios-section {
background-color: rgba(249, 250, 251, 0.5);
}
.scenarios-grid {
display: flex;
flex-direction: column;
gap: 48rpx;
}
.scenario-card {
background: white;
border-radius: 16rpx;
padding: 48rpx 32rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
}
.scenario-badge {
display: inline-block;
background-color: rgba(168, 85, 247, 0.1);
color: #8b5cf6;
padding: 8rpx 16rpx;
border-radius: 12rpx;
font-size: 22rpx;
margin-bottom: 16rpx;
}
.scenario-title {
font-size: 36rpx;
font-weight: bold;
color: #1f2937;
margin-bottom: 24rpx;
}
.scenario-desc {
font-size: 28rpx;
color: #6b7280;
line-height: 1.6;
margin-bottom: 32rpx;
}
.scenario-tags {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.tag {
background-color: rgba(99, 102, 241, 0.1);
color: #6366f1;
padding: 8rpx 16rpx;
border-radius: 12rpx;
font-size: 22rpx;
}
/* CTA Section */
.cta-section {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
padding: 120rpx 32rpx;
}
.cta-content {
text-align: center;
max-width: 1000rpx;
margin: 0 auto;
}
.cta-title {
font-size: 64rpx;
font-weight: bold;
color: white;
margin-bottom: 32rpx;
}
.cta-desc {
font-size: 32rpx;
color: rgba(255, 255, 255, 0.9);
line-height: 1.6;
margin-bottom: 64rpx;
}
.cta-main-button {
background: white;
color: #6366f1;
border: none;
border-radius: 12rpx;
padding: 24rpx 64rpx;
font-size: 32rpx;
font-weight: 600;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 32rpx;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.1);
}
.cta-secondary-button {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 2rpx solid rgba(255, 255, 255, 0.3);
border-radius: 12rpx;
padding: 20rpx 48rpx;
font-size: 28rpx;
font-weight: 500;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 64rpx;
backdrop-filter: blur(10rpx);
}
.cta-secondary-button .button-text {
margin-right: 8rpx;
}
.cta-secondary-button .arrow {
font-size: 24rpx;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32rpx;
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 48rpx;
font-weight: bold;
color: white;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
/* Footer */
.footer {
padding: 64rpx 32rpx;
background-color: #f9fafb;
}
.footer-text {
text-align: center;
font-size: 24rpx;
color: #6b7280;
}
/* Dialog Overlay */
.dialog-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: 9998;
padding: 32rpx;
}
/* Dialog Styles */
.dialog-content {
background: white;
border-radius: 16rpx;
padding: 48rpx 32rpx;
max-width: 600rpx;
width: 100%;
box-shadow: 0 16rpx 64rpx rgba(0, 0, 0, 0.2);
}
.dialog-header {
margin-bottom: 32rpx;
}
.dialog-form {
margin-bottom: 32rpx;
}
.form-item {
margin-bottom: 24rpx;
}
.form-label {
font-size: 28rpx;
color: #374151;
margin-bottom: 12rpx;
font-weight: 500;
}
.form-input {
width: 100%;
height: 88rpx;
border: 2rpx solid #e5e7eb;
border-radius: 8rpx;
padding: 0 24rpx;
font-size: 28rpx;
color: #1f2937;
background-color: #ffffff;
box-sizing: border-box;
}
.form-input:focus {
border-color: #6366f1;
outline: none;
}
.dialog-title {
font-size: 40rpx;
font-weight: bold;
color: #1f2937;
margin-bottom: 16rpx;
text-align: center;
}
.dialog-desc {
font-size: 28rpx;
color: #6b7280;
line-height: 1.6;
text-align: center;
}
.dialog-buttons {
display: flex;
gap: 24rpx;
}
.dialog-button {
flex: 1;
padding: 24rpx;
border-radius: 12rpx;
font-size: 32rpx;
font-weight: 600;
border: none;
}
.cancel-button {
background: transparent;
color: #6b7280;
border: 2rpx solid #e5e7eb;
}
.confirm-button {
background: #6366f1;
color: white;
}
/* Success Toast */
.success-toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(34, 197, 94, 0.9);
color: white;
padding: 32rpx 48rpx;
border-radius: 16rpx;
z-index: 9999;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
}
.success-text {
font-size: 32rpx;
font-weight: 600;
color: white;
}
/* 响应式适配 */
@media (min-width: 768rpx) {
.values-grid,
.scenarios-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48rpx;
}
.modules-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32rpx;
}
.stats-grid {
grid-template-columns: repeat(4, 1fr);
}
}
@media (min-width: 1024rpx) {
.values-grid {
grid-template-columns: repeat(3, 1fr);
}
.modules-grid {
grid-template-columns: repeat(3, 1fr);
}
}
</style>

167
pages/index/site.vue Normal file
View File

@@ -0,0 +1,167 @@
<template>
<view class="webview-container">
<!-- 头部导航栏 -->
<view class="header-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<text class="nav-title">老游条电竞学院</text>
<view class="nav-right"></view>
</view>
</view>
<!-- WebView 内容区域 -->
<view class="webview-content" :style="{ marginTop: (statusBarHeight + 44) + 'px' }">
<web-view :src="webUrl" @message="onMessage" @error="onError" @load="onLoad"></web-view>
</view>
<!-- 加载提示 -->
<view v-if="loading" class="loading-overlay">
<view class="loading-content">
<uni-icons type="spinner-cycle" size="30" color="#007AFF"></uni-icons>
<text class="loading-text">加载中...</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
webUrl: 'https://www.lytiao.com/',
loading: true,
statusBarHeight: 0
}
},
onLoad() {
// 获取状态栏高度
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight || 0
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack({
delta: 1
})
},
// WebView 加载完成
onLoad(event) {
console.log('WebView 加载完成:', event)
this.loading = false
},
// WebView 加载错误
onError(event) {
console.error('WebView 加载错误:', event)
this.loading = false
uni.showToast({
title: '页面加载失败',
icon: 'none',
duration: 2000
})
},
// 接收 WebView 消息
onMessage(event) {
console.log('收到 WebView 消息:', event.detail.data)
}
}
}
</script>
<style scoped>
.webview-container {
width: 100%;
height: 100vh;
background-color: #fff;
}
.header-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid #e5e5e5;
}
.nav-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 44px;
padding: 0 16px;
}
.nav-left {
display: flex;
align-items: center;
cursor: pointer;
}
.back-text {
margin-left: 4px;
font-size: 16px;
color: #333;
}
.nav-title {
font-size: 18px;
font-weight: 600;
color: #333;
text-align: center;
flex: 1;
}
.nav-right {
width: 60px;
}
.webview-content {
width: 100%;
height: calc(100vh - 44px);
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
}
.loading-content {
display: flex;
flex-direction: column;
align-items: center;
}
.loading-text {
margin-top: 12px;
font-size: 14px;
color: #666;
}
/* 适配不同平台 */
/* #ifdef MP-WEIXIN */
.webview-content {
height: calc(100vh - 88px);
}
/* #endif */
/* #ifdef H5 */
.header-nav {
background-color: #fff;
}
/* #endif */
</style>

126
pages/login/login-page.scss Normal file
View File

@@ -0,0 +1,126 @@
// 隐藏 edge 浏览器的密码查看按钮
/* #ifdef H5 */
.input-box ::v-deep{
.uni-input-input[type="password"] {
&::-ms-reveal {
display: none;
}
}
}
/* #endif */
.uni-content {
padding: 0 60rpx;
}
.login-logo {
display: none;
}
/* #ifndef APP-NVUE */
@media screen and (min-width: 690px) {
.uni-content {
/* #ifndef H5 */
padding: 0;
max-width: 300px;
margin-left: calc(50% - 200px);
/* #endif */
/* #ifdef H5 */
margin: 0 auto;
position: relative;
top: 100px;
padding: 30px 40px 80px 40px;
max-width: 450px;
max-height: 450px;
border-radius: 10px;
box-shadow: 0 0 20px #efefef;
background-color: #FFF;
/* #endif */
}
/* #ifdef H5 */
.login-logo {
display: flex;
justify-content: center;
}
.login-logo image {
width: 60px;
height: 60px;
}
.register-back{
display: none;
}
uni-button{
padding-bottom: 1px;
}
/* #endif */
}
.uni-content view {
box-sizing: border-box;
}
/* #endif */
.title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding: 18px 0;
font-weight: 800;
flex-direction: column;
}
.tip {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
color: #BDBDC0;
font-size: 11px;
margin: 6px 0;
}
/* #ifndef APP-NVUE */
// 解决小程序端开启虚拟节点virtualHost引起的 class = input-box丢失的问题 [详情参考](https://uniapp.dcloud.net.cn/matter.html#%E5%90%84%E5%AE%B6%E5%B0%8F%E7%A8%8B%E5%BA%8F%E5%AE%9E%E7%8E%B0%E6%9C%BA%E5%88%B6%E4%B8%8D%E5%90%8C-%E5%8F%AF%E8%83%BD%E5%AD%98%E5%9C%A8%E7%9A%84%E5%B9%B3%E5%8F%B0%E5%85%BC%E5%AE%B9%E9%97%AE%E9%A2%98)
.uni-content ::v-deep .uni-easyinput__content,
/* #endif */
.input-box {
height: 44px;
background-color: #F8F8F8 !important;
border-radius: 0;
font-size: 14px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
}
.link {
color: #04498c;
cursor: pointer;
}
.uni-content ::v-deep .uni-forms-item__inner {
padding-bottom: 8px;
}
.uni-btn {
text-align: center;
height: 40px;
line-height: 40px;
margin: 15px 0 10px 0;
color: #FFF !important;
border-radius: 5px;
font-size: 16px;
}
.uni-body.uni_modules-uni-id-pages-pages-login-login-withoutpwd{
height: auto !important;
}

197
pages/login/login.vue Normal file
View File

@@ -0,0 +1,197 @@
<!-- 账号密码登录页 -->
<template>
<view class="uni-content">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">账号密码登录</text>
<uni-forms>
<uni-forms-item name="username">
<uni-easyinput :focus="focusUsername" @blur="focusUsername = false" class="input-box"
:inputBorder="false" v-model="username" placeholder="请输入手机号/用户名/邮箱" />
</uni-forms-item>
<uni-forms-item name="password">
<uni-easyinput :focus="focusPassword" @blur="focusPassword = false" class="input-box" clearable
type="password" :inputBorder="false" v-model="password" placeholder="请输入密码" />
</uni-forms-item>
</uni-forms>
<view class="private">
<radio-group @click="radioChange">
<radio :value="val" :checked="val===1">
<view class="privateRadio">
<view>阅读并同意存客宝</view>
<view><navigator class="link" open-type="navigate" url="/pages/login/privatememo">隐私政策</navigator></view>
<view></view>
<view><navigator class="link" url="/pages/login/usermemo">用户服务协议</navigator></view>
</view>
</radio>
</radio-group>
</view>
<button class="uni-btn" type="primary" @click="pwdLogin">登录</button>
</view>
</template>
<script>
export default {
data() {
return {
"redirectTo": "",
"password": "",
"username": "",
"captcha": "",
"needCaptcha": false,
"focusUsername": false,
"focusPassword": false,
"logo": "/static/logo.png",
"val": 0
}
},
onShow() {
// #ifdef H5
document.onkeydown = event => {
var e = event || window.event;
if (e && e.keyCode == 13) { //回车键的键值为13
this.pwdLogin()
}
};
// #endif
},
onLoad(options) {
// 获取传递过来的redirect参数
this.redirectTo = options.redirect ? decodeURIComponent(options.redirect) : null;
if (!this.redirectTo.startsWith("/")) {
this.redirectTo = "/" + this.redirectTo
}
},
methods: {
radioChange(e){
if(this.val==0){
this.val=1;
}else{
this.val=0;
}
uni.setStorageSync('agreePrivate', this.val);
},
/**
* 密码登录
*/
pwdLogin() {
let agreePrivate=uni.getStorageSync('agreePrivate');
if(agreePrivate==0){
uni.showToast({
title: '还未阅读并同意存客宝协议',
icon: 'none',
duration: 1000
});
return;
}
if (!this.password.length) {
this.focusPassword = true
return uni.showToast({
title: '请输入密码',
icon: 'none',
duration: 3000
});
}
if (!this.username.length) {
this.focusUsername = true
return uni.showToast({
title: '请输入手机号/用户名/邮箱',
icon: 'none',
duration: 3000
});
}
let data = {
"password": this.password,
"username": this.username
}
let that = this
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
},
url: this.$config.apiUrl + '/v1/frontend/business/user/login',
data: data,
success: (res) => {
if (res.data.code == 10000) {
try {
uni.setStorageSync('token', res.data.data.token);
console.log(that.redirectTo)
if (!that.redirectTo || that.redirectTo.includes('pages/ucenter/ucenter')) {
uni.redirectTo({
url: "/pages/ucenter/ucenter"
})
} else {
uni.redirectTo({
url: that.redirectTo
})
}
} catch (e) {
console.log(e)
}
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 3000
})
}
}
});
}
}
}
</script>
<style lang="scss" scoped>
@import "@/pages/login/login-page.scss";
@media screen and (min-width: 690px) {
.uni-content {
height: auto;
}
}
.forget {
font-size: 12px;
color: #8a8f8b;
}
.private{
font-size: 12px;
.link{
color: blue;
text-decoration: underline;
}
}
.privateRadio{
display: flex;
justify-content: space-between; /* 水平分布 */
align-items: center; /* 垂直居中 */
height: 30px;
}
.link-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
margin-top: 20px;
}
.link {
font-size: 12px;
}
</style>

134
pages/poster/index.vue Normal file
View File

@@ -0,0 +1,134 @@
<template>
<view class="page">
<button style="padding: 0;margin-left: 0; text-align: left;" class="nullBtn" v-if='taskid == 487'>
<image class="posterImg" src="https://ckbapi.quwanzhi.com/upload/img/ckb.png" mode="widthFix"></image>
</button>
<button style="padding: 0;margin-left: 0; text-align: left;" class="nullBtn" open-type="getPhoneNumber" v-else
@getphonenumber="getPhoneNumber">
<image class="posterImg" :src="poster.sUrl" mode="widthFix"></image>
</button>
</view>
</template>
<script>
import {
request
} from '@/utils/request';
export default {
data() {
return {
taskid: '',
token: "test",
task: null,
poster: null
}
},
onLoad(options) {
let scene = options.scene;
let taskId = options.id;
if (scene != undefined) {
this.taskid = scene;
} else if (taskId != undefined) {
this.taskid = taskId;
} else {
this.taskid = 487;
}
this.getPosterData()
},
methods: {
getPosterData() {
let that = this;
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/getone',
data: {
oldId: that.taskid,
},
success: (res) => {
if (res.data.code == 200) {
that.poster = res.data.data.poster
that.task = res.data.data.task
that.taskid = res.data.data.task.id
//修改标题
uni.setNavigationBarTitle({
title: ""
});
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
},
onShareAppMessage() {
return {
title: '',
path: '/pages/poster/index2?id=' + this.taskid,
imageUrl: poster.sUrl // 可以省略不写,这样会默认截图当前页面作为分享图片
};
},
// 获取用户手机号
getPhoneNumber(res) {
let that = this;
if (res.detail.errMsg == "getPhoneNumber:ok") {
uni.showLoading({
title: '请求中...',
mask: true
})
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/decryptphone',
data: {
id: that.taskid,
code: res.detail.code
},
success: (res) => {
uni.hideLoading();
if (res.data.code == 200) {
uni.showModal({
title: '提示',
content: that.task.sTip,
success: function(res) {
console.log(res)
}
})
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
}
},
}
}
</script>
<style>
.page {
width: 100vw;
height: 100vh;
}
.posterImg {
width: 100%;
}
</style>

193
pages/poster/index2.vue Normal file
View File

@@ -0,0 +1,193 @@
<template>
<view class="page">
<button style="padding: 0;margin-left: 0; text-align: left;" class="nullBtn" v-if='taskid == 487'>
<image class="posterImg" src="https://ckbapi.quwanzhi.com/upload/img/ckb.png" mode="widthFix"></image>
</button>
<button style="padding: 0;margin-left: 0; text-align: left;" class="nullBtn" open-type="getPhoneNumber" v-else
@getphonenumber="getPhoneNumber">
<image class="posterImg" :src="poster.sUrl" mode="widthFix"></image>
</button>
</view>
</template>
<script>
import {
request
} from '@/utils/request';
export default {
data() {
return {
taskid: '',
token: "test",
task: null,
poster: null,
cid: '',
}
},
onLoad(options) {
let scene = options.scene;
let taskId = options.id;
let cid = options.cid;
console.log('页面参数:', { scene, taskId, cid });
// 小程序端解析 scene 参数
// #ifdef MP-WEIXIN
if (scene != undefined) {
// 先对 scene 进行 URL 解码(处理 %2C 等编码字符)
try {
scene = decodeURIComponent(scene);
} catch (e) {
console.warn('scene 解码失败,使用原始值:', e);
}
// 如果 scene 包含逗号,分割成 [taskId, channelId]
if (scene.includes(',')) {
const parts = scene.split(',');
this.taskid = parts[0] || '';
this.cid = parts[1] || '0';
} else {
// 如果不包含逗号,只有 taskIdchannelId 为 0
this.taskid = scene;
this.cid = '0';
}
} else if (taskId != undefined) {
// 兼容旧参数:使用 options.id
this.taskid = taskId;
this.cid = cid || '0';
} else {
// 默认值
this.taskid = 487;
this.cid = '0';
}
// #endif
// H5 端兼容旧参数
// #ifdef H5
if (taskId != undefined) {
this.taskid = taskId;
this.cid = cid || '0';
} else if (scene != undefined) {
// H5 也可能有 scene 参数(从分享链接等)
// 先对 scene 进行 URL 解码
try {
scene = decodeURIComponent(scene);
} catch (e) {
console.warn('scene 解码失败,使用原始值:', e);
}
if (scene.includes(',')) {
const parts = scene.split(',');
this.taskid = parts[0] || '';
this.cid = parts[1] || '0';
} else {
this.taskid = scene;
this.cid = '0';
}
} else {
this.taskid = 487;
this.cid = '0';
}
// #endif
console.log('解析结果:', { taskid: this.taskid, cid: this.cid });
this.getPosterData()
},
methods: {
getPosterData() {
let that = this;
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/getone',
data: {
id: that.taskid,
},
success: (res) => {
if (res.data.code == 200) {
that.poster = res.data.data.poster
that.task = res.data.data.task
//修改标题
uni.setNavigationBarTitle({
title: ""
});
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
},
onShareAppMessage() {
return {
title: '',
path: '/pages/poster/index2?id=' + this.taskid,
imageUrl: poster.sUrl // 可以省略不写,这样会默认截图当前页面作为分享图片
};
},
// 获取用户手机号
getPhoneNumber(res) {
let that = this;
if (res.detail.errMsg == "getPhoneNumber:ok") {
uni.showLoading({
title: '请求中...',
mask: true
})
uni.request({
method: 'POST',
header: {
'content-type': 'application/x-www-form-urlencoded', //自定义请求头信息
'token': that.token
},
url: that.$config.apiUrl2 + '/v1/frontend/business/poster/decryptphone',
data: {
id: that.taskid,
cid: that.cid,
code: res.detail.code
},
success: (res) => {
uni.hideLoading();
if (res.data.code == 200) {
uni.showModal({
title: '提示',
content: that.task.sTip,
success: function(res) {
console.log(res)
}
})
} else {
uni.showToast({
title: res.data.message,
icon: 'none',
duration: 2000
});
}
}
});
}
},
}
}
</script>
<style>
.page {
width: 100vw;
height: 100vh;
}
.posterImg {
width: 100%;
}
</style>

102
pages/ucenter/ucenter.vue Normal file
View File

@@ -0,0 +1,102 @@
<template>
<view class="center">
<view class="userInfo" @click.capture="toUserInfo">
<view class="defaultAvatarUrl">
<uni-icons color="#ffffff" size="50" type="person-filled" />
</view>
<view class="logo-title">
<text class="uer-name">{{userInfo.sName}}</text>
</view>
</view>
<u-cell-group :customStyle="style">
<u-cell title="我的表单" :isLink="true" url="/pages/form/list"></u-cell>
<!-- <u-cell title="我的收益" :isLink="true"></u-cell> -->
</u-cell-group>
</view>
</template>
<script>
import {
request
} from '@/utils/request';
export default {
data() {
return {
userInfo: {},
style: {
"background-color": "#f9f9f9",
}
}
},
onShow() {
request({
url: '/v1/backend/system/sysuser/profile/info',
data: {},
// ...
}).then((response) => {
this.userInfo = response.data.data
}).catch((error) => {
// 处理错误
});
},
methods: {
}
}
</script>
<style lang="scss" scoped>
view {
display: flex;
box-sizing: border-box;
flex-direction: column;
}
page {
background-color: #f8f8f8;
}
.center {
flex: 1;
flex-direction: column;
background-color: #f8f8f8;
}
.userInfo {
// padding: 20rpx;
padding-top: 60px;
background-image: url(@/static/headers.png);
flex-direction: column;
align-items: center;
margin-bottom: 30rpx;
}
.defaultAvatarUrl {
width: 150rpx;
height: 150rpx;
background-color: #007aff;
border-radius: 100%;
justify-content: center;
align-items: center;
}
.uer-name {
height: 100rpx;
line-height: 100rpx;
font-size: 38rpx;
color: #FFFFFF;
}
.center-list {
margin-bottom: 30rpx;
background-color: #f9f9f9;
}
.center-list-cell {
width: 750rpx;
background-color: #007AFF;
height: 40rpx;
}
</style>

28
project.config.json Normal file
View File

@@ -0,0 +1,28 @@
{
"appid": "wx3724765328cccf03",
"compileType": "miniprogram",
"libVersion": "3.8.10",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"coverView": true,
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
}
}

View File

@@ -0,0 +1,7 @@
{
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"projectname": "ckb_mini",
"setting": {
"compileHotReLoad": true
}
}

BIN
static/c1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

BIN
static/c2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

BIN
static/c3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

BIN
static/c4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 B

BIN
static/c5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

BIN
static/c6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

BIN
static/c7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

BIN
static/c8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

BIN
static/c9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

20
static/customicons.css Normal file
View File

@@ -0,0 +1,20 @@
@font-face {
font-family: "customicons"; /* Project id 2878519 */
src:url('/static/customicons.ttf') format('truetype');
}
.customicons {
font-family: "customicons" !important;
}
.youxi:before {
content: "\e60e";
}
.wenjian:before {
content: "\e60f";
}
.zhuanfa:before {
content: "\e610";
}

BIN
static/customicons.ttf Normal file

Binary file not shown.

BIN
static/headers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
static/tabbar/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
static/tabbar/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
static/tabbar/me.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
static/tabbar/me_active.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
static/uni.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

4
uni.scss Normal file
View File

@@ -0,0 +1,4 @@
@import '@/uni_modules/uni-scss/variables.scss';
/* uni.scss */
@import 'uview-ui/theme.scss';

50
utils/request.js Normal file
View File

@@ -0,0 +1,50 @@
// utils/request.js
import config from '@/config';
export function request(options) {
// 在请求发送前做一些处理,如添加 token
const token = uni.getStorageSync('token');
options.header = options.header || {};
options.header['token'] = token;
options.header['content-type'] = 'application/x-www-form-urlencoded';
options.method = options.method || "POST";
options.url = config.apiUrl + options.url;
return new Promise((resolve, reject) => {
uni.request({
...options,
success: (response) => {
if (response.data.code == 10030) {
// 没有登录跳转到登录页并带上当前页面的URL
redirectToLoginWithRedirect();
} else {
resolve(response);
}
},
fail: (error) => {
reject(error);
},
});
});
}
function redirectToLoginWithRedirect() {
let currentRoute = '';
// 获取当前页面路径和参数
const pages = getCurrentPages();
if (pages.length) {
const currentPage = pages[pages.length - 1];
currentRoute = currentPage.route;
const query = Object.keys(currentPage.options)
.map(key => `${key}=${currentPage.options[key]}`)
.join('&');
if (query) currentRoute += '?' + query;
}
// 重定向到登录页面
uni.redirectTo({
url: `/pages/login/login?redirect=${encodeURIComponent(currentRoute)}`,
});
}