Merge branch 'develop' of https://gitee.com/Tyssen/yi-shi into develop
This commit is contained in:
3
Cunkebao/.env.production
Normal file
3
Cunkebao/.env.production
Normal file
@@ -0,0 +1,3 @@
|
||||
# 生产环境配置
|
||||
VUE_APP_BASE_API = 'https://api.cunkebao.com'
|
||||
VUE_APP_ENV = 'production'
|
||||
3
Cunkebao/.gitignore
vendored
3
Cunkebao/.gitignore
vendored
@@ -1 +1,2 @@
|
||||
node_modules
|
||||
node_modules
|
||||
.env
|
||||
@@ -1,6 +1,6 @@
|
||||
# 村客宝 UniApp
|
||||
# 存客宝 UniApp
|
||||
|
||||
基于uni-app框架开发的村客宝移动端应用,支持H5、微信小程序、App等多端部署。
|
||||
基于uni-app框架开发的存客宝移动端应用,支持H5、微信小程序、App等多端部署。
|
||||
|
||||
## 项目结构
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import request from '@/utils/request'
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {Object} data 登录数据
|
||||
* @param {string} data.username 用户名
|
||||
* @param {string} data.account 账号(手机号)
|
||||
* @param {string} data.password 密码
|
||||
* @param {boolean} data.is_encrypted 密码是否已加密
|
||||
* @param {number} data.typeId 用户类型
|
||||
* @returns {Promise} 登录结果
|
||||
*/
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: '/api/auth/login',
|
||||
url: '/v1/auth/login',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
@@ -19,13 +19,14 @@ export function login(data) {
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
* @param {Object} data 登录数据
|
||||
* @param {string} data.mobile 手机号
|
||||
* @param {string} data.account 手机号
|
||||
* @param {string} data.code 验证码
|
||||
* @param {number} data.typeId 用户类型
|
||||
* @returns {Promise} 登录结果
|
||||
*/
|
||||
export function mobileLogin(data) {
|
||||
return request({
|
||||
url: '/api/auth/mobile-login',
|
||||
url: '/v1/auth/mobile-login',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
@@ -34,13 +35,13 @@ export function mobileLogin(data) {
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param {Object} data 数据
|
||||
* @param {string} data.mobile 手机号
|
||||
* @param {string} data.account 手机号
|
||||
* @param {string} data.type 验证码类型(login:登录,register:注册)
|
||||
* @returns {Promise} 发送结果
|
||||
*/
|
||||
export function sendCode(data) {
|
||||
return request({
|
||||
url: '/api/auth/code',
|
||||
url: '/v1/auth/code',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
@@ -52,7 +53,7 @@ export function sendCode(data) {
|
||||
*/
|
||||
export function getUserInfo() {
|
||||
return request({
|
||||
url: '/api/auth/info',
|
||||
url: '/v1/auth/info',
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
@@ -63,7 +64,7 @@ export function getUserInfo() {
|
||||
*/
|
||||
export function refreshToken() {
|
||||
return request({
|
||||
url: '/api/auth/refresh',
|
||||
url: '/v1/auth/refresh',
|
||||
method: 'POST'
|
||||
})
|
||||
}
|
||||
@@ -86,7 +87,7 @@ export function logout() {
|
||||
*/
|
||||
export function wechatLogin(data) {
|
||||
return request({
|
||||
url: '/api/auth/wechat-login',
|
||||
url: '/v1/auth/wechat-login',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
@@ -100,7 +101,7 @@ export function wechatLogin(data) {
|
||||
*/
|
||||
export function appleLogin(data) {
|
||||
return request({
|
||||
url: '/api/auth/apple-login',
|
||||
url: '/v1/auth/apple-login',
|
||||
method: 'POST',
|
||||
data
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name" : "村客宝",
|
||||
"name" : "存客宝",
|
||||
"appid" : "",
|
||||
"description" : "村客宝应用",
|
||||
"description" : "存客宝应用",
|
||||
"versionName" : "1.0.0",
|
||||
"versionCode" : "100",
|
||||
"transformPx" : false,
|
||||
@@ -69,6 +69,6 @@
|
||||
"enable" : true
|
||||
}
|
||||
},
|
||||
"title" : "村客宝"
|
||||
"title" : "存客宝"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cunkebao",
|
||||
"version": "1.0.0",
|
||||
"description": "村客宝 - 基于 uni-app 的跨平台应用",
|
||||
"description": "存客宝 - 基于 uni-app 的跨平台应用",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
||||
@@ -28,6 +28,55 @@
|
||||
"navigationBarTitleText": "设备管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/device/detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "设备详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/wechat/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "微信号管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/wechat/detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "账号详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/traffic/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "流量池"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/traffic/create",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "新建分发"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/content/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "内容库"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/content/detail",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "内容库详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/scenarios/index",
|
||||
"style": {
|
||||
@@ -59,7 +108,7 @@
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "村客宝",
|
||||
"navigationBarTitleText": "存客宝",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
></u-navbar>
|
||||
|
||||
<view class="content">
|
||||
<view class="title">村客宝隐私政策</view>
|
||||
<view class="title">存客宝隐私政策</view>
|
||||
<view class="date">生效日期:2023年1月1日</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">一、引言</view>
|
||||
<view class="paragraph">
|
||||
村客宝(以下简称"我们")非常重视您的隐私和个人信息保护。本隐私政策旨在向您说明我们如何收集、使用、存储、共享和保护您的个人信息,以及您享有的相关权利。
|
||||
存客宝(以下简称"我们")非常重视您的隐私和个人信息保护。本隐私政策旨在向您说明我们如何收集、使用、存储、共享和保护您的个人信息,以及您享有的相关权利。
|
||||
</view>
|
||||
<view class="paragraph">
|
||||
请您在使用我们的服务前,仔细阅读并了解本隐私政策的全部内容。如您对本隐私政策有任何疑问,可随时联系我们的客服。
|
||||
|
||||
@@ -6,26 +6,26 @@
|
||||
></u-navbar>
|
||||
|
||||
<view class="content">
|
||||
<view class="title">村客宝用户协议</view>
|
||||
<view class="title">存客宝用户协议</view>
|
||||
<view class="date">生效日期:2023年1月1日</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">一、总则</view>
|
||||
<view class="section-title">一、协议的接受与变更</view>
|
||||
<view class="paragraph">
|
||||
1.1 村客宝用户协议(以下简称"本协议")是您与村客宝平台(以下简称"我们")之间就村客宝平台服务等相关事宜所订立的契约。
|
||||
1.1 存客宝用户协议(以下简称"本协议")是您与存客宝平台(以下简称"我们")之间就存客宝平台服务等相关事宜所订立的契约。
|
||||
</view>
|
||||
<view class="paragraph">
|
||||
1.2 您应当在使用村客宝平台服务之前认真阅读本协议全部内容。如您对本协议有任何疑问,可随时咨询我们的客服。
|
||||
1.2 您应当在使用存客宝平台服务之前认真阅读本协议全部内容。如您对本协议有任何疑问,可随时咨询我们的客服。
|
||||
</view>
|
||||
<view class="paragraph">
|
||||
1.3 您点击"同意"或"下一步",或您使用村客宝平台服务,即视为您已阅读并同意签署本协议。本协议自您确认同意之时起生效。
|
||||
1.3 您点击"同意"或"下一步",或您使用存客宝平台服务,即视为您已阅读并同意签署本协议。本协议自您确认同意之时起生效。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">二、账号注册与使用</view>
|
||||
<view class="paragraph">
|
||||
2.1 您应当保证您具有完全民事行为能力,能够独立承担民事责任,并独立承担使用村客宝平台服务的一切法律责任。
|
||||
2.1 您应当保证您具有完全民事行为能力,能够独立承担民事责任,并独立承担使用存客宝平台服务的一切法律责任。
|
||||
</view>
|
||||
<view class="paragraph">
|
||||
2.2 您注册成功后,我们将给予您一个用户账号及相应的密码,该用户账号和密码由您负责保管。
|
||||
@@ -38,7 +38,7 @@
|
||||
<view class="section">
|
||||
<view class="section-title">三、服务内容</view>
|
||||
<view class="paragraph">
|
||||
3.1 村客宝平台服务的具体内容由我们根据实际情况提供,包括但不限于信息发布、交易撮合、数据统计等。
|
||||
3.1 存客宝平台服务的具体内容由我们根据实际情况提供,包括但不限于信息发布、交易撮合、数据统计等。
|
||||
</view>
|
||||
<view class="paragraph">
|
||||
3.2 我们有权不经事先通知,随时变更、中断或终止部分或全部的服务。
|
||||
@@ -46,19 +46,19 @@
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">四、用户义务</view>
|
||||
<view class="section-title">四、用户行为规范</view>
|
||||
<view class="paragraph">
|
||||
4.1 您在使用村客宝平台服务时,必须遵守中华人民共和国相关法律法规。
|
||||
4.1 您在使用存客宝平台服务时,必须遵守中华人民共和国相关法律法规。
|
||||
</view>
|
||||
<view class="paragraph">
|
||||
4.2 您不得利用村客宝平台服务从事违法违规行为,包括但不限于发布违法信息、侵犯他人知识产权等。
|
||||
4.2 您不得利用存客宝平台服务从事违法违规行为,包括但不限于发布违法信息、侵犯他人知识产权等。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">五、知识产权</view>
|
||||
<view class="paragraph">
|
||||
5.1 村客宝平台所包含的全部智力成果,包括但不限于程序、源代码、图标、图饰、图像、图表、文字等,均受著作权法、商标法、专利法及其他知识产权法律法规的保护。
|
||||
5.1 存客宝平台所包含的全部智力成果,包括但不限于程序、源代码、图标、图饰、图像、图表、文字等,均受著作权法、商标法、专利法及其他知识产权法律法规的保护。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
7.1 我们有权随时修改本协议,并在修改后的协议生效前通过适当方式通知您。
|
||||
</view>
|
||||
<view class="paragraph">
|
||||
7.2 如您不同意修改后的协议,可以选择停止使用村客宝平台服务;如您继续使用村客宝平台服务,则视为您已同意修改后的协议。
|
||||
7.2 如您不同意修改后的协议,可以选择停止使用存客宝平台服务;如您继续使用存客宝平台服务,则视为您已同意修改后的协议。
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
325
Cunkebao/pages/content/components/FriendSelector.vue
Normal file
325
Cunkebao/pages/content/components/FriendSelector.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
@close="onClose"
|
||||
mode="bottom"
|
||||
:safeAreaInsetBottom="true"
|
||||
:round="10"
|
||||
:closeable="true"
|
||||
closeIconPos="top-right"
|
||||
closeIconColor="#999"
|
||||
:maskCloseAble="true"
|
||||
height="85%"
|
||||
>
|
||||
<view class="friend-selector">
|
||||
<!-- 标题栏 -->
|
||||
<view class="selector-header">
|
||||
<text class="selector-title">选择微信好友</text>
|
||||
<view class="close-icon" @click="onClose">
|
||||
<u-icon name="close" size="28" color="#999"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索好友"
|
||||
:showAction="false"
|
||||
clearabled
|
||||
shape="round"
|
||||
:clearabled="true"
|
||||
height="70"
|
||||
bgColor="#f4f4f4"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 好友列表 -->
|
||||
<scroll-view scroll-y class="friend-list">
|
||||
<view
|
||||
class="friend-item"
|
||||
v-for="(friend, index) in filteredFriends"
|
||||
:key="index"
|
||||
@click="toggleSelect(friend)"
|
||||
>
|
||||
<view class="friend-checkbox">
|
||||
<u-radio
|
||||
:name="friend.id"
|
||||
v-model="friend.selected"
|
||||
:disabled="disabled"
|
||||
@change="() => toggleSelect(friend)"
|
||||
shape="circle"
|
||||
activeColor="#4080ff"
|
||||
></u-radio>
|
||||
</view>
|
||||
<view class="friend-avatar">
|
||||
<image :src="friend.avatar || '/static/images/avatar.png'" mode="aspectFill" class="avatar-img"></image>
|
||||
</view>
|
||||
<view class="friend-info">
|
||||
<view class="friend-name">{{friend.name}}</view>
|
||||
<view class="friend-id">{{friend.wechatId}}</view>
|
||||
<view class="friend-client">归属客户:{{friend.client}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="action-buttons">
|
||||
<view class="cancel-btn" @click="onCancel">取消</view>
|
||||
<view class="confirm-btn" @click="onConfirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FriendSelector',
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchKeyword: '',
|
||||
friends: [
|
||||
{
|
||||
id: '1',
|
||||
name: '好友1',
|
||||
wechatId: 'wxid_0y06hq00',
|
||||
client: '客户1',
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '好友2',
|
||||
wechatId: 'wxid_mt5oz9fz',
|
||||
client: '客户2',
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '好友3',
|
||||
wechatId: 'wxid_bma8xfh8',
|
||||
client: '客户3',
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: '好友4',
|
||||
wechatId: 'wxid_9xazw62h',
|
||||
client: '客户4',
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: '好友5',
|
||||
wechatId: 'wxid_v1fv02q3',
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredFriends() {
|
||||
if (!this.searchKeyword) {
|
||||
return this.friends;
|
||||
}
|
||||
|
||||
return this.friends.filter(friend =>
|
||||
friend.name.includes(this.searchKeyword) ||
|
||||
friend.wechatId.includes(this.searchKeyword) ||
|
||||
(friend.client && friend.client.includes(this.searchKeyword))
|
||||
);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(newVal) {
|
||||
if (newVal) {
|
||||
this.initSelection();
|
||||
}
|
||||
},
|
||||
selected: {
|
||||
handler: function(newVal) {
|
||||
this.initSelection();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化选择状态
|
||||
initSelection() {
|
||||
this.friends.forEach(friend => {
|
||||
friend.selected = this.selected.includes(friend.id);
|
||||
});
|
||||
},
|
||||
|
||||
// 切换选择状态
|
||||
toggleSelect(friend) {
|
||||
if (this.disabled) return;
|
||||
|
||||
if (!this.multiple) {
|
||||
// 单选模式
|
||||
this.friends.forEach(item => {
|
||||
item.selected = item.id === friend.id;
|
||||
});
|
||||
} else {
|
||||
// 多选模式
|
||||
friend.selected = !friend.selected;
|
||||
}
|
||||
},
|
||||
|
||||
// 取消按钮
|
||||
onCancel() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
|
||||
// 确定按钮
|
||||
onConfirm() {
|
||||
const selectedFriends = this.friends.filter(friend => friend.selected);
|
||||
this.$emit('confirm', selectedFriends);
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
|
||||
// 关闭弹窗
|
||||
onClose() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.friend-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.selector-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.selector-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 30rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.friend-list {
|
||||
flex: 1;
|
||||
padding: 0 30rpx;
|
||||
overflow-y: auto;
|
||||
|
||||
.friend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.friend-checkbox {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.friend-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-right: 20rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.friend-info {
|
||||
flex: 1;
|
||||
|
||||
.friend-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.friend-id {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.friend-client {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
padding: 20rpx 30rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.cancel-btn, .confirm-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 40rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #4080ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
325
Cunkebao/pages/content/components/GroupSelector.vue
Normal file
325
Cunkebao/pages/content/components/GroupSelector.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
@close="onClose"
|
||||
mode="bottom"
|
||||
:safeAreaInsetBottom="true"
|
||||
:round="10"
|
||||
:closeable="true"
|
||||
closeIconPos="top-right"
|
||||
closeIconColor="#999"
|
||||
:maskCloseAble="true"
|
||||
height="85%"
|
||||
>
|
||||
<view class="group-selector">
|
||||
<!-- 标题栏 -->
|
||||
<view class="selector-header">
|
||||
<text class="selector-title">选择聊天群</text>
|
||||
<view class="close-icon" @click="onClose">
|
||||
<u-icon name="close" size="28" color="#999"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索聊天群"
|
||||
:showAction="false"
|
||||
clearabled
|
||||
shape="round"
|
||||
:clearabled="true"
|
||||
height="70"
|
||||
bgColor="#f4f4f4"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 群列表 -->
|
||||
<scroll-view scroll-y class="group-list">
|
||||
<view
|
||||
class="group-item"
|
||||
v-for="(group, index) in filteredGroups"
|
||||
:key="index"
|
||||
@click="toggleSelect(group)"
|
||||
>
|
||||
<view class="group-checkbox">
|
||||
<u-radio
|
||||
:name="group.id"
|
||||
v-model="group.selected"
|
||||
:disabled="disabled"
|
||||
@change="() => toggleSelect(group)"
|
||||
shape="circle"
|
||||
activeColor="#4080ff"
|
||||
></u-radio>
|
||||
</view>
|
||||
<view class="group-avatar">
|
||||
<image :src="group.avatar || '/static/images/avatar.png'" mode="aspectFill" class="avatar-img"></image>
|
||||
</view>
|
||||
<view class="group-info">
|
||||
<view class="group-name">{{group.name}}</view>
|
||||
<view class="group-id">{{group.groupId}}</view>
|
||||
<view class="group-member-count">{{group.memberCount}}人</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="action-buttons">
|
||||
<view class="cancel-btn" @click="onCancel">取消</view>
|
||||
<view class="confirm-btn" @click="onConfirm">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GroupSelector',
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchKeyword: '',
|
||||
groups: [
|
||||
{
|
||||
id: '1',
|
||||
name: '产品讨论群',
|
||||
groupId: '12345678910',
|
||||
memberCount: 120,
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '客户交流群',
|
||||
groupId: '28374656374',
|
||||
memberCount: 88,
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '企业内部群',
|
||||
groupId: '98374625162',
|
||||
memberCount: 56,
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: '推广活动群',
|
||||
groupId: '38273645123',
|
||||
memberCount: 240,
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: '售后服务群',
|
||||
groupId: '73645182934',
|
||||
memberCount: 178,
|
||||
avatar: '/static/images/avatar.png',
|
||||
selected: false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredGroups() {
|
||||
if (!this.searchKeyword) {
|
||||
return this.groups;
|
||||
}
|
||||
|
||||
return this.groups.filter(group =>
|
||||
group.name.includes(this.searchKeyword) ||
|
||||
group.groupId.includes(this.searchKeyword)
|
||||
);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(newVal) {
|
||||
if (newVal) {
|
||||
this.initSelection();
|
||||
}
|
||||
},
|
||||
selected: {
|
||||
handler: function(newVal) {
|
||||
this.initSelection();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化选择状态
|
||||
initSelection() {
|
||||
this.groups.forEach(group => {
|
||||
group.selected = this.selected.includes(group.id);
|
||||
});
|
||||
},
|
||||
|
||||
// 切换选择状态
|
||||
toggleSelect(group) {
|
||||
if (this.disabled) return;
|
||||
|
||||
if (!this.multiple) {
|
||||
// 单选模式
|
||||
this.groups.forEach(item => {
|
||||
item.selected = item.id === group.id;
|
||||
});
|
||||
} else {
|
||||
// 多选模式
|
||||
group.selected = !group.selected;
|
||||
}
|
||||
},
|
||||
|
||||
// 取消按钮
|
||||
onCancel() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
|
||||
// 确定按钮
|
||||
onConfirm() {
|
||||
const selectedGroups = this.groups.filter(group => group.selected);
|
||||
this.$emit('confirm', selectedGroups);
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
|
||||
// 关闭弹窗
|
||||
onClose() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.group-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.selector-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
position: relative;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.selector-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 30rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.group-list {
|
||||
flex: 1;
|
||||
padding: 0 30rpx;
|
||||
overflow-y: auto;
|
||||
|
||||
.group-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
.group-checkbox {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.group-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-right: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.group-info {
|
||||
flex: 1;
|
||||
|
||||
.group-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.group-id {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.group-member-count {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
padding: 20rpx 30rpx;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
|
||||
.cancel-btn, .confirm-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 40rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #4080ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
504
Cunkebao/pages/content/detail.vue
Normal file
504
Cunkebao/pages/content/detail.vue
Normal file
@@ -0,0 +1,504 @@
|
||||
<template>
|
||||
<view class="detail-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<view class="back-icon" @click="goBack">
|
||||
<u-icon name="arrow-left" size="42" color="black"></u-icon>
|
||||
</view>
|
||||
<view class="title">内容库详情</view>
|
||||
<view class="header-right">
|
||||
<view class="save-btn" @click="saveContent">
|
||||
<u-icon name="checkbox-mark" size="28" color="#fff"></u-icon>
|
||||
<text class="save-text">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-container">
|
||||
<!-- 内容库名称 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">内容库名称</view>
|
||||
<view class="form-input-box">
|
||||
<input type="text" v-model="form.title" placeholder="示例内容库" class="form-input" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据来源配置 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">数据来源配置</view>
|
||||
<view class="source-buttons">
|
||||
<view class="source-btn" :class="{ active: form.dataSource === 'friend' }" @click="setDataSource('friend')">
|
||||
<text>选择微信好友</text>
|
||||
</view>
|
||||
<view class="source-btn" :class="{ active: form.dataSource === 'group' }" @click="setDataSource('group')">
|
||||
<text>选择聊天群</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 当选择微信好友时显示 -->
|
||||
<view class="friend-list-box" v-if="form.dataSource === 'friend'">
|
||||
<view class="friend-select-btn" @click="showFriendSelector">
|
||||
<text>选择微信好友</text>
|
||||
</view>
|
||||
<view class="selected-friends" v-if="form.selectedFriends.length > 0">
|
||||
<view class="friend-tag" v-for="(friend, index) in form.selectedFriends" :key="index">
|
||||
<text>{{friend.name || friend}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 当选择聊天群时显示 -->
|
||||
<view class="group-list-box" v-if="form.dataSource === 'group'">
|
||||
<view class="group-select-btn" @click="showGroupSelector">
|
||||
<text>选择聊天群</text>
|
||||
</view>
|
||||
<view class="selected-groups" v-if="form.selectedGroups.length > 0">
|
||||
<view class="group-tag" v-for="(group, index) in form.selectedGroups" :key="index">
|
||||
<text>{{group.name || group}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关键字设置 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<text>关键字设置</text>
|
||||
<u-icon name="arrow-down" size="28" color="#666" @click="toggleKeywordSection"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 是否启用AI -->
|
||||
<view class="form-item">
|
||||
<view class="form-label-with-desc">
|
||||
<view class="label-row">
|
||||
<text>是否启用AI</text>
|
||||
<u-switch v-model="form.enableAI" activeColor="#4080ff"></u-switch>
|
||||
</view>
|
||||
<view class="label-desc">
|
||||
<text>当启用AI之后,该内容库下的所有内容,都会通过AI重新生成内容。</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI提示词 -->
|
||||
<view class="form-item" v-if="form.enableAI">
|
||||
<view class="form-label">AI 提示词</view>
|
||||
<view class="form-textarea-box">
|
||||
<textarea v-model="form.aiPrompt" placeholder="AI提示词示例" class="form-textarea" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 时间限制 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">时间限制</view>
|
||||
<view class="form-date-box" @click="showDatePicker">
|
||||
<u-icon name="calendar" size="28" color="#666"></u-icon>
|
||||
<text class="date-text">{{form.dateRange || '选择日期范围'}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 是否启用 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label-with-switch">
|
||||
<text>是否启用</text>
|
||||
<u-switch v-model="form.isEnabled" activeColor="#4080ff"></u-switch>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<CustomTabBar active="work"></CustomTabBar>
|
||||
|
||||
<!-- 微信好友选择器 -->
|
||||
<FriendSelector
|
||||
:show="showFriendSelectorFlag"
|
||||
:selected="selectedFriendIds"
|
||||
:multiple="true"
|
||||
@update:show="showFriendSelectorFlag = $event"
|
||||
@confirm="handleFriendConfirm"
|
||||
@cancel="showFriendSelectorFlag = false"
|
||||
/>
|
||||
|
||||
<!-- 聊天群选择器 -->
|
||||
<GroupSelector
|
||||
:show="showGroupSelectorFlag"
|
||||
:selected="selectedGroupIds"
|
||||
:multiple="true"
|
||||
@update:show="showGroupSelectorFlag = $event"
|
||||
@confirm="handleGroupConfirm"
|
||||
@cancel="showGroupSelectorFlag = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
import FriendSelector from './components/FriendSelector.vue'
|
||||
import GroupSelector from './components/GroupSelector.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomTabBar,
|
||||
FriendSelector,
|
||||
GroupSelector
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: '', // 编辑时的内容库ID
|
||||
form: {
|
||||
title: '', // 内容库名称
|
||||
dataSource: 'friend', // 数据来源: friend-微信好友, group-聊天群
|
||||
selectedFriends: [], // 已选择的好友
|
||||
selectedGroups: [], // 已选择的群组
|
||||
enableAI: true, // 是否启用AI
|
||||
aiPrompt: 'AI提示词示例', // AI提示词
|
||||
isKeywordExpanded: false, // 关键字设置是否展开
|
||||
dateRange: '', // 时间限制
|
||||
isEnabled: true // 是否启用
|
||||
},
|
||||
isEdit: false, // 是否为编辑模式
|
||||
showFriendSelectorFlag: false, // 是否显示好友选择器
|
||||
showGroupSelectorFlag: false, // 是否显示群组选择器
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取已选择的好友ID列表
|
||||
selectedFriendIds() {
|
||||
return this.form.selectedFriends.map(friend => {
|
||||
return typeof friend === 'object' ? friend.id : friend;
|
||||
});
|
||||
},
|
||||
|
||||
// 获取已选择的群组ID列表
|
||||
selectedGroupIds() {
|
||||
return this.form.selectedGroups.map(group => {
|
||||
return typeof group === 'object' ? group.id : group;
|
||||
});
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.id = options.id;
|
||||
this.isEdit = true;
|
||||
this.loadContentDetail();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 设置数据来源
|
||||
setDataSource(source) {
|
||||
this.form.dataSource = source;
|
||||
},
|
||||
|
||||
// 显示好友选择器
|
||||
showFriendSelector() {
|
||||
this.showFriendSelectorFlag = true;
|
||||
},
|
||||
|
||||
// 显示群组选择器
|
||||
showGroupSelector() {
|
||||
this.showGroupSelectorFlag = true;
|
||||
},
|
||||
|
||||
// 处理好友选择确认
|
||||
handleFriendConfirm(selectedFriends) {
|
||||
this.form.selectedFriends = selectedFriends;
|
||||
},
|
||||
|
||||
// 处理群组选择确认
|
||||
handleGroupConfirm(selectedGroups) {
|
||||
this.form.selectedGroups = selectedGroups;
|
||||
},
|
||||
|
||||
// 切换关键字设置区域
|
||||
toggleKeywordSection() {
|
||||
this.form.isKeywordExpanded = !this.form.isKeywordExpanded;
|
||||
},
|
||||
|
||||
// 显示日期选择器
|
||||
showDatePicker() {
|
||||
// 这里应该调用日期选择器组件
|
||||
uni.showToast({
|
||||
title: '日期选择功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
// 保存内容库
|
||||
saveContent() {
|
||||
// 表单验证
|
||||
if (!this.form.title) {
|
||||
uni.showToast({
|
||||
title: '请输入内容库名称',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证是否选择了数据来源
|
||||
if (this.form.dataSource === 'friend' && this.form.selectedFriends.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请选择微信好友',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.form.dataSource === 'group' && this.form.selectedGroups.length === 0) {
|
||||
uni.showToast({
|
||||
title: '请选择聊天群',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 在实际应用中,这里应该提交表单数据到服务器
|
||||
uni.showLoading({
|
||||
title: '保存中...'
|
||||
});
|
||||
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
// 加载内容库详情数据
|
||||
loadContentDetail() {
|
||||
// 在实际应用中,这里应该从服务器获取内容库详情
|
||||
console.log('加载内容库详情:', this.id);
|
||||
// 模拟数据加载
|
||||
if (this.isEdit) {
|
||||
// 模拟已有数据
|
||||
this.form = {
|
||||
title: '示例内容库',
|
||||
dataSource: 'friend',
|
||||
selectedFriends: ['张三', '李四'],
|
||||
selectedGroups: [],
|
||||
enableAI: true,
|
||||
aiPrompt: 'AI提示词示例',
|
||||
isKeywordExpanded: false,
|
||||
dateRange: '',
|
||||
isEnabled: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
padding-bottom: 150rpx; /* 为底部导航栏预留空间 */
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.back-icon {
|
||||
width: 60rpx;
|
||||
color: #000;
|
||||
padding: 10rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 600;
|
||||
margin-left: -60rpx; /* 使标题居中 */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
.save-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #4080ff;
|
||||
border-radius: 30rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
color: #fff;
|
||||
|
||||
.save-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 20rpx 30rpx;
|
||||
|
||||
.form-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.form-label {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-label-with-desc {
|
||||
.label-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.label-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label-with-switch {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-input-box {
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-textarea-box {
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.source-buttons {
|
||||
display: flex;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.source-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
border-right: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: #e6f7ff;
|
||||
color: #4080ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.friend-list-box, .group-list-box {
|
||||
.friend-select-btn, .group-select-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.selected-friends, .selected-groups {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.friend-tag, .group-tag {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 30rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
margin-right: 15rpx;
|
||||
margin-bottom: 15rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-date-box {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
|
||||
.date-text {
|
||||
margin-left: 10rpx;
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
741
Cunkebao/pages/content/index.vue
Normal file
741
Cunkebao/pages/content/index.vue
Normal file
@@ -0,0 +1,741 @@
|
||||
<template>
|
||||
<view class="content-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<view class="back-icon" @click="goBack">
|
||||
<u-icon name="arrow-left" size="42" color="black"></u-icon>
|
||||
</view>
|
||||
<view class="title">内容库</view>
|
||||
<view class="header-right">
|
||||
<view class="add-btn" @click="createContent">
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">新建</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<view class="content-wrapper">
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索内容库..."
|
||||
:showAction="false"
|
||||
shape="round"
|
||||
:clearabled="true"
|
||||
height="70"
|
||||
bgColor="#f4f4f4"
|
||||
></u-search>
|
||||
<view class="filter-btn" @click="showFilter">
|
||||
<u-icon name="filter" size="36" color="#000"></u-icon>
|
||||
</view>
|
||||
<view class="refresh-btn" @click="refreshData">
|
||||
<u-icon name="reload" size="36" color="#000"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'all' }"
|
||||
@click="switchTab('all')"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'friends' }"
|
||||
@click="switchTab('friends')"
|
||||
>
|
||||
微信好友
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'groups' }"
|
||||
@click="switchTab('groups')"
|
||||
>
|
||||
聊天群
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容列表 -->
|
||||
<view class="content-list" v-if="filteredContents.length > 0">
|
||||
<view class="content-item" v-for="(item, index) in filteredContents" :key="index">
|
||||
<view class="content-header">
|
||||
<text class="content-title">{{item.title}}</text>
|
||||
<view class="usage-tag" :class="item.used ? 'used' : 'unused'">
|
||||
{{item.used ? '已使用' : '未使用'}}
|
||||
</view>
|
||||
<view class="more-icon" @click.stop="showOptions(item)">
|
||||
<u-icon name="more-dot-fill" size="32" color="#333"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-info">
|
||||
<view class="info-row">
|
||||
<text class="info-label">来源:</text>
|
||||
<view class="source-avatars">
|
||||
<image v-for="(avatar, i) in item.sourceAvatars" :key="i" :src="avatar" mode="aspectFill" class="source-avatar"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">创建人:</text>
|
||||
<text class="info-value">{{item.creator}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">内容数量:</text>
|
||||
<text class="info-value">{{item.contentCount}}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">更新时间:</text>
|
||||
<text class="info-value">{{item.updateTime}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载中提示 -->
|
||||
<view class="loading-container" v-else-if="loading">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-container" v-else>
|
||||
<image src="/static/images/empty.png" mode="aspectFit" class="empty-img"></image>
|
||||
<text class="empty-text">暂无内容</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<CustomTabBar active="work"></CustomTabBar>
|
||||
|
||||
<!-- 筛选弹窗 -->
|
||||
<u-popup :show="showFilterPopup" @close="closeFilterPopup" mode="bottom">
|
||||
<view class="popup-content">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">筛选条件</text>
|
||||
<view class="popup-close" @click="closeFilterPopup">
|
||||
<u-icon name="close" size="28" color="#999"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-body">
|
||||
<view class="filter-section">
|
||||
<view class="filter-title">使用状态</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option"
|
||||
:class="{ active: selectedUsage === 'all' }"
|
||||
@click="selectUsage('all')"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
class="filter-option"
|
||||
:class="{ active: selectedUsage === 'used' }"
|
||||
@click="selectUsage('used')"
|
||||
>
|
||||
已使用
|
||||
</view>
|
||||
<view
|
||||
class="filter-option"
|
||||
:class="{ active: selectedUsage === 'unused' }"
|
||||
@click="selectUsage('unused')"
|
||||
>
|
||||
未使用
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-section">
|
||||
<view class="filter-title">更新时间</view>
|
||||
<view class="filter-options">
|
||||
<view
|
||||
class="filter-option"
|
||||
:class="{ active: selectedTime === 'all' }"
|
||||
@click="selectTime('all')"
|
||||
>
|
||||
全部时间
|
||||
</view>
|
||||
<view
|
||||
class="filter-option"
|
||||
:class="{ active: selectedTime === 'today' }"
|
||||
@click="selectTime('today')"
|
||||
>
|
||||
今天
|
||||
</view>
|
||||
<view
|
||||
class="filter-option"
|
||||
:class="{ active: selectedTime === 'week' }"
|
||||
@click="selectTime('week')"
|
||||
>
|
||||
本周
|
||||
</view>
|
||||
<view
|
||||
class="filter-option"
|
||||
:class="{ active: selectedTime === 'month' }"
|
||||
@click="selectTime('month')"
|
||||
>
|
||||
本月
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="filter-buttons">
|
||||
<view class="reset-btn" @click="resetFilter">重置</view>
|
||||
<view class="confirm-btn" @click="applyFilter">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 内容操作弹窗 -->
|
||||
<u-popup :show="showActionPopup" @close="closeActionPopup" mode="bottom">
|
||||
<view class="action-list">
|
||||
<view class="action-item" @click="editContent">
|
||||
<u-icon name="edit-pen" size="32" color="#333"></u-icon>
|
||||
<text>编辑内容</text>
|
||||
</view>
|
||||
<view class="action-item" @click="shareContent">
|
||||
<u-icon name="share" size="32" color="#333"></u-icon>
|
||||
<text>分享内容</text>
|
||||
</view>
|
||||
<view class="action-item delete" @click="deleteContent">
|
||||
<u-icon name="trash" size="32" color="#fa5151"></u-icon>
|
||||
<text>删除内容</text>
|
||||
</view>
|
||||
<view class="action-item cancel" @click="closeActionPopup">
|
||||
<text>取消</text>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomTabBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchKeyword: '',
|
||||
currentTab: 'all',
|
||||
showFilterPopup: false,
|
||||
showActionPopup: false,
|
||||
selectedUsage: 'all',
|
||||
selectedTime: 'all',
|
||||
tempSelectedUsage: 'all',
|
||||
tempSelectedTime: 'all',
|
||||
loading: true,
|
||||
currentContent: null,
|
||||
contents: [
|
||||
{
|
||||
id: 1,
|
||||
title: '微信好友广告',
|
||||
used: true,
|
||||
sourceAvatars: ['/static/images/avatar.png'],
|
||||
creator: '海尼',
|
||||
contentCount: 0,
|
||||
updateTime: '2024-02-09 12:30'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '开发群',
|
||||
used: true,
|
||||
sourceAvatars: ['/static/images/avatar.png'],
|
||||
creator: 'karuo',
|
||||
contentCount: 0,
|
||||
updateTime: '2024-02-09 12:30'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredContents() {
|
||||
let result = [...this.contents];
|
||||
|
||||
// 搜索关键词筛选
|
||||
if (this.searchKeyword) {
|
||||
result = result.filter(item =>
|
||||
item.title.includes(this.searchKeyword) ||
|
||||
item.creator.includes(this.searchKeyword)
|
||||
);
|
||||
}
|
||||
|
||||
// 标签页筛选
|
||||
if (this.currentTab === 'friends') {
|
||||
result = result.filter(item => item.title.includes('好友'));
|
||||
} else if (this.currentTab === 'groups') {
|
||||
result = result.filter(item => item.title.includes('群'));
|
||||
}
|
||||
|
||||
// 使用状态筛选
|
||||
if (this.selectedUsage === 'used') {
|
||||
result = result.filter(item => item.used);
|
||||
} else if (this.selectedUsage === 'unused') {
|
||||
result = result.filter(item => !item.used);
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
if (this.selectedTime !== 'all') {
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
||||
const weekAgo = today - 7 * 24 * 60 * 60 * 1000;
|
||||
const monthAgo = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()).getTime();
|
||||
|
||||
result = result.filter(item => {
|
||||
const itemTime = new Date(item.updateTime).getTime();
|
||||
if (this.selectedTime === 'today') {
|
||||
return itemTime >= today;
|
||||
} else if (this.selectedTime === 'week') {
|
||||
return itemTime >= weekAgo;
|
||||
} else if (this.selectedTime === 'month') {
|
||||
return itemTime >= monthAgo;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 刷新数据
|
||||
refreshData() {
|
||||
this.loading = true;
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'none'
|
||||
});
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
// 显示筛选弹窗
|
||||
showFilter() {
|
||||
this.tempSelectedUsage = this.selectedUsage;
|
||||
this.tempSelectedTime = this.selectedTime;
|
||||
this.showFilterPopup = true;
|
||||
},
|
||||
|
||||
// 关闭筛选弹窗
|
||||
closeFilterPopup() {
|
||||
this.showFilterPopup = false;
|
||||
},
|
||||
|
||||
// 切换标签页
|
||||
switchTab(tab) {
|
||||
this.currentTab = tab;
|
||||
},
|
||||
|
||||
// 选择使用状态
|
||||
selectUsage(usage) {
|
||||
this.tempSelectedUsage = usage;
|
||||
},
|
||||
|
||||
// 选择时间范围
|
||||
selectTime(time) {
|
||||
this.tempSelectedTime = time;
|
||||
},
|
||||
|
||||
// 重置筛选条件
|
||||
resetFilter() {
|
||||
this.tempSelectedUsage = 'all';
|
||||
this.tempSelectedTime = 'all';
|
||||
},
|
||||
|
||||
// 应用筛选条件
|
||||
applyFilter() {
|
||||
this.selectedUsage = this.tempSelectedUsage;
|
||||
this.selectedTime = this.tempSelectedTime;
|
||||
this.closeFilterPopup();
|
||||
},
|
||||
|
||||
// 显示内容操作弹窗
|
||||
showOptions(content) {
|
||||
this.currentContent = content;
|
||||
this.showActionPopup = true;
|
||||
},
|
||||
|
||||
// 关闭内容操作弹窗
|
||||
closeActionPopup() {
|
||||
this.showActionPopup = false;
|
||||
},
|
||||
|
||||
// 创建内容
|
||||
createContent() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/content/detail'
|
||||
});
|
||||
},
|
||||
|
||||
// 编辑内容
|
||||
editContent() {
|
||||
uni.showToast({
|
||||
title: `编辑内容:${this.currentContent.title}`,
|
||||
icon: 'none'
|
||||
});
|
||||
this.closeActionPopup();
|
||||
},
|
||||
|
||||
// 分享内容
|
||||
shareContent() {
|
||||
uni.showToast({
|
||||
title: `分享内容:${this.currentContent.title}`,
|
||||
icon: 'none'
|
||||
});
|
||||
this.closeActionPopup();
|
||||
},
|
||||
|
||||
// 删除内容
|
||||
deleteContent() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `确定要删除内容"${this.currentContent.title}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.contents = this.contents.filter(item => item.id !== this.currentContent.id);
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
this.closeActionPopup();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 加载数据
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
padding-bottom: 150rpx; /* 为底部导航栏预留空间 */
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.back-icon {
|
||||
width: 60rpx;
|
||||
color: #000;
|
||||
padding: 10rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 600;
|
||||
margin-left: -60rpx; /* 使标题居中 */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
.add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #4080ff;
|
||||
border-radius: 30rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
color: #fff;
|
||||
|
||||
.add-icon {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-right: 6rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 30rpx 20rpx;
|
||||
|
||||
.u-search {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-btn, .refresh-btn {
|
||||
margin-left: 20rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
margin: 0 30rpx 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
|
||||
&.active {
|
||||
color: #4080ff;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40rpx;
|
||||
height: 4rpx;
|
||||
background-color: #4080ff;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-list {
|
||||
padding: 0 30rpx;
|
||||
|
||||
.content-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.content-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.content-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.usage-tag {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 16rpx;
|
||||
|
||||
&.used {
|
||||
background-color: #e6f7ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
&.unused {
|
||||
background-color: #f6ffed;
|
||||
color: #52c41a;
|
||||
}
|
||||
}
|
||||
|
||||
.more-icon {
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.content-info {
|
||||
.info-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
|
||||
.info-label {
|
||||
color: #999;
|
||||
min-width: 150rpx;
|
||||
}
|
||||
|
||||
.source-avatars {
|
||||
display: flex;
|
||||
|
||||
.source-avatar {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-container, .empty-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 300rpx;
|
||||
|
||||
.loading-text, .empty-text {
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.empty-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-body {
|
||||
padding: 30rpx;
|
||||
|
||||
.filter-section {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.filter-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.filter-option {
|
||||
padding: 16rpx 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&.active {
|
||||
background-color: #e6f7ff;
|
||||
color: #4080ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
margin-top: 40rpx;
|
||||
|
||||
.reset-btn, .confirm-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #4080ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-list {
|
||||
background-color: #fff;
|
||||
border-top-left-radius: 16rpx;
|
||||
border-top-right-radius: 16rpx;
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30rpx 0;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
u-icon {
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
&.delete {
|
||||
color: #fa5151;
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
color: #666;
|
||||
margin-top: 16rpx;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
668
Cunkebao/pages/device/detail.vue
Normal file
668
Cunkebao/pages/device/detail.vue
Normal file
@@ -0,0 +1,668 @@
|
||||
<template>
|
||||
<view class="device-detail-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="navbar-left" @click="goBack">
|
||||
<u-icon name="arrow-left" size="44" color="#333"></u-icon>
|
||||
</view>
|
||||
<text class="navbar-title">设备详情</text>
|
||||
<view class="navbar-right">
|
||||
<u-icon name="setting" size="44" color="#333" @click="openSettings"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备信息卡片 -->
|
||||
<view class="device-card">
|
||||
<view class="device-icon-box">
|
||||
<u-icon name="smartphone" size="54" color="#4080ff"></u-icon>
|
||||
</view>
|
||||
<view class="device-title">
|
||||
<text class="device-name">{{ deviceInfo.name }}</text>
|
||||
<text class="device-status" :class="deviceInfo.status === '在线' ? 'online' : 'offline'">{{ deviceInfo.status }}</text>
|
||||
</view>
|
||||
<view class="device-info-row">
|
||||
<text class="device-info-label">IMEI: </text>
|
||||
<text class="device-info-value">{{ deviceInfo.imei }}</text>
|
||||
</view>
|
||||
<view class="device-info-row">
|
||||
<text class="device-info-label">历史ID: </text>
|
||||
<text class="device-info-value">{{ deviceInfo.historyId }}</text>
|
||||
</view>
|
||||
<view class="device-stats">
|
||||
<!-- 电量指示器 -->
|
||||
<view class="battery-indicator">
|
||||
<u-icon name="integral" size="40" :color="deviceInfo.status === '在线' ? '#07c160' : '#909399'"></u-icon>
|
||||
<text class="battery-percentage">{{ deviceInfo.battery }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 网络状态 -->
|
||||
<view class="wifi-indicator">
|
||||
<u-icon name="wifi" size="40" :color="deviceInfo.status === '在线' ? '#4080ff' : '#909399'"></u-icon>
|
||||
<text class="wifi-status">{{ deviceInfo.wifiStatus }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="device-last-active">
|
||||
<text class="last-active-label">最后活跃: </text>
|
||||
<text class="last-active-time">{{ deviceInfo.lastActive }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标签切换栏 -->
|
||||
<u-tabs
|
||||
:list="tabsList"
|
||||
:current="tabCurrent"
|
||||
@change="handleTabChange"
|
||||
activeStyle="color: #333; font-weight: bold;"
|
||||
inactiveStyle="color: #888;"
|
||||
itemStyle="height: 90rpx; font-size: 30rpx;"
|
||||
lineColor="#4080ff"
|
||||
lineWidth="60rpx"
|
||||
lineHeight="4rpx"
|
||||
></u-tabs>
|
||||
|
||||
<!-- 基本信息内容 -->
|
||||
<view v-if="tabCurrent === 0" class="tab-content">
|
||||
<view class="features-list">
|
||||
<!-- 自动加好友 -->
|
||||
<view class="feature-item">
|
||||
<view class="feature-left">
|
||||
<text class="feature-title">自动加好友</text>
|
||||
<text class="feature-desc">自动通过好友验证</text>
|
||||
</view>
|
||||
<view class="feature-right">
|
||||
<u-switch v-model="features.autoAddFriend" activeColor="#4080ff"></u-switch>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自动回复 -->
|
||||
<view class="feature-item">
|
||||
<view class="feature-left">
|
||||
<text class="feature-title">自动回复</text>
|
||||
<text class="feature-desc">自动回复好友消息</text>
|
||||
</view>
|
||||
<view class="feature-right">
|
||||
<u-switch v-model="features.autoReply" activeColor="#4080ff"></u-switch>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 朋友圈同步 -->
|
||||
<view class="feature-item">
|
||||
<view class="feature-left">
|
||||
<text class="feature-title">朋友圈同步</text>
|
||||
<text class="feature-desc">自动同步朋友圈内容</text>
|
||||
</view>
|
||||
<view class="feature-right">
|
||||
<u-switch v-model="features.momentSync" activeColor="#4080ff"></u-switch>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI会话 -->
|
||||
<view class="feature-item">
|
||||
<view class="feature-left">
|
||||
<text class="feature-title">AI会话</text>
|
||||
<text class="feature-desc">启用AI智能对话</text>
|
||||
</view>
|
||||
<view class="feature-right">
|
||||
<u-switch v-model="features.aiChat" activeColor="#4080ff"></u-switch>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 统计数据卡片 -->
|
||||
<view class="stats-container">
|
||||
<view class="stats-card">
|
||||
<view class="stats-icon">
|
||||
<u-icon name="account" size="40" color="#4080ff"></u-icon>
|
||||
</view>
|
||||
<text class="stats-title">好友总数</text>
|
||||
<text class="stats-value">768</text>
|
||||
</view>
|
||||
|
||||
<view class="stats-card">
|
||||
<view class="stats-icon">
|
||||
<u-icon name="chat" size="40" color="#4080ff"></u-icon>
|
||||
</view>
|
||||
<text class="stats-title">消息数量</text>
|
||||
<text class="stats-value">5,678</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 关联账号内容 -->
|
||||
<view v-if="tabCurrent === 1" class="tab-content">
|
||||
<view class="wechat-accounts-list">
|
||||
<!-- 账号项 1 -->
|
||||
<view class="wechat-account-item">
|
||||
<view class="wechat-avatar">
|
||||
<u-avatar src="/static/images/avatar.png" size="80"></u-avatar>
|
||||
</view>
|
||||
<view class="wechat-info">
|
||||
<view class="wechat-name-row">
|
||||
<text class="wechat-name">老张</text>
|
||||
<text class="wechat-status normal">正常</text>
|
||||
</view>
|
||||
<view class="wechat-id-row">
|
||||
<text class="wechat-id-label">微信号: </text>
|
||||
<text class="wechat-id-value">wxid_abc123</text>
|
||||
</view>
|
||||
<view class="wechat-gender-row">
|
||||
<text class="wechat-gender-label">性别: </text>
|
||||
<text class="wechat-gender-value">男</text>
|
||||
</view>
|
||||
<view class="wechat-friends-row">
|
||||
<text class="wechat-friends-label">好友数: </text>
|
||||
<text class="wechat-friends-value">523</text>
|
||||
<text class="wechat-add-friends">可加友</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号项 2 -->
|
||||
<view class="wechat-account-item">
|
||||
<view class="wechat-avatar">
|
||||
<u-avatar src="/static/images/avatar.png" size="80"></u-avatar>
|
||||
</view>
|
||||
<view class="wechat-info">
|
||||
<view class="wechat-name-row">
|
||||
<text class="wechat-name">老李</text>
|
||||
<text class="wechat-status warning">异常</text>
|
||||
</view>
|
||||
<view class="wechat-id-row">
|
||||
<text class="wechat-id-label">微信号: </text>
|
||||
<text class="wechat-id-value">wxid_xyz789</text>
|
||||
</view>
|
||||
<view class="wechat-gender-row">
|
||||
<text class="wechat-gender-label">性别: </text>
|
||||
<text class="wechat-gender-value">男</text>
|
||||
</view>
|
||||
<view class="wechat-friends-row">
|
||||
<text class="wechat-friends-label">好友数: </text>
|
||||
<text class="wechat-friends-value">245</text>
|
||||
<text class="wechat-add-friends used">已使用</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作记录内容 -->
|
||||
<view v-if="tabCurrent === 2" class="tab-content">
|
||||
<view class="operation-logs">
|
||||
<!-- 操作记录项 1 -->
|
||||
<view class="operation-item">
|
||||
<view class="operation-icon">
|
||||
<u-icon name="reload" size="36" color="#4080ff"></u-icon>
|
||||
</view>
|
||||
<view class="operation-info">
|
||||
<text class="operation-title">开启自动加好友</text>
|
||||
<text class="operation-meta">操作人: 系统 · 2024-02-09 15:30:45</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作记录项 2 -->
|
||||
<view class="operation-item">
|
||||
<view class="operation-icon">
|
||||
<u-icon name="reload" size="36" color="#4080ff"></u-icon>
|
||||
</view>
|
||||
<view class="operation-info">
|
||||
<text class="operation-title">添加微信号</text>
|
||||
<text class="operation-meta">操作人: 管理员 · 2024-02-09 14:20:33</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<CustomTabBar active="home"></CustomTabBar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomTabBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabsList: [
|
||||
{ name: '基本信息' },
|
||||
{ name: '关联账号' },
|
||||
{ name: '操作记录' }
|
||||
],
|
||||
tabCurrent: 0,
|
||||
features: {
|
||||
autoAddFriend: true,
|
||||
autoReply: true,
|
||||
momentSync: false,
|
||||
aiChat: true
|
||||
},
|
||||
deviceInfo: {
|
||||
name: '设备 1',
|
||||
imei: 'sd123123',
|
||||
historyId: 'vx412321, vfbadasd',
|
||||
battery: '85%',
|
||||
wifiStatus: '已连接',
|
||||
lastActive: '2024-02-09 15:30:45',
|
||||
status: '在线'
|
||||
},
|
||||
wechatAccounts: [
|
||||
{
|
||||
avatar: '/static/images/avatar.png',
|
||||
name: '老张',
|
||||
status: '正常',
|
||||
id: 'wxid_abc123',
|
||||
gender: '男',
|
||||
friends: 523,
|
||||
canAddFriends: true
|
||||
},
|
||||
{
|
||||
avatar: '/static/images/avatar.png',
|
||||
name: '老李',
|
||||
status: '异常',
|
||||
id: 'wxid_xyz789',
|
||||
gender: '男',
|
||||
friends: 245,
|
||||
canAddFriends: false
|
||||
}
|
||||
],
|
||||
operationLogs: [
|
||||
{
|
||||
title: '开启自动加好友',
|
||||
operator: '系统',
|
||||
time: '2024-02-09 15:30:45'
|
||||
},
|
||||
{
|
||||
title: '添加微信号',
|
||||
operator: '管理员',
|
||||
time: '2024-02-09 14:20:33'
|
||||
}
|
||||
],
|
||||
deviceId: null
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 获取路由参数中的设备ID
|
||||
if (options && options.id) {
|
||||
this.deviceId = options.id;
|
||||
console.log('设备ID:', this.deviceId);
|
||||
}
|
||||
|
||||
// 加载设备详情数据
|
||||
this.loadDeviceDetail();
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
openSettings() {
|
||||
uni.showToast({
|
||||
title: '设置功能即将上线',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
},
|
||||
handleTabChange(index) {
|
||||
this.tabCurrent = index;
|
||||
},
|
||||
loadDeviceDetail() {
|
||||
// 这里模拟API调用获取设备详情数据
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
|
||||
// 模拟网络请求延迟
|
||||
setTimeout(() => {
|
||||
// 根据设备ID获取不同的设备数据
|
||||
// 在实际应用中,这里应该是从服务器获取数据
|
||||
if (this.deviceId) {
|
||||
// 使用预设数据,实际项目中应替换为API调用
|
||||
console.log('加载设备ID为', this.deviceId, '的详情数据');
|
||||
|
||||
// 模拟不同的设备数据
|
||||
if (this.deviceId === '1') {
|
||||
// 设备1数据保持不变,已在data中预设
|
||||
} else if (this.deviceId === '2') {
|
||||
this.deviceInfo.name = '设备 2';
|
||||
this.deviceInfo.imei = 'sd123124';
|
||||
this.deviceInfo.battery = '65%';
|
||||
this.deviceInfo.lastActive = '2024-02-08 10:15:23';
|
||||
} else if (this.deviceId === '3') {
|
||||
this.deviceInfo.name = '设备 3';
|
||||
this.deviceInfo.imei = 'sd123125';
|
||||
this.deviceInfo.battery = '92%';
|
||||
this.deviceInfo.lastActive = '2024-02-09 08:45:12';
|
||||
} else if (this.deviceId === '4') {
|
||||
this.deviceInfo.name = '设备 4';
|
||||
this.deviceInfo.imei = 'sd123126';
|
||||
this.deviceInfo.battery = '23%';
|
||||
this.deviceInfo.status = '离线';
|
||||
this.deviceInfo.wifiStatus = '未连接';
|
||||
this.deviceInfo.lastActive = '2024-02-07 16:20:35';
|
||||
}
|
||||
}
|
||||
|
||||
uni.hideLoading();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.device-detail-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
padding-bottom: 150rpx; /* 为底部导航栏预留空间 */
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 30rpx;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
.navbar-left {
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
width: 80rpx;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* 设备信息卡片 */
|
||||
.device-card {
|
||||
margin: 20rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.device-icon-box {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 60rpx;
|
||||
background-color: #f0f5ff;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.device-title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 16rpx 0;
|
||||
|
||||
.device-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.device-status {
|
||||
font-size: 26rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&.online {
|
||||
background-color: rgba(7, 193, 96, 0.1);
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
&.offline {
|
||||
background-color: rgba(144, 147, 153, 0.1);
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.device-info-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 8rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
|
||||
.device-info-value {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.device-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 20rpx 0;
|
||||
|
||||
.battery-indicator, .wifi-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 30rpx;
|
||||
|
||||
.battery-percentage, .wifi-status {
|
||||
margin-left: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.device-last-active {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-top: 16rpx;
|
||||
|
||||
.last-active-time {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 选项卡内容 */
|
||||
.tab-content {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 功能设置列表 */
|
||||
.features-list {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.feature-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.feature-title {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 统计数据卡片 */
|
||||
.stats-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.stats-card {
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin: 0 10rpx;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.stats-icon {
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 48rpx;
|
||||
color: #4080ff;
|
||||
font-weight: bold;
|
||||
font-family: 'Digital-Bold', sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 微信账号列表 */
|
||||
.wechat-accounts-list {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.wechat-account-item {
|
||||
display: flex;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.wechat-avatar {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.wechat-info {
|
||||
flex: 1;
|
||||
|
||||
.wechat-name-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.wechat-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.wechat-status {
|
||||
font-size: 24rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&.normal {
|
||||
background-color: rgba(7, 193, 96, 0.1);
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: rgba(250, 81, 81, 0.1);
|
||||
color: #fa5151;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wechat-id-row, .wechat-gender-row, .wechat-friends-row {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin: 6rpx 0;
|
||||
}
|
||||
|
||||
.wechat-friends-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.wechat-add-friends {
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
|
||||
&:not(.used) {
|
||||
background-color: rgba(7, 193, 96, 0.1);
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
&.used {
|
||||
background-color: rgba(144, 147, 153, 0.1);
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 操作记录 */
|
||||
.operation-logs {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.operation-item {
|
||||
display: flex;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.operation-icon {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.operation-info {
|
||||
flex: 1;
|
||||
|
||||
.operation-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.operation-meta {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -68,9 +68,9 @@
|
||||
<!-- 设备列表 -->
|
||||
<view class="device-list">
|
||||
<!-- 设备项 1 -->
|
||||
<view class="device-item">
|
||||
<view class="device-item" @click="goToDeviceDetail(1)">
|
||||
<view class="device-checkbox">
|
||||
<u-checkbox size="40" iconSize="35" v-model="device1Selected" shape="circle" activeColor="#4080ff"></u-checkbox>
|
||||
<u-checkbox size="40" iconSize="35" v-model="device1Selected" shape="circle" activeColor="#4080ff" @click.stop></u-checkbox>
|
||||
</view>
|
||||
<view class="device-info">
|
||||
<view class="device-header">
|
||||
@@ -87,10 +87,10 @@
|
||||
</view>
|
||||
|
||||
<!-- 设备项 2 -->
|
||||
<view class="device-item">
|
||||
<view class="device-item" @click="goToDeviceDetail(2)">
|
||||
<view class="device-checkbox">
|
||||
<u-checkbox v-model="device2Selected" shape="circle" activeColor="#4080ff"
|
||||
size="40" iconSize="35"
|
||||
size="40" iconSize="35" @click.stop
|
||||
></u-checkbox>
|
||||
</view>
|
||||
<view class="device-info">
|
||||
@@ -108,9 +108,9 @@
|
||||
</view>
|
||||
|
||||
<!-- 设备项 3 -->
|
||||
<view class="device-item">
|
||||
<view class="device-item" @click="goToDeviceDetail(3)">
|
||||
<view class="device-checkbox">
|
||||
<u-checkbox size="40" iconSize="35" v-model="device3Selected" shape="circle" activeColor="#4080ff"></u-checkbox>
|
||||
<u-checkbox size="40" iconSize="35" v-model="device3Selected" shape="circle" activeColor="#4080ff" @click.stop></u-checkbox>
|
||||
</view>
|
||||
<view class="device-info">
|
||||
<view class="device-header">
|
||||
@@ -127,9 +127,9 @@
|
||||
</view>
|
||||
|
||||
<!-- 设备项 4 -->
|
||||
<view class="device-item">
|
||||
<view class="device-item" @click="goToDeviceDetail(4)">
|
||||
<view class="device-checkbox">
|
||||
<u-checkbox size="40" iconSize="35" v-model="device4Selected" shape="circle" activeColor="#4080ff"></u-checkbox>
|
||||
<u-checkbox size="40" iconSize="35" v-model="device4Selected" shape="circle" activeColor="#4080ff" @click.stop></u-checkbox>
|
||||
</view>
|
||||
<view class="device-info">
|
||||
<view class="device-header">
|
||||
@@ -356,6 +356,13 @@ export default {
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到设备详情页
|
||||
goToDeviceDetail(deviceId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/device/detail?id=${deviceId}`
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -100,6 +100,8 @@
|
||||
<script>
|
||||
import LineChart from '@/components/LineChart.vue'
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
import Auth from '@/utils/auth'
|
||||
import { getUserInfo, logout } from '@/api/user'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -119,14 +121,42 @@ export default {
|
||||
{ icon: 'coupon', color: 'yellow', count: 167, label: '海报获客' },
|
||||
{ icon: 'play-right', color: 'black', count: 156, label: '抖音获客' },
|
||||
{ icon: 'heart', color: 'red', count: 89, label: '小红书获客' }
|
||||
]
|
||||
],
|
||||
userInfo: null
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 检查登录状态
|
||||
if (!Auth.isLogin()) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
this.fetchUserInfo();
|
||||
|
||||
// 加载数据
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
// 获取用户信息
|
||||
fetchUserInfo() {
|
||||
// 先尝试从缓存获取
|
||||
this.userInfo = Auth.getUserInfo();
|
||||
|
||||
// 然后从服务器获取最新信息
|
||||
getUserInfo().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.userInfo = res.data;
|
||||
Auth.setUserInfo(res.data);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取用户信息失败:', err);
|
||||
});
|
||||
},
|
||||
|
||||
// 加载数据
|
||||
loadData() {
|
||||
// 这里可以添加API调用获取实际数据
|
||||
@@ -139,6 +169,34 @@ export default {
|
||||
uni.navigateTo({
|
||||
url: '/pages/notification/index'
|
||||
});
|
||||
},
|
||||
|
||||
// 退出登录
|
||||
handleLogout() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确认退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 直接清除本地保存的登录信息
|
||||
Auth.removeAll();
|
||||
|
||||
// 显示退出成功提示
|
||||
uni.showToast({
|
||||
title: '退出成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
|
||||
// 跳转到登录页面
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,6 +232,25 @@ export default {
|
||||
display: flex;
|
||||
color: black;
|
||||
align-items: center;
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20rpx;
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 30rpx;
|
||||
|
||||
.user-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-bell {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
<!-- 手机号输入 -->
|
||||
<view class="input-box">
|
||||
<u--input
|
||||
v-model="form.mobile"
|
||||
v-model="form.account"
|
||||
placeholder="+86手机号"
|
||||
prefixIcon="phone"
|
||||
prefixIconStyle="font-size: 52rpx;color: #909399;padding-right: 16rpx;"
|
||||
prefixIconStyle="font-size: 40rpx; color: #909399; padding-right: 16rpx;"
|
||||
clearable
|
||||
type="number"
|
||||
maxlength="11"
|
||||
@@ -71,7 +71,7 @@
|
||||
fontSize="30rpx"
|
||||
suffixIcon="eye"
|
||||
@clickSuffixIcon="showPassword = !showPassword"
|
||||
suffixIconStyle="font-size: 45rpx;"
|
||||
suffixIconStyle="font-size: 40rpx;"
|
||||
></u--input>
|
||||
</view>
|
||||
|
||||
@@ -101,9 +101,10 @@
|
||||
<!-- 登录按钮 -->
|
||||
<u-button
|
||||
text="登录"
|
||||
type="primary"
|
||||
type="info"
|
||||
:disabled="!canLogin"
|
||||
@click="handleLogin"
|
||||
customStyle="width: 100%; margin-top: 40rpx; height: 96rpx; border-radius: 24rpx; font-size: 32rpx; font-weight: 500;"
|
||||
customStyle="width: 100%; margin-top: 40rpx; height: 96rpx; border-radius: 24rpx; font-size: 40rpx; font-weight: bold; background-color: #2563eb; color: #fff;"
|
||||
></u-button>
|
||||
|
||||
<!-- 分割线 -->
|
||||
@@ -117,13 +118,13 @@
|
||||
<view class="other-login">
|
||||
<!-- 微信登录 -->
|
||||
<button class="wechat-btn" @click="handleWechatLogin">
|
||||
<u-icon name="weixin-fill" size="56" color="#07c160" class="wechat-icon"></u-icon>
|
||||
<u-icon name="weixin-fill" size="44" color="#07c160" class="wechat-icon"></u-icon>
|
||||
<text>使用微信登录</text>
|
||||
</button>
|
||||
|
||||
<!-- Apple登录 -->
|
||||
<button class="apple-btn" @click="handleAppleLogin">
|
||||
<u-icon name="apple-fill" size="56" color="#333333" class="apple-icon"></u-icon>
|
||||
<u-icon name="apple-fill" size="44" color="#333333" class="apple-icon"></u-icon>
|
||||
<text>使用 Apple 登录</text>
|
||||
</button>
|
||||
</view>
|
||||
@@ -135,6 +136,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { login, mobileLogin, sendCode } from '@/api/user'
|
||||
import Auth from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -144,9 +148,10 @@ export default {
|
||||
],
|
||||
current: 0,
|
||||
form: {
|
||||
mobile: '',
|
||||
account: '',
|
||||
code: '',
|
||||
password: ''
|
||||
password: '',
|
||||
typeId: 1 // 默认账号类型为运营后台/操盘手
|
||||
},
|
||||
showPassword: false,
|
||||
isAgree: false,
|
||||
@@ -161,7 +166,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
isValidMobile() {
|
||||
return /^1\d{10}$/.test(this.form.mobile)
|
||||
return /^1\d{10}$/.test(this.form.account)
|
||||
},
|
||||
canLogin() {
|
||||
if (!this.isAgree || !this.isValidMobile) return false
|
||||
@@ -185,18 +190,48 @@ export default {
|
||||
},
|
||||
getCode() {
|
||||
if (this.sending || !this.isValidMobile) return
|
||||
this.sending = true
|
||||
this.codeTips = '60s'
|
||||
let seconds = 60
|
||||
const timer = setInterval(() => {
|
||||
seconds--
|
||||
this.codeTips = `${seconds}s`
|
||||
if (seconds <= 0) {
|
||||
clearInterval(timer)
|
||||
|
||||
// 发送验证码接口调用
|
||||
sendCode({
|
||||
account: this.form.account,
|
||||
type: 'login'
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
// 发送成功,开始倒计时
|
||||
this.sending = true
|
||||
this.codeTips = '60s'
|
||||
let seconds = 60
|
||||
const timer = setInterval(() => {
|
||||
seconds--
|
||||
this.codeTips = `${seconds}s`
|
||||
if (seconds <= 0) {
|
||||
clearInterval(timer)
|
||||
this.sending = false
|
||||
this.codeTips = '发送验证码'
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
// 提示用户
|
||||
uni.showToast({
|
||||
title: '验证码已发送',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
// 发送失败
|
||||
uni.showToast({
|
||||
title: res.msg || '验证码发送失败',
|
||||
icon: 'none'
|
||||
})
|
||||
this.sending = false
|
||||
this.codeTips = '发送验证码'
|
||||
}
|
||||
}, 1000)
|
||||
}).catch(err => {
|
||||
console.error('发送验证码失败:', err)
|
||||
uni.showToast({
|
||||
title: '验证码发送失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
this.sending = false
|
||||
})
|
||||
},
|
||||
handleLogin() {
|
||||
if (!this.canLogin) {
|
||||
@@ -237,42 +272,69 @@ export default {
|
||||
mask: true
|
||||
})
|
||||
|
||||
// 模拟登录成功
|
||||
setTimeout(() => {
|
||||
// 根据当前登录方式选择不同的登录API
|
||||
const loginAction = this.current === 0 ?
|
||||
mobileLogin({
|
||||
account: this.form.account,
|
||||
code: this.form.code,
|
||||
typeId: this.form.typeId
|
||||
}) :
|
||||
login({
|
||||
account: this.form.account,
|
||||
password: this.form.password,
|
||||
typeId: this.form.typeId
|
||||
});
|
||||
|
||||
// 调用登录接口
|
||||
loginAction.then(res => {
|
||||
// 隐藏加载提示
|
||||
uni.hideLoading()
|
||||
|
||||
// 保存登录状态和用户信息
|
||||
uni.setStorageSync('token', 'mock_token_' + Date.now())
|
||||
uni.setStorageSync('userInfo', {
|
||||
mobile: this.form.mobile,
|
||||
loginTime: Date.now()
|
||||
})
|
||||
|
||||
// 显示登录成功提示
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
|
||||
// 延迟跳转到首页
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
success: () => {
|
||||
console.log('跳转到首页成功')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('跳转失败:', err)
|
||||
uni.showToast({
|
||||
title: '跳转失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
if (res.code === 200) {
|
||||
// 登录成功,保存token和用户信息
|
||||
Auth.setToken(res.data.token, res.data.token_expired - Math.floor(Date.now() / 1000));
|
||||
Auth.setUserInfo(res.data.member);
|
||||
|
||||
// 显示登录成功提示
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
}, 1500)
|
||||
}, 1000)
|
||||
|
||||
// 延迟跳转到首页
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
success: () => {
|
||||
console.log('跳转到首页成功')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('跳转失败:', err)
|
||||
uni.showToast({
|
||||
title: '跳转失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}, 1500)
|
||||
} else {
|
||||
// 登录失败
|
||||
uni.showToast({
|
||||
title: res.msg || '登录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}).catch(err => {
|
||||
// 隐藏加载提示
|
||||
uni.hideLoading()
|
||||
|
||||
console.error('登录失败:', err)
|
||||
uni.showToast({
|
||||
title: '登录失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
},
|
||||
handleWechatLogin() {
|
||||
console.log('微信登录')
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-card">
|
||||
<view class="avatar-wrap">
|
||||
<template v-if="userInfo.avatar">
|
||||
<template v-if="userInfo && userInfo.avatar">
|
||||
<image class="avatar" :src="userInfo.avatar"></image>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -22,8 +22,8 @@
|
||||
</template>
|
||||
</view>
|
||||
<view class="user-info">
|
||||
<view class="username">卡若</view>
|
||||
<view class="account">账号: 84675209</view>
|
||||
<view class="username">{{ userInfo && userInfo.username ? userInfo.username : '未设置昵称' }}</view>
|
||||
<view class="account">账号: {{ userInfo && userInfo.account ? userInfo.account : '未登录' }}</view>
|
||||
<view class="edit-profile-btn" @click="editProfile">
|
||||
编辑资料
|
||||
</view>
|
||||
@@ -74,6 +74,8 @@
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
import Auth from '@/utils/auth'
|
||||
import { getUserInfo, logout } from '@/api/user'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -81,28 +83,48 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userInfo: {
|
||||
avatar: null,
|
||||
username: '卡若',
|
||||
account: '84675209'
|
||||
}
|
||||
userInfo: null
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
// 每次显示页面时获取最新的用户信息
|
||||
this.getUserInfo();
|
||||
},
|
||||
onLoad() {
|
||||
// 检查登录状态
|
||||
if (!Auth.isLogin()) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
this.getUserInfo();
|
||||
},
|
||||
methods: {
|
||||
// 获取用户信息
|
||||
getUserInfo() {
|
||||
// 这里可以添加获取用户信息的API调用
|
||||
console.log('获取用户信息');
|
||||
// 示例数据,实际应从API获取
|
||||
this.userInfo = {
|
||||
avatar: 'https://images.unsplash.com/photo-1568602471122-7832951cc4c5?w=400&h=400&auto=format&fit=crop',
|
||||
username: '卡若',
|
||||
account: '84675209'
|
||||
};
|
||||
// 先从本地缓存获取
|
||||
const cachedUserInfo = Auth.getUserInfo();
|
||||
if (cachedUserInfo) {
|
||||
this.userInfo = cachedUserInfo;
|
||||
}
|
||||
|
||||
// 同时从服务器获取最新信息
|
||||
getUserInfo().then(res => {
|
||||
if (res.code === 200) {
|
||||
this.userInfo = res.data;
|
||||
// 更新本地缓存
|
||||
Auth.setUserInfo(res.data);
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('获取用户信息失败:', err);
|
||||
// 如果获取失败但有缓存,使用缓存数据
|
||||
if (!this.userInfo) {
|
||||
this.userInfo = Auth.getUserInfo();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到设置页面
|
||||
@@ -140,14 +162,22 @@ export default {
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 清除登录状态
|
||||
// uni.removeStorageSync('token');
|
||||
// uni.removeStorageSync('userInfo');
|
||||
// 直接清除本地保存的登录信息
|
||||
Auth.removeAll();
|
||||
|
||||
// 显示退出成功提示
|
||||
uni.showToast({
|
||||
title: '退出成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
});
|
||||
|
||||
// 跳转到登录页面
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -178,7 +208,9 @@ export default {
|
||||
z-index: 999;
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-size: 45rpx;
|
||||
font-weight: bold;
|
||||
color: #2664ec;
|
||||
}
|
||||
|
||||
.header-icons {
|
||||
|
||||
578
Cunkebao/pages/traffic/create.vue
Normal file
578
Cunkebao/pages/traffic/create.vue
Normal file
@@ -0,0 +1,578 @@
|
||||
<template>
|
||||
<view class="traffic-create-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<view class="back-icon" @click="goBack">
|
||||
<u-icon name="arrow-left" size="42" color="black"></u-icon>
|
||||
</view>
|
||||
<view class="title">{{pageTitle}}</view>
|
||||
<view class="header-right">
|
||||
<view class="save-btn" @click="saveTraffic">
|
||||
<text class="save-text">保存</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-container">
|
||||
<!-- 流量包名称 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">流量包名称</view>
|
||||
<view class="form-input-box">
|
||||
<input type="text" v-model="form.title" placeholder="例如:普通流量包" class="form-input" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 价格设置 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">价格设置(元/流量包)</view>
|
||||
<view class="form-input-box">
|
||||
<input type="digit" v-model="form.price" placeholder="0.00" class="form-input" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 流量规模 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">流量规模(人数)</view>
|
||||
<view class="form-input-box">
|
||||
<input type="number" v-model="form.quantity" placeholder="0" class="form-input" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标签管理 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">标签管理</view>
|
||||
<view class="tags-container">
|
||||
<view class="tags-list">
|
||||
<view
|
||||
v-for="(tag, index) in form.tags"
|
||||
:key="index"
|
||||
class="tag-item"
|
||||
>
|
||||
<text class="tag-text">{{tag}}</text>
|
||||
<text class="tag-delete" @click="removeTag(index)">×</text>
|
||||
</view>
|
||||
<view class="tag-add" @click="showAddTagModal">
|
||||
<text>+ 添加标签</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 流量来源 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">流量来源</view>
|
||||
<view class="source-radios">
|
||||
<view
|
||||
class="source-radio"
|
||||
:class="{ active: form.source === 'platform' }"
|
||||
@click="form.source = 'platform'"
|
||||
>
|
||||
<view class="radio-dot">
|
||||
<view class="inner-dot" v-if="form.source === 'platform'"></view>
|
||||
</view>
|
||||
<text>平台</text>
|
||||
</view>
|
||||
<view
|
||||
class="source-radio"
|
||||
:class="{ active: form.source === 'custom' }"
|
||||
@click="form.source = 'custom'"
|
||||
>
|
||||
<view class="radio-dot">
|
||||
<view class="inner-dot" v-if="form.source === 'custom'"></view>
|
||||
</view>
|
||||
<text>自定义</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地区限制 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">地区限制</view>
|
||||
<view class="region-select" @click="showRegionSelector">
|
||||
<text>{{form.region || '选择地区'}}</text>
|
||||
<u-icon name="arrow-right" size="28" color="#999"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 流量时间 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">流量时间</view>
|
||||
<view class="datetime-select" @click="showDatetimePicker">
|
||||
<text>{{form.datetime || '选择时间'}}</text>
|
||||
<u-icon name="arrow-right" size="28" color="#999"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 流量限制 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label-with-switch">
|
||||
<text>流量限制</text>
|
||||
<u-switch v-model="form.enableLimit" activeColor="#4080ff"></u-switch>
|
||||
</view>
|
||||
<view class="limit-input-box" v-if="form.enableLimit">
|
||||
<input type="number" v-model="form.limitPerDay" placeholder="每日限制数量" class="form-input" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加标签弹窗 -->
|
||||
<u-popup :show="showTagModal" @close="hideAddTagModal" mode="center">
|
||||
<view class="tag-modal">
|
||||
<view class="tag-modal-header">
|
||||
<text class="modal-title">添加标签</text>
|
||||
</view>
|
||||
<view class="tag-modal-body">
|
||||
<input type="text" v-model="newTagText" placeholder="请输入标签名称" class="tag-input" />
|
||||
</view>
|
||||
<view class="tag-modal-footer">
|
||||
<view class="modal-btn cancel-btn" @click="hideAddTagModal">取消</view>
|
||||
<view class="modal-btn confirm-btn" @click="addNewTag">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isEdit: false, // 是否为编辑模式
|
||||
trafficId: null, // 流量包ID
|
||||
form: {
|
||||
title: '', // 流量包名称
|
||||
price: '', // 价格
|
||||
quantity: '', // 流量规模
|
||||
tags: [], // 标签数组
|
||||
source: 'platform', // 流量来源:platform-平台,custom-自定义
|
||||
region: '', // 地区限制
|
||||
datetime: '', // 流量时间
|
||||
enableLimit: false, // 是否启用流量限制
|
||||
limitPerDay: '' // 每日限制数量
|
||||
},
|
||||
showTagModal: false, // 是否显示添加标签弹窗
|
||||
newTagText: '' // 新标签的文本
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 判断是否为编辑模式
|
||||
if (options.id) {
|
||||
this.isEdit = true;
|
||||
this.trafficId = options.id;
|
||||
this.loadTrafficData();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 页面标题
|
||||
pageTitle() {
|
||||
return this.isEdit ? '编辑分发' : '新建分发';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载流量包数据
|
||||
loadTrafficData() {
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
|
||||
// 模拟API请求获取数据
|
||||
setTimeout(() => {
|
||||
// 根据ID获取对应的数据
|
||||
// 这里使用模拟数据
|
||||
let mockData = {
|
||||
1: {
|
||||
title: '普通流量包',
|
||||
price: '0.50',
|
||||
quantity: '10',
|
||||
tags: ['新用户', '低活跃度', '全国'],
|
||||
source: 'platform',
|
||||
region: '全国',
|
||||
datetime: '2023-09-01',
|
||||
enableLimit: true,
|
||||
limitPerDay: '5'
|
||||
},
|
||||
2: {
|
||||
title: '高质量流量',
|
||||
price: '2.50',
|
||||
quantity: '25',
|
||||
tags: ['高消费', '高活跃度', '一线城市'],
|
||||
source: 'custom',
|
||||
region: '一线城市',
|
||||
datetime: '2023-09-05',
|
||||
enableLimit: false,
|
||||
limitPerDay: ''
|
||||
},
|
||||
3: {
|
||||
title: '精准营销流量',
|
||||
price: '3.80',
|
||||
quantity: '50',
|
||||
tags: ['潜在客户', '有购买意向', '华东地区'],
|
||||
source: 'platform',
|
||||
region: '华东地区',
|
||||
datetime: '2023-09-10',
|
||||
enableLimit: true,
|
||||
limitPerDay: '10'
|
||||
}
|
||||
};
|
||||
|
||||
// 获取数据并填充表单
|
||||
if (mockData[this.trafficId]) {
|
||||
this.form = mockData[this.trafficId];
|
||||
uni.hideLoading();
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '未找到对应数据',
|
||||
icon: 'none'
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 保存流量包
|
||||
saveTraffic() {
|
||||
// 表单验证
|
||||
if (!this.form.title) {
|
||||
uni.showToast({
|
||||
title: '请输入流量包名称',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.form.price) {
|
||||
uni.showToast({
|
||||
title: '请输入价格',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.form.quantity) {
|
||||
uni.showToast({
|
||||
title: '请输入流量规模',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 在实际应用中,这里应该提交表单数据到服务器
|
||||
uni.showLoading({
|
||||
title: this.isEdit ? '更新中...' : '保存中...'
|
||||
});
|
||||
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: this.isEdit ? '更新成功' : '创建成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
// 显示添加标签弹窗
|
||||
showAddTagModal() {
|
||||
this.showTagModal = true;
|
||||
this.newTagText = '';
|
||||
},
|
||||
|
||||
// 隐藏添加标签弹窗
|
||||
hideAddTagModal() {
|
||||
this.showTagModal = false;
|
||||
},
|
||||
|
||||
// 添加新标签
|
||||
addNewTag() {
|
||||
if (this.newTagText.trim()) {
|
||||
this.form.tags.push(this.newTagText.trim());
|
||||
this.hideAddTagModal();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '标签名称不能为空',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// 删除标签
|
||||
removeTag(index) {
|
||||
this.form.tags.splice(index, 1);
|
||||
},
|
||||
|
||||
// 显示地区选择器
|
||||
showRegionSelector() {
|
||||
// 这里应该调用地区选择器组件
|
||||
uni.showToast({
|
||||
title: '地区选择功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
},
|
||||
|
||||
// 显示时间选择器
|
||||
showDatetimePicker() {
|
||||
// 这里应该调用时间选择器组件
|
||||
uni.showToast({
|
||||
title: '时间选择功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-create-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.back-icon {
|
||||
width: 60rpx;
|
||||
color: #000;
|
||||
padding: 10rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 600;
|
||||
margin-left: -60rpx; /* 使标题居中 */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
.save-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #4080ff;
|
||||
border-radius: 30rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
color: #fff;
|
||||
|
||||
.save-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
padding: 20rpx 30rpx;
|
||||
|
||||
.form-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.form-label {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.form-label-with-switch {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.form-input-box, .limit-input-box {
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tags-container {
|
||||
.tags-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.tag-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f0f7ff;
|
||||
border-radius: 8rpx;
|
||||
padding: 10rpx 16rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.tag-text {
|
||||
font-size: 26rpx;
|
||||
color: #4080ff;
|
||||
}
|
||||
|
||||
.tag-delete {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-left: 10rpx;
|
||||
padding: 0 5rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-add {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 10rpx 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.source-radios {
|
||||
display: flex;
|
||||
|
||||
.source-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 40rpx;
|
||||
|
||||
.radio-dot {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #999;
|
||||
margin-right: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.inner-dot {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #4080ff;
|
||||
}
|
||||
}
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.radio-dot {
|
||||
border-color: #4080ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.region-select, .datetime-select {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 80rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
|
||||
text {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag-modal {
|
||||
width: 600rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.tag-modal-header {
|
||||
padding: 30rpx;
|
||||
text-align: center;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.modal-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-modal-body {
|
||||
padding: 30rpx;
|
||||
|
||||
.tag-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-modal-footer {
|
||||
display: flex;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
|
||||
.modal-btn {
|
||||
flex: 1;
|
||||
height: 90rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30rpx;
|
||||
|
||||
&.cancel-btn {
|
||||
color: #666;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
&.confirm-btn {
|
||||
color: #4080ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
305
Cunkebao/pages/traffic/index.vue
Normal file
305
Cunkebao/pages/traffic/index.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<view class="traffic-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<view class="back-icon" @click="goBack">
|
||||
<u-icon name="arrow-left" size="42" color="black"></u-icon>
|
||||
</view>
|
||||
<view class="title">流量分发</view>
|
||||
<view class="header-right">
|
||||
<view class="add-btn" @click="createTraffic">
|
||||
<u-icon name="plus" size="28" color="#fff"></u-icon>
|
||||
<text class="add-text">新建分发</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 流量包列表 -->
|
||||
<view class="traffic-list">
|
||||
<!-- 普通流量包 -->
|
||||
<view class="traffic-item">
|
||||
<view class="traffic-header">
|
||||
<text class="traffic-title">普通流量包</text>
|
||||
<view class="traffic-actions">
|
||||
<u-icon name="edit-pen" size="36" color="#999" @click="editTraffic(1)"></u-icon>
|
||||
<u-icon name="trash" size="36" color="#999" @click="deleteTraffic(1)" class="trash-icon"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="traffic-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">0.50</text>
|
||||
<text class="price-unit">/ 流量包</text>
|
||||
</view>
|
||||
<view class="traffic-info">
|
||||
<text class="total-added">总添加人数: 10 人</text>
|
||||
</view>
|
||||
<view class="traffic-tags">
|
||||
<view class="tag">新用户</view>
|
||||
<view class="tag">低活跃度</view>
|
||||
<view class="tag">全国</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 高质量流量 -->
|
||||
<view class="traffic-item">
|
||||
<view class="traffic-header">
|
||||
<text class="traffic-title">高质量流量</text>
|
||||
<view class="traffic-actions">
|
||||
<u-icon name="edit-pen" size="36" color="#999" @click="editTraffic(2)"></u-icon>
|
||||
<u-icon name="trash" size="36" color="#999" @click="deleteTraffic(2)" class="trash-icon"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="traffic-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">2.50</text>
|
||||
<text class="price-unit">/ 流量包</text>
|
||||
</view>
|
||||
<view class="traffic-info">
|
||||
<text class="total-added">总添加人数: 25 人</text>
|
||||
</view>
|
||||
<view class="traffic-tags">
|
||||
<view class="tag">高消费</view>
|
||||
<view class="tag">高活跃度</view>
|
||||
<view class="tag">一线城市</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 精准营销流量 -->
|
||||
<view class="traffic-item">
|
||||
<view class="traffic-header">
|
||||
<text class="traffic-title">精准营销流量</text>
|
||||
<view class="traffic-actions">
|
||||
<u-icon name="edit-pen" size="36" color="#999" @click="editTraffic(3)"></u-icon>
|
||||
<u-icon name="trash" size="36" color="#999" @click="deleteTraffic(3)" class="trash-icon"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="traffic-price">
|
||||
<text class="price-symbol">¥</text>
|
||||
<text class="price-value">3.80</text>
|
||||
<text class="price-unit">/ 流量包</text>
|
||||
</view>
|
||||
<view class="traffic-info">
|
||||
<text class="total-added">总添加人数: 50 人</text>
|
||||
</view>
|
||||
<view class="traffic-tags">
|
||||
<view class="tag">潜在客户</view>
|
||||
<view class="tag">有购买意向</view>
|
||||
<view class="tag">华东地区</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<CustomTabBar active="work"></CustomTabBar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomTabBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
trafficItems: [
|
||||
{
|
||||
id: 1,
|
||||
title: '普通流量包',
|
||||
price: 0.5,
|
||||
totalAdded: 10,
|
||||
tags: ['新用户', '低活跃度', '全国']
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '高质量流量',
|
||||
price: 2.5,
|
||||
totalAdded: 25,
|
||||
tags: ['高消费', '高活跃度', '一线城市']
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '精准营销流量',
|
||||
price: 3.8,
|
||||
totalAdded: 50,
|
||||
tags: ['潜在客户', '有购买意向', '华东地区']
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 创建新的流量分发
|
||||
createTraffic() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/traffic/create'
|
||||
})
|
||||
},
|
||||
|
||||
// 编辑流量分发
|
||||
editTraffic(id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/traffic/create?id=${id}`
|
||||
})
|
||||
},
|
||||
|
||||
// 删除流量分发
|
||||
deleteTraffic(id) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该流量分发吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 模拟删除操作
|
||||
this.trafficItems = this.trafficItems.filter(item => item.id !== id);
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.traffic-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
padding-bottom: 150rpx; /* 为底部导航栏预留空间 */
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.back-icon {
|
||||
width: 60rpx;
|
||||
color: #000;
|
||||
padding: 10rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 600;
|
||||
margin-left: -60rpx; /* 使标题居中 */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
.add-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #4080ff;
|
||||
border-radius: 30rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
color: #fff;
|
||||
|
||||
.add-text {
|
||||
font-size: 28rpx;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.traffic-list {
|
||||
padding: 30rpx;
|
||||
|
||||
.traffic-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
.traffic-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.traffic-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.traffic-actions {
|
||||
display: flex;
|
||||
|
||||
.trash-icon {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.traffic-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.price-symbol {
|
||||
font-size: 30rpx;
|
||||
color: #1cc15e;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
font-size: 60rpx;
|
||||
color: #1cc15e;
|
||||
font-weight: 700;
|
||||
font-family: 'Digital-Bold', sans-serif;
|
||||
margin: 0 8rpx;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.traffic-info {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.total-added {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.traffic-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.tag {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
919
Cunkebao/pages/wechat/detail.vue
Normal file
919
Cunkebao/pages/wechat/detail.vue
Normal file
@@ -0,0 +1,919 @@
|
||||
<template>
|
||||
<view class="detail-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<view class="back-icon" @click="goBack">
|
||||
<u-icon name="arrow-left" size="42" color="black"></u-icon>
|
||||
</view>
|
||||
<view class="title">账号详情</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 账号信息卡片 -->
|
||||
<view class="account-card">
|
||||
<view class="avatar-section">
|
||||
<image :src="accountInfo.avatar || '/static/images/avatar.png'" mode="aspectFill" class="avatar-img"></image>
|
||||
</view>
|
||||
<view class="basic-info">
|
||||
<view class="name-status">
|
||||
<text class="account-name">{{accountInfo.name}}</text>
|
||||
<text class="status-tag" :class="getStatusClass(accountInfo.status)">{{accountInfo.status}}</text>
|
||||
</view>
|
||||
<view class="account-id">微信号:{{accountInfo.wechatId}}</view>
|
||||
<view class="action-buttons">
|
||||
<view class="action-btn device-btn" @click="goToDeviceDetail">
|
||||
<u-icon name="setting" size="28" color="#333"></u-icon>
|
||||
<text>设备{{accountInfo.deviceNumber}}</text>
|
||||
</view>
|
||||
<view class="action-btn friends-btn" @click="showTransferModal">
|
||||
<u-icon name="account" size="28" color="#333"></u-icon>
|
||||
<text>好友转移</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 好友转移模态框 -->
|
||||
<view class="transfer-modal" v-if="showTransferModalFlag">
|
||||
<view class="modal-mask" @click="hideTransferModal"></view>
|
||||
<view class="modal-content">
|
||||
<view class="modal-header">
|
||||
<text class="modal-title">好友转移确认</text>
|
||||
<view class="close-btn" @click="hideTransferModal">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="transfer-title">即将导出该微信号的好友列表,用于创建新的获客计划</view>
|
||||
|
||||
<view class="account-info-box">
|
||||
<view class="account-avatar">
|
||||
<image :src="accountInfo.avatar || '/static/images/avatar.png'" mode="aspectFill" class="avatar-small"></image>
|
||||
</view>
|
||||
<view class="account-detail">
|
||||
<text class="account-name-text">{{accountInfo.name}}</text>
|
||||
<text class="account-id-text">{{accountInfo.wechatId}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="transfer-tips">
|
||||
<view class="tip-item">
|
||||
<text class="dot">•</text>
|
||||
<text class="tip-text">将导出该账号下的所有好友信息</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="dot">•</text>
|
||||
<text class="tip-text">好友信息将用于创建新的订单获客计划</text>
|
||||
</view>
|
||||
<view class="tip-item">
|
||||
<text class="dot">•</text>
|
||||
<text class="tip-text">导出过程中请勿关闭页面</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-footer">
|
||||
<view class="cancel-btn" @click="hideTransferModal">取消</view>
|
||||
<view class="confirm-btn" @click="confirmTransfer">确认转移</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 标签栏 -->
|
||||
<view class="tab-section">
|
||||
<view class="tab-item" :class="{ active: activeTab === 'overview' }" @click="activeTab = 'overview'">
|
||||
<text>账号概览</text>
|
||||
</view>
|
||||
<view class="tab-item" :class="{ active: activeTab === 'friends' }" @click="activeTab = 'friends'">
|
||||
<text>好友列表 ({{accountInfo.friendsCount}})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号概览内容 -->
|
||||
<block v-if="activeTab === 'overview'">
|
||||
<!-- 年龄卡片 -->
|
||||
<view class="info-card">
|
||||
<view class="card-header">
|
||||
<u-icon name="calendar" size="28" color="#666"></u-icon>
|
||||
<text class="card-title">账号年龄</text>
|
||||
</view>
|
||||
<view class="age-content">
|
||||
<view class="age-value">{{accountInfo.age}}</view>
|
||||
<view class="age-register">注册时间:{{accountInfo.registerDate}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 活跃度卡片 -->
|
||||
<view class="info-card">
|
||||
<view class="card-header">
|
||||
<u-icon name="chat" size="28" color="#666"></u-icon>
|
||||
<text class="card-title">活跃程度</text>
|
||||
</view>
|
||||
<view class="activity-content">
|
||||
<view class="activity-value">{{accountInfo.activityRate}}次/天</view>
|
||||
<view class="total-days">总聊天数:{{accountInfo.totalChatDays}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号权重评估 -->
|
||||
<view class="info-card">
|
||||
<view class="card-header">
|
||||
<u-icon name="star" size="28" color="#FFAD33"></u-icon>
|
||||
<text class="card-title">账号权重评估</text>
|
||||
<text class="score">{{accountInfo.score}} 分</text>
|
||||
</view>
|
||||
<view class="evaluate-content">
|
||||
<view class="evaluate-text">账号状态良好</view>
|
||||
|
||||
<!-- 账号年龄评分 -->
|
||||
<view class="evaluate-item">
|
||||
<view class="item-header">
|
||||
<text class="item-name">账号年龄</text>
|
||||
<text class="item-percent">{{accountInfo.ageScore}}%</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" :style="{width: accountInfo.ageScore + '%', backgroundColor: '#4080ff'}"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 活跃度评分 -->
|
||||
<view class="evaluate-item">
|
||||
<view class="item-header">
|
||||
<text class="item-name">活跃度</text>
|
||||
<text class="item-percent">{{accountInfo.activityScore}}%</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" :style="{width: accountInfo.activityScore + '%', backgroundColor: '#4080ff'}"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 限制影响评分 -->
|
||||
<view class="evaluate-item">
|
||||
<view class="item-header">
|
||||
<text class="item-name">限制影响</text>
|
||||
<text class="item-percent">{{accountInfo.limitScore}}%</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" :style="{width: accountInfo.limitScore + '%', backgroundColor: '#4080ff'}"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 实名认证评分 -->
|
||||
<view class="evaluate-item">
|
||||
<view class="item-header">
|
||||
<text class="item-name">实名认证</text>
|
||||
<text class="item-percent">{{accountInfo.verifyScore}}%</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" :style="{width: accountInfo.verifyScore + '%', backgroundColor: '#4080ff'}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加好友统计 -->
|
||||
<view class="info-card">
|
||||
<view class="card-header">
|
||||
<u-icon name="account-add" size="28" color="#4080ff"></u-icon>
|
||||
<text class="card-title">添加好友统计</text>
|
||||
<u-icon name="info-circle" size="28" color="#999"></u-icon>
|
||||
</view>
|
||||
<view class="friends-stat-content">
|
||||
<view class="stat-row">
|
||||
<text class="stat-label">今日已添加</text>
|
||||
<text class="stat-value blue">{{accountInfo.todayAdded}}</text>
|
||||
</view>
|
||||
<view class="stat-row">
|
||||
<text class="stat-label">添加进度</text>
|
||||
<text class="stat-progress">{{accountInfo.todayAdded}}/{{accountInfo.dailyLimit}}</text>
|
||||
</view>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" :style="{width: (accountInfo.todayAdded / accountInfo.dailyLimit * 100) + '%', backgroundColor: '#4080ff'}"></view>
|
||||
</view>
|
||||
<view class="limit-tip">
|
||||
<text>根据当前账号权重({{accountInfo.score}}分),每日最多可添加 {{accountInfo.dailyLimit}} 个好友</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 限制记录 -->
|
||||
<view class="info-card">
|
||||
<view class="card-header">
|
||||
<u-icon name="warning" size="28" color="#fa5151"></u-icon>
|
||||
<text class="card-title">限制记录</text>
|
||||
<text class="limit-count">共 {{accountInfo.limitRecords.length}} 次</text>
|
||||
</view>
|
||||
<view class="limit-records">
|
||||
<view class="limit-item" v-for="(record, index) in accountInfo.limitRecords" :key="index">
|
||||
<text class="limit-reason">{{record.reason}}</text>
|
||||
<text class="limit-date">{{record.date}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 好友列表内容 -->
|
||||
<block v-if="activeTab === 'friends'">
|
||||
<view class="friends-list-container">
|
||||
<view class="search-filter">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索好友"
|
||||
:showAction="false"
|
||||
shape="round"
|
||||
bgColor="#f4f4f4"
|
||||
></u-search>
|
||||
</view>
|
||||
|
||||
<view class="friends-list">
|
||||
<view class="empty-tip" v-if="filteredFriends.length === 0">
|
||||
<text>暂无好友数据</text>
|
||||
</view>
|
||||
<view class="friend-item" v-for="(friend, index) in filteredFriends" :key="index">
|
||||
<image :src="friend.avatar" mode="aspectFill" class="friend-avatar"></image>
|
||||
<view class="friend-info">
|
||||
<view class="friend-name">{{friend.name}}</view>
|
||||
<view class="friend-remark">备注:{{friend.remark || '无'}}</view>
|
||||
</view>
|
||||
<view class="friend-action">
|
||||
<view class="action-button">
|
||||
<u-icon name="chat" size="24" color="#4080ff"></u-icon>
|
||||
<text>聊天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<CustomTabBar active="profile"></CustomTabBar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomTabBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
id: '', // 微信账号ID
|
||||
activeTab: 'overview', // 当前激活的标签页:overview 或 friends
|
||||
searchKeyword: '', // 搜索关键词
|
||||
showTransferModalFlag: false, // 是否显示好友转移模态框
|
||||
accountInfo: {
|
||||
avatar: '/static/images/avatar.png',
|
||||
name: '卡若-25vig',
|
||||
status: '正常',
|
||||
wechatId: 'wxid_hahphr2h',
|
||||
deviceNumber: '1',
|
||||
friendsCount: 192,
|
||||
age: '2年8个月',
|
||||
registerDate: '2021-06-15',
|
||||
activityRate: '42',
|
||||
totalChatDays: '15,234',
|
||||
score: 85,
|
||||
ageScore: 90,
|
||||
activityScore: 85,
|
||||
limitScore: 80,
|
||||
verifyScore: 100,
|
||||
todayAdded: 12,
|
||||
dailyLimit: 17,
|
||||
limitRecords: [
|
||||
{ reason: '添加好友过于频繁', date: '2024-02-25' },
|
||||
{ reason: '营销内容违规', date: '2024-01-15' }
|
||||
]
|
||||
},
|
||||
friends: [] // 好友列表
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 过滤后的好友列表
|
||||
filteredFriends() {
|
||||
if (!this.searchKeyword) return this.friends;
|
||||
return this.friends.filter(friend =>
|
||||
friend.name.includes(this.searchKeyword) ||
|
||||
(friend.remark && friend.remark.includes(this.searchKeyword))
|
||||
);
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.id) {
|
||||
this.id = options.id;
|
||||
this.loadAccountInfo();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 获取状态样式类
|
||||
getStatusClass(status) {
|
||||
if (status === '正常') return 'status-normal';
|
||||
if (status === '运营') return 'status-running';
|
||||
return '';
|
||||
},
|
||||
|
||||
// 跳转到设备详情页
|
||||
goToDeviceDetail() {
|
||||
// 根据账号关联的设备编号跳转
|
||||
uni.navigateTo({
|
||||
url: `/pages/device/detail?id=${this.accountInfo.deviceNumber}`
|
||||
});
|
||||
},
|
||||
|
||||
// 显示好友转移模态框
|
||||
showTransferModal() {
|
||||
this.showTransferModalFlag = true;
|
||||
},
|
||||
|
||||
// 隐藏好友转移模态框
|
||||
hideTransferModal() {
|
||||
this.showTransferModalFlag = false;
|
||||
},
|
||||
|
||||
// 确认好友转移
|
||||
confirmTransfer() {
|
||||
// 在实际应用中,这里应该调用API进行好友转移
|
||||
uni.showLoading({
|
||||
title: '转移中...'
|
||||
});
|
||||
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '好友转移成功',
|
||||
icon: 'success'
|
||||
});
|
||||
this.hideTransferModal();
|
||||
}, 1500);
|
||||
},
|
||||
|
||||
// 加载账号信息
|
||||
loadAccountInfo() {
|
||||
// 这里应该是API调用,现在使用模拟数据
|
||||
console.log(`加载账号信息: ${this.id}`);
|
||||
|
||||
// 模拟加载好友数据
|
||||
this.friends = [
|
||||
{
|
||||
id: 1,
|
||||
name: '张三',
|
||||
avatar: '/static/images/avatar.png',
|
||||
remark: '客户-北京'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '李四',
|
||||
avatar: '/static/images/avatar.png',
|
||||
remark: '客户-上海'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '王五',
|
||||
avatar: '/static/images/avatar.png',
|
||||
remark: '客户-广州'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.detail-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
padding-bottom: 150rpx; /* 为底部导航栏预留空间 */
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.back-icon {
|
||||
width: 60rpx;
|
||||
color: #000;
|
||||
padding: 10rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 600;
|
||||
margin-left: -60rpx; /* 使标题居中 */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.account-card {
|
||||
margin: 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.avatar-section {
|
||||
margin-right: 30rpx;
|
||||
|
||||
.avatar-img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.basic-info {
|
||||
flex: 1;
|
||||
|
||||
.name-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.account-name {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 24rpx;
|
||||
padding: 2rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&.status-normal {
|
||||
background-color: #4cd964;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.status-running {
|
||||
background-color: #ff6b6b;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-id {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f5f5f5;
|
||||
margin-right: 20rpx;
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-section {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
margin: 0 30rpx 20rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #4080ff;
|
||||
font-weight: 500;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 6rpx;
|
||||
background-color: #4080ff;
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-card {
|
||||
margin: 0 30rpx 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.card-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
margin-left: 10rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #4cd964;
|
||||
}
|
||||
|
||||
.limit-count {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.age-content, .activity-content {
|
||||
padding: 20rpx 0;
|
||||
|
||||
.age-value, .activity-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: 600;
|
||||
color: #4080ff;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.age-register, .total-days {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.evaluate-content {
|
||||
.evaluate-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.evaluate-item {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.item-name {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-percent {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.friends-stat-content {
|
||||
.stat-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.stat-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
|
||||
&.blue {
|
||||
color: #4080ff;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-progress {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-tip {
|
||||
margin-top: 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-records {
|
||||
.limit-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.limit-reason {
|
||||
font-size: 28rpx;
|
||||
color: #fa5151;
|
||||
}
|
||||
|
||||
.limit-date {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
position: relative;
|
||||
height: 10rpx;
|
||||
margin: 10rpx 0;
|
||||
|
||||
.progress-bg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ebeef5;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.friends-list-container {
|
||||
margin: 0 30rpx;
|
||||
|
||||
.search-filter {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.friends-list {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
|
||||
.empty-tip {
|
||||
padding: 40rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.friend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.friend-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.friend-info {
|
||||
flex: 1;
|
||||
|
||||
.friend-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.friend-remark {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.friend-action {
|
||||
.action-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #4080ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 好友转移模态框样式 */
|
||||
.transfer-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.modal-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
width: 650rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
z-index: 1001;
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
.modal-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.close-icon {
|
||||
font-size: 44rpx;
|
||||
color: #999;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30rpx;
|
||||
|
||||
.transfer-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.account-info-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f8f9fc;
|
||||
padding: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.account-avatar {
|
||||
margin-right: 20rpx;
|
||||
|
||||
.avatar-small {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.account-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.account-name-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.account-id-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.transfer-tips {
|
||||
margin-top: 20rpx;
|
||||
|
||||
.tip-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 15rpx;
|
||||
|
||||
.dot {
|
||||
margin-right: 10rpx;
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
padding: 20rpx 30rpx 40rpx;
|
||||
|
||||
.cancel-btn, .confirm-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 40rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #4080ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
439
Cunkebao/pages/wechat/index.vue
Normal file
439
Cunkebao/pages/wechat/index.vue
Normal file
@@ -0,0 +1,439 @@
|
||||
<template>
|
||||
<view class="wechat-container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="header">
|
||||
<view class="back-icon" @click="goBack">
|
||||
<u-icon name="arrow-left" size="42" color="black"></u-icon>
|
||||
</view>
|
||||
<view class="title">微信号</view>
|
||||
<view class="header-right"></view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<view class="search-input">
|
||||
<u-search
|
||||
v-model="searchKeyword"
|
||||
placeholder="搜索微信号/昵称"
|
||||
:showAction="false"
|
||||
shape="round"
|
||||
:clearabled="true"
|
||||
height="70"
|
||||
bgColor="#f4f4f4"
|
||||
></u-search>
|
||||
</view>
|
||||
<view class="filter-icon">
|
||||
<u-icon name="filter" size="36" color="#000"></u-icon>
|
||||
</view>
|
||||
<view class="refresh-icon">
|
||||
<u-icon name="reload" size="36" color="#000"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信号列表 -->
|
||||
<view class="wechat-list">
|
||||
<!-- 微信号项 1 -->
|
||||
<view class="wechat-item" @click="goToDetail(wechatAccounts[0])">
|
||||
<view class="wechat-avatar">
|
||||
<image src="/static/images/avatar.png" mode="aspectFill" class="avatar-img"></image>
|
||||
</view>
|
||||
<view class="wechat-info">
|
||||
<view class="wechat-header">
|
||||
<view class="wechat-name">卡若-25vig</view>
|
||||
<view class="wechat-status">
|
||||
<text class="status-tag status-running">运营</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wechat-id">微信号:wxid_8evmgz0y</view>
|
||||
<view class="friends-info">
|
||||
<text class="friends-count">好友数量:6095</text>
|
||||
<text class="friends-added">今日新增:<text class="added-count">+7</text></text>
|
||||
</view>
|
||||
<view class="daily-limit">
|
||||
<text class="limit-text">今日可添加:8</text>
|
||||
<view class="limit-progress">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" style="width: 35%;"></view>
|
||||
</view>
|
||||
<text class="limit-count">7/20</text>
|
||||
</view>
|
||||
<view class="device-info">
|
||||
<text class="device-label">所属设备:</text>
|
||||
<text class="device-value">设备1</text>
|
||||
<text class="last-active-label">最后活跃:</text>
|
||||
<text class="last-active-value">2025/3/26 12:25:10</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wechat-action" @click.stop="transferFriends(wechatAccounts[0].wechatId)">
|
||||
<view class="transfer-btn">
|
||||
<u-icon name="swap-right" size="28" color="#333"></u-icon>
|
||||
<text>好友转移</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信号项 2 -->
|
||||
<view class="wechat-item" @click="goToDetail(wechatAccounts[1])">
|
||||
<view class="wechat-avatar">
|
||||
<image src="/static/images/avatar.png" mode="aspectFill" class="avatar-img"></image>
|
||||
</view>
|
||||
<view class="wechat-info">
|
||||
<view class="wechat-header">
|
||||
<view class="wechat-name">卡若-zok7e</view>
|
||||
<view class="wechat-status">
|
||||
<text class="status-tag status-normal">正常</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wechat-id">微信号:wxid_7mlqr91i</view>
|
||||
<view class="friends-info">
|
||||
<text class="friends-count">好友数量:4149</text>
|
||||
<text class="friends-added">今日新增:<text class="added-count">+11</text></text>
|
||||
</view>
|
||||
<view class="daily-limit">
|
||||
<text class="limit-text">今日可添加:5</text>
|
||||
<view class="limit-progress">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" style="width: 55%;"></view>
|
||||
</view>
|
||||
<text class="limit-count">11/20</text>
|
||||
</view>
|
||||
<view class="device-info">
|
||||
<text class="device-label">所属设备:</text>
|
||||
<text class="device-value">设备1</text>
|
||||
<text class="last-active-label">最后活跃:</text>
|
||||
<text class="last-active-value">2025/3/26 11:30:34</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wechat-action" @click.stop="transferFriends(wechatAccounts[1].wechatId)">
|
||||
<view class="transfer-btn">
|
||||
<u-icon name="swap-right" size="28" color="#333"></u-icon>
|
||||
<text>好友转移</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信号项 3 -->
|
||||
<view class="wechat-item" @click="goToDetail(wechatAccounts[2])">
|
||||
<view class="wechat-avatar">
|
||||
<image src="/static/images/avatar.png" mode="aspectFill" class="avatar-img"></image>
|
||||
</view>
|
||||
<view class="wechat-info">
|
||||
<view class="wechat-header">
|
||||
<view class="wechat-name">卡若-ip9ob</view>
|
||||
<view class="wechat-status">
|
||||
<text class="status-tag status-normal">正常</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wechat-id">微信号:wxid_jzfn1nmr</view>
|
||||
<view class="friends-info">
|
||||
<text class="friends-count">好友数量:4131</text>
|
||||
<text class="friends-added">今日新增:<text class="added-count">+11</text></text>
|
||||
</view>
|
||||
<view class="daily-limit">
|
||||
<text class="limit-text">今日可添加:11</text>
|
||||
<view class="limit-progress">
|
||||
<view class="progress-bg"></view>
|
||||
<view class="progress-value" style="width: 55%;"></view>
|
||||
</view>
|
||||
<text class="limit-count">11/20</text>
|
||||
</view>
|
||||
<view class="device-info">
|
||||
<text class="device-label">所属设备:</text>
|
||||
<text class="device-value">设备1</text>
|
||||
<text class="last-active-label">最后活跃:</text>
|
||||
<text class="last-active-value">2025/3/26 10:45:22</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wechat-action" @click.stop="transferFriends(wechatAccounts[2].wechatId)">
|
||||
<view class="transfer-btn">
|
||||
<u-icon name="swap-right" size="28" color="#333"></u-icon>
|
||||
<text>好友转移</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<CustomTabBar active="profile"></CustomTabBar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomTabBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchKeyword: '',
|
||||
wechatAccounts: [
|
||||
{
|
||||
name: '卡若-25vig',
|
||||
status: '运营',
|
||||
wechatId: 'wxid_8evmgz0y',
|
||||
friendsCount: 6095,
|
||||
todayAdded: 7,
|
||||
todayLimit: 8,
|
||||
maxLimit: 20,
|
||||
device: '设备1',
|
||||
lastActive: '2025/3/26 12:25:10'
|
||||
},
|
||||
{
|
||||
name: '卡若-zok7e',
|
||||
status: '正常',
|
||||
wechatId: 'wxid_7mlqr91i',
|
||||
friendsCount: 4149,
|
||||
todayAdded: 11,
|
||||
todayLimit: 5,
|
||||
maxLimit: 20,
|
||||
device: '设备1',
|
||||
lastActive: '2025/3/26 11:30:34'
|
||||
},
|
||||
{
|
||||
name: '卡若-ip9ob',
|
||||
status: '正常',
|
||||
wechatId: 'wxid_jzfn1nmr',
|
||||
friendsCount: 4131,
|
||||
todayAdded: 11,
|
||||
todayLimit: 11,
|
||||
maxLimit: 20,
|
||||
device: '设备1',
|
||||
lastActive: '2025/3/26 10:45:22'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
|
||||
// 好友转移
|
||||
transferFriends(wechatId) {
|
||||
uni.showToast({
|
||||
title: `${wechatId} 好友转移功能即将上线`,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到详情页
|
||||
goToDetail(account) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/wechat/detail?id=${account.wechatId}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wechat-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
padding-bottom: 150rpx; /* 为底部导航栏预留空间 */
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
.back-icon {
|
||||
width: 60rpx;
|
||||
color: #000;
|
||||
padding: 10rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 38rpx;
|
||||
font-weight: 600;
|
||||
margin-left: -60rpx; /* 使标题居中 */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
width: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #fff;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filter-icon, .refresh-icon {
|
||||
margin-left: 20rpx;
|
||||
padding: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.wechat-list {
|
||||
padding: 20rpx 30rpx;
|
||||
|
||||
.wechat-item {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
position: relative;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.wechat-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-right: 20rpx;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.wechat-info {
|
||||
flex: 1;
|
||||
|
||||
.wechat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
.wechat-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.wechat-status {
|
||||
.status-tag {
|
||||
font-size: 24rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border-radius: 6rpx;
|
||||
|
||||
&.status-running {
|
||||
background-color: #ff6b6b;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.status-normal {
|
||||
background-color: #4cd964;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wechat-id {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.friends-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
|
||||
.friends-count {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.added-count {
|
||||
color: #4cd964;
|
||||
}
|
||||
}
|
||||
|
||||
.daily-limit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.limit-text {
|
||||
width: 200rpx;
|
||||
}
|
||||
|
||||
.limit-progress {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
height: 10rpx;
|
||||
margin: 0 16rpx;
|
||||
|
||||
.progress-bg {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #ebeef5;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.progress-value {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
background-color: #4080ff;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.limit-count {
|
||||
width: 100rpx;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.device-info {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
|
||||
.device-label, .last-active-label {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.device-value {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wechat-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.transfer-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1 +1,477 @@
|
||||
|
||||
<template>
|
||||
<view class="work-container">
|
||||
<!-- 顶部标题 -->
|
||||
<view class="header">
|
||||
<text class="title">工作台</text>
|
||||
</view>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<view class="stats-cards">
|
||||
<view class="stats-card">
|
||||
<view class="stats-label">总任务数</view>
|
||||
<view class="stats-value blue">42</view>
|
||||
<view class="progress-bar">
|
||||
<view class="progress-filled blue" style="width: 70%;"></view>
|
||||
</view>
|
||||
<view class="stats-detail">
|
||||
<text>进行中:12 / 已完成:30</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-card light-green">
|
||||
<view class="stats-label">今日任务</view>
|
||||
<view class="stats-value green">12</view>
|
||||
<view class="trend-info">
|
||||
<u-icon name="arrow-up" color="#2fc25b" size="20"></u-icon>
|
||||
<text class="trend-text">活跃度 98%</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 常用功能 -->
|
||||
<view class="section-title">常用功能</view>
|
||||
|
||||
<view class="function-grid">
|
||||
<!-- 流量分发 -->
|
||||
<view class="function-card" @click="navigateTo('/pages/traffic/index')">
|
||||
<view class="icon-wrapper light-green">
|
||||
<text class="icon">$</text>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">流量分发</view>
|
||||
<view class="function-desc">定义你的流量价格</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自动点赞 -->
|
||||
<view class="function-card" @click="handleAutoLike">
|
||||
<view class="icon-wrapper light-blue">
|
||||
<u-icon name="thumb-up" color="#4080ff" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">自动点赞</view>
|
||||
<view class="function-desc">定时对好友朋友圈点赞</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 朋友圈同步 -->
|
||||
<view class="function-card" @click="handleMomentSync">
|
||||
<view class="icon-wrapper light-purple">
|
||||
<u-icon name="reload" color="#7551ff" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">朋友圈同步</view>
|
||||
<view class="function-desc">多微信朋友圈同步发布</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 微信号管理 -->
|
||||
<view class="function-card" @click="navigateTo('/pages/wechat/index')">
|
||||
<view class="icon-wrapper light-green">
|
||||
<u-icon name="weixin-fill" color="#48d2a0" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">微信号管理</view>
|
||||
<view class="function-desc">管理已绑定的微信账号</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 群消息推送 -->
|
||||
<view class="function-card" @click="handleGroupMessage">
|
||||
<view class="icon-wrapper light-orange">
|
||||
<u-icon name="chat" color="#ff9e45" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">群消息推送</view>
|
||||
<view class="function-desc">批量向群内自动发消息</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自动建群 -->
|
||||
<view class="function-card" @click="handleAutoGroup">
|
||||
<view class="icon-wrapper light-green">
|
||||
<u-icon name="team" color="#48d2a0" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">自动建群</view>
|
||||
<view class="function-desc">智能匹分好友建群</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI话术助手 -->
|
||||
<view class="function-card" @click="handleAIChatAssistant">
|
||||
<view class="icon-wrapper light-blue">
|
||||
<u-icon name="tv" color="#4080ff" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">AI话术助手</view>
|
||||
<view class="function-desc">智能回复,提高互动质量</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI智能助手 -->
|
||||
<view class="section-title">AI 智能助手</view>
|
||||
|
||||
<view class="function-grid">
|
||||
<!-- AI数据分析 -->
|
||||
<view class="function-card" @click="handleAIDataAnalysis">
|
||||
<view class="icon-wrapper light-blue">
|
||||
<u-icon name="tv" color="#4080ff" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">AI数据分析</view>
|
||||
<view class="function-desc">智能分析客户行为特征</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- AI策略优化 -->
|
||||
<view class="function-card" @click="handleAIStrategy">
|
||||
<view class="icon-wrapper light-cyan">
|
||||
<u-icon name="tv" color="#36cfc9" size="28"></u-icon>
|
||||
</view>
|
||||
<view class="function-content">
|
||||
<view class="function-name">AI策略优化</view>
|
||||
<view class="function-desc">智能优化获客策略</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容库卡片 -->
|
||||
<view class="func-card" @click="goToTraffic">
|
||||
<view class="func-card-icon traffic-icon">
|
||||
<u-icon name="man-add-fill" color="#fff" size="44"></u-icon>
|
||||
</view>
|
||||
<view class="func-card-info">
|
||||
<text class="func-card-title">流量池</text>
|
||||
<text class="func-card-desc">管理您的流量用户</text>
|
||||
</view>
|
||||
<view class="func-card-arrow">
|
||||
<u-icon name="arrow-right" color="#ccc" size="28"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="func-card" @click="goToContent">
|
||||
<view class="func-card-icon content-icon">
|
||||
<u-icon name="folder" color="#fff" size="44"></u-icon>
|
||||
</view>
|
||||
<view class="func-card-info">
|
||||
<text class="func-card-title">内容库</text>
|
||||
<text class="func-card-desc">管理微信内容素材</text>
|
||||
</view>
|
||||
<view class="func-card-arrow">
|
||||
<u-icon name="arrow-right" color="#ccc" size="28"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部TabBar -->
|
||||
<CustomTabBar active="work"></CustomTabBar>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CustomTabBar from '@/components/CustomTabBar.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomTabBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 将来可从API获取的数据
|
||||
totalTasks: 42,
|
||||
completedTasks: 30,
|
||||
todayTasks: 12,
|
||||
activeRate: 98
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 页面导航
|
||||
navigateTo(url) {
|
||||
uni.navigateTo({
|
||||
url: url
|
||||
});
|
||||
},
|
||||
|
||||
// 自动点赞功能处理
|
||||
handleAutoLike() {
|
||||
// 由于尚未实现该页面,我们显示一个toast提示
|
||||
this.showFunctionMessage('自动点赞');
|
||||
},
|
||||
|
||||
// 朋友圈同步功能处理
|
||||
handleMomentSync() {
|
||||
this.showFunctionMessage('朋友圈同步');
|
||||
},
|
||||
|
||||
// 群消息推送功能处理
|
||||
handleGroupMessage() {
|
||||
this.showFunctionMessage('群消息推送');
|
||||
},
|
||||
|
||||
// 自动建群功能处理
|
||||
handleAutoGroup() {
|
||||
this.showFunctionMessage('自动建群');
|
||||
},
|
||||
|
||||
// AI话术助手功能处理
|
||||
handleAIChatAssistant() {
|
||||
this.showFunctionMessage('AI话术助手');
|
||||
},
|
||||
|
||||
// AI数据分析功能处理
|
||||
handleAIDataAnalysis() {
|
||||
this.showFunctionMessage('AI数据分析');
|
||||
},
|
||||
|
||||
// AI策略优化功能处理
|
||||
handleAIStrategy() {
|
||||
this.showFunctionMessage('AI策略优化');
|
||||
},
|
||||
|
||||
// 显示功能消息
|
||||
showFunctionMessage(functionName) {
|
||||
uni.showToast({
|
||||
title: `${functionName}功能即将上线`,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到流量池页面
|
||||
goToTraffic() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/traffic/index'
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转到内容库页面
|
||||
goToContent() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/content/index'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.work-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f9fafb;
|
||||
padding: 0 30rpx 150rpx;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 30rpx 0 20rpx;
|
||||
|
||||
.title {
|
||||
font-size: 42rpx;
|
||||
font-weight: 600;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-cards {
|
||||
display: flex;
|
||||
margin: 20rpx 0 30rpx;
|
||||
|
||||
.stats-card {
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
margin-right: 15rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
background-color: #f5fffa;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 50rpx;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
&.blue {
|
||||
color: #4080ff;
|
||||
}
|
||||
|
||||
&.green {
|
||||
color: #2fc25b;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 10rpx;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.progress-filled {
|
||||
height: 100%;
|
||||
background-color: #4080ff;
|
||||
border-radius: 10rpx;
|
||||
|
||||
&.blue {
|
||||
background-color: #4080ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stats-detail {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.trend-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
|
||||
.trend-text {
|
||||
font-size: 24rpx;
|
||||
color: #2fc25b;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #000;
|
||||
margin: 30rpx 0 20rpx;
|
||||
}
|
||||
|
||||
.function-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -10rpx;
|
||||
|
||||
.function-card {
|
||||
width: calc(50% - 20rpx);
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin: 0 10rpx 20rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.icon-wrapper {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 16rpx;
|
||||
|
||||
&.light-green {
|
||||
background-color: rgba(47, 194, 91, 0.1);
|
||||
}
|
||||
|
||||
&.light-pink {
|
||||
background-color: rgba(255, 87, 122, 0.1);
|
||||
}
|
||||
|
||||
&.light-purple {
|
||||
background-color: rgba(112, 102, 224, 0.1);
|
||||
}
|
||||
|
||||
&.light-orange {
|
||||
background-color: rgba(255, 153, 0, 0.1);
|
||||
}
|
||||
|
||||
&.light-blue {
|
||||
background-color: rgba(64, 128, 255, 0.1);
|
||||
}
|
||||
|
||||
&.light-cyan {
|
||||
background-color: rgba(54, 207, 201, 0.1);
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-size: 34rpx;
|
||||
color: #2fc25b;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.function-content {
|
||||
flex: 1;
|
||||
|
||||
.function-name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.function-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.func-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.func-card-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 16rpx;
|
||||
|
||||
&.traffic-icon {
|
||||
background-color: #4080ff;
|
||||
}
|
||||
|
||||
&.content-icon {
|
||||
background-color: #2fc25b;
|
||||
}
|
||||
}
|
||||
|
||||
.func-card-info {
|
||||
flex: 1;
|
||||
|
||||
.func-card-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.func-card-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.func-card-arrow {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1
Cunkebao/static/images/avatar.png
Normal file
1
Cunkebao/static/images/avatar.png
Normal file
@@ -0,0 +1 @@
|
||||
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAALF0lEQVR4nO1da4hkVxX+9kyGYBJREw0KGtSQGBWD+MMojO6AEklIVBSM+g/xB5I4M1XVnT2JERFJouJPUYkBFRUfSVQQUcnEeXR3Ve/d3TUddU0wGk2IRB/4nM6dWVPTPdN963FP1fmg6E7fulV1vrPOPvvsc/YphUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhoIWyKsv6/eZZZRme7/fD3aVp/E5RtN4qIu4uy/D6sgyvL8v4gqLovs2KKLEZqbOz1bPC4Cc+6e99CXGnKTpvOQA46cuy7LyuKMLHTk7GPSKMUYIyNdU9xwXhR/FJnK33Zyxgt3grzE8JzYMi0nOqoJdleKFLxAM2CU+VRnhfUbRO1+BkBsrWfQQu+aIIb7ZK+BOJ4PoSjrCZXy5CL4gQ7pFULLB9f6fVaj7DN1KmQLlRQB0RUeoApUbImRvUK+LETyrDPeK4ixz+3f9Oe0xAERneUISw3YVjl1I+FOJB29/VfF1C/Ift9qbX+jZVSxkgV7XODJ3wJx2AnbZfF/1O/KJm+3ujiYSX+H3xhNsU/9d26wWbhPBvFEe7s/MaPynZ46wz2judfqfX/v0KgbXj+c6YmQvhA34vFcLk1EbFR02SXdERSXnGJa15/7JtP0Tx/mf7tY+p2t3rvFJC2OF8Vfg/YGp8u90919e2AiTnzLEsw10rTjqc9NF2+7kWeNRt2+NjHt9Ps1OZp1ILykrjbpb9vtcWUHKlTclEPVeF9dq2X/5eAc0BsX073HJ/14PhVLGhCd6gA6A5tztXSYAPxVdwZb4Ym5Ls8xzALwQQWvWo0nPE8Ds+9lFhRMITb/n3jI9V4SFXCSG5B85SBsjYWOdsF4gfnYQQPuPK8J8j9J27PYQnf+p2PebjDiN85Osd3ue/L5aK+/xlbHk9sU3LZwAc3W6W/UvYfyshfcCRHpjudlvnS6Z5NKvb3UtTpiyK+OKiCL/tH5YnlwYJ4bUaZFzdeoHYZ/sMrQ3/O44X/eD9ByN8zzd6YnxscnLMqc1LXJl+Vt6e6HS6l/o1vdoVv1tS3aEHZHR08z6B8LLg19cVK4RdLpRPesj9txLS3xnC3SJylq/3BXyDSJxilbg3pLrH/eZZlrHNj/9GbJbfH6ELmB+LCy1qTzZG8iUg9TGfq7sDkbCPQuJTnLBgH/y8hPgNPsBN0sL9nHxAJmbi+f7gHXYN9/sVv3riqDtXGMPrUvdcAgjtxMSVZwvEA/z9EF3I/DijFNd0fsbBdgF9uoxXujZU/5+YaG2RVCQl5n/sn7tDQvZCwj8MQZaRXC9V9h8YOD4uSWkA0T+/Xl7Q79OHMORmxW3+yXs0Wkxzo6BZMX/ygUqgOcxSV/7VKYb3+Ocf5qgzW8uPnRIY9oO4cULkRaVJyvK2f+j+ZTM6FfoOGpiYuHGm22291qryRhHxnfIwvvvVEX+zYjuFODM+3jnNb+i9zmA/7qXCl9mvN8iQvtffqJI+wzfBe8ZdYZMbzP5QiHo5Ui2fqRzBuoCYFG2jYNjvS4iPyLTm/vzYjJt1GUrdx/p3/5l9Vma4c9Wz/GZL4mvgMykGQNgGdoFS9p1r5JnYWV5Gs+N1N6yLUhfB3FDvUf6z1sJCfI29OfmYfXPkYJQ1hHTGQuhV6sP1KifFBQ5hNdzG5ZjE1Z9Mf1X5qfAV2xef1JVg0Bqr/0f8vaeP+Pw9AhCjq3gNs7wqXLr/Ue/vLY7LWMEoAxKuY5+q9fQTvH788WYuEJtOuZtXxR17FJO+Xa08Bf7AYOVaCvHfcrteiJtkwLjAvUdCfMA9+f+Svj1RxBvHxpqX+A27ztfYJZV6a2qzYgpTLK81MLrL9c7+ZIGTcAcDmGuZj7C3Tm1ZQ7RDiLdv6rZea8+J2/e8hEhVu4D5pNJMD0goyVapjFJXgJjV1f2XwjZrA3INq9qUTz8uK13EzO2jYvyoCpw+RVKytk1HXlbWIHSPPg0r8VT/UPwRfSdUx6Yo2YNgD5sFO1qLgDBzq9oWHIi7WB05Fb96oEMI5WDvuVAHxUa2ISD8AJo/LdpQAkL1+a+y7MadUpgfaHTJjnC1gPCnx41IXxOL8EgTR0vy9wO9LwvF1VFDQNgwZGmNfehndvfT4FcnAyA37EZAmOpb4UYzISAsuYnFWNTl4QeOqISsC0BM95d7JyYmmhAQZl1VHf2nvQ+Gg6e9QLgWaYGQM5B1AcjUVOtsrlvxzqrBMZNFDVIVxO/uLxAmozf7QO3o+ICQp3MBSOEDDZyYHNQgt3JmBITLUbVWXDVe1NcP7w8IcwkjAGQ/mTQJIUwyljNKwh73lLsVDPo/Sd4GdxgA4RM9sBGK8hQhYyAXqBLCKY83jY3FR9sBgZxuFwt6pWKEfTJRgw8Ii3drzxb3Asg+QvzGiQBh0b8LyPdPGBDOnDglzm03J+Lj2wkEkjOWsvXO7HTe4KbskwHyDxeQ1roHhPyqXgDJCQgZLrWRAgm3ZQ2EtUlUVBYrj6QBIYMmy/EQJuP4gMuYLfA2Ub9qwxTXkfkC4YTWiVXL9fHWJPGxDJMpCu4jP8uaZjc0QEaOV0P0Asgg86yOOQQsUGXPeoH9pSgOgJFzfLZWULjSxJWSVNqbk+VrSvUMTAi7ixcO/J6OIQzVVSYLiwKQYCjpQPYGhAvS+/u9FUPMPShDmCbgrq1/5VbGIl9OmBTvwQU9JzjG7Hq7RDz8PvDJZQiEA7L9lDmzm4/0GFgv+fC+M1eKvf5Uf8mW/+xH90Tn7MuBJfX83yxH6acXuPTLfYXeRyeDBRn+uHQ2M++Xk4TwW0bZ/SaOrOBz3e02B9jRlTXZpnw3cLc/jm3qdq4Q8VpfuvVODZU4QkBWhV+fWNn5wX0+JOu9MHRJbFZDCIgqQNYJAVHXGMiJABnGaG4vgDDf4xtSbkBO0DYw+TeFJqPrIR51e/eqoWTLrHD5UaZb0GvmNNQWU8tnpzJ3LiB3D3Ht2VozKFV7WXwwRwEIexZOtNzcGxBObK4nQJiQxJOzwefN4wnOzZ6qWz8+3nnhMJVIWNzEBv9+AFE6kG8QhvwbW0P459nMD7U/o3VfF4CQ58pCKAsB2G0xKyC+b4v0HO7jMhSAuCkYCKp2QDwlRZ46HTmqKGsgyy+F4D4v3N6H1JHMgHDD2fU0uWrTdGmAkGPPg+O+x9H83TCGlUtA3OpcQy2xEZMXdMJAliPvq0lUqzPCupdx1RMAYVEqu6PZ2OAOKAEHhB1RvJF1/p+vNBG3jfF9X6kj6w4QduLTTZxWUXQwAGHbPltduFp9ugHEz+dJ72rniYR0fczWEQMgrFGRSpW1AcLOd7Z78E2o+w0QL1a9jk/1iQBhxzuL7NL0aqcA4h1Yq/x9OtxfgMwupv+xLpnq8+8u+nZIDzrW2FZtWbO9NrPjsIYrnmZJr8MpgHQp7hDpd4DLTgEL6xXu5s6FGlLaqw+xvZ5bnZA31ms/V0/7BRRFo55pQ24Yk9F0lp7s+x9u7Z7srkJgkgzrS8YJijngOWAzzDs6xCEAAA6dSURBVCj/l1C/lW/r1A4a/iJjhUKhUCgUCoVCoVAoFAqFQqFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBQKBRn/A1JxgAhWnMiQAAAAAElFTkSuQmCC
|
||||
BIN
Cunkebao/static/images/empty.png
Normal file
BIN
Cunkebao/static/images/empty.png
Normal file
Binary file not shown.
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* 认证相关工具函数
|
||||
*/
|
||||
import { refreshToken } from '@/api/user';
|
||||
|
||||
const TOKEN_KEY = 'token';
|
||||
const TOKEN_EXPIRES_KEY = 'token_expires';
|
||||
@@ -67,6 +68,28 @@ function removeAll() {
|
||||
removeUserInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Token
|
||||
* @returns {Promise} 刷新结果
|
||||
*/
|
||||
function refreshTokenAsync() {
|
||||
return new Promise((resolve, reject) => {
|
||||
refreshToken()
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
// 更新Token
|
||||
setToken(res.data.token, res.data.token_expired - Math.floor(Date.now() / 1000));
|
||||
resolve(res);
|
||||
} else {
|
||||
reject(res);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否已登录
|
||||
* @returns {boolean} 是否已登录
|
||||
@@ -84,6 +107,24 @@ function isLogin() {
|
||||
return nowTime < expiresTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户类型
|
||||
* @returns {number} 用户类型ID
|
||||
*/
|
||||
function getUserType() {
|
||||
const userInfo = getUserInfo();
|
||||
return userInfo ? userInfo.typeId || 0 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为管理员
|
||||
* @returns {boolean} 是否为管理员
|
||||
*/
|
||||
function isAdmin() {
|
||||
const userInfo = getUserInfo();
|
||||
return userInfo ? !!userInfo.isAdmin : false;
|
||||
}
|
||||
|
||||
export default {
|
||||
setToken,
|
||||
getToken,
|
||||
@@ -92,5 +133,8 @@ export default {
|
||||
getUserInfo,
|
||||
removeUserInfo,
|
||||
removeAll,
|
||||
isLogin
|
||||
isLogin,
|
||||
refreshToken: refreshTokenAsync,
|
||||
getUserType,
|
||||
isAdmin
|
||||
};
|
||||
@@ -1,9 +1,7 @@
|
||||
import Auth from './auth';
|
||||
|
||||
// 服务器地址
|
||||
const BASE_URL = process.env.NODE_ENV === 'development'
|
||||
? 'http://localhost:8080'
|
||||
: 'https://api.example.com';
|
||||
const BASE_URL = process.env.VUE_APP_BASE_API || 'http://yishi.com';
|
||||
|
||||
// 请求超时时间
|
||||
const TIMEOUT = 10000;
|
||||
@@ -17,7 +15,7 @@ function requestInterceptor(config) {
|
||||
// 获取 token
|
||||
const token = uni.getStorageSync('token');
|
||||
|
||||
// 如果有 token,则带上请求头
|
||||
// 如果有 token,则带上请求头 Authorization: Bearer + token
|
||||
if (token) {
|
||||
config.header = {
|
||||
...config.header,
|
||||
@@ -39,11 +37,8 @@ function requestInterceptor(config) {
|
||||
function responseInterceptor(response) {
|
||||
// 未登录或token失效 - 取消登录拦截
|
||||
if (response.data.code === 401) {
|
||||
// 只在控制台打印信息,不进行拦截
|
||||
console.log('登录已过期,但不进行拦截');
|
||||
console.log('登录已过期,需要重新登录');
|
||||
|
||||
/*
|
||||
// 以下代码已注释,取消登录拦截
|
||||
// 清除登录信息
|
||||
Auth.removeToken();
|
||||
Auth.removeUserInfo();
|
||||
@@ -54,29 +49,43 @@ function responseInterceptor(response) {
|
||||
});
|
||||
|
||||
return Promise.reject(new Error('登录已过期,请重新登录'));
|
||||
*/
|
||||
|
||||
// 直接返回响应,不拦截
|
||||
return response.data;
|
||||
}
|
||||
|
||||
// token需要刷新 - 取消登录拦截
|
||||
// token需要刷新 - 410 状态码
|
||||
if (response.data.code === 410) {
|
||||
// 只在控制台打印信息,不进行拦截
|
||||
console.log('Token需要刷新,但不进行拦截');
|
||||
|
||||
/*
|
||||
// 以下代码已注释,取消登录拦截
|
||||
// 处理token刷新逻辑,这里简化处理
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
|
||||
return Promise.reject(new Error('登录已过期,请重新登录'));
|
||||
*/
|
||||
|
||||
// 直接返回响应,不拦截
|
||||
return response.data;
|
||||
// 尝试刷新 token
|
||||
return Auth.refreshToken()
|
||||
.then(res => {
|
||||
if (res.code === 200) {
|
||||
// 更新本地token
|
||||
Auth.setToken(res.data.token, res.data.token_expired - Math.floor(Date.now() / 1000));
|
||||
|
||||
// 使用新token重试原请求
|
||||
const config = response.config;
|
||||
config.header.Authorization = `Bearer ${res.data.token}`;
|
||||
|
||||
// 重新发起请求
|
||||
return request(config);
|
||||
} else {
|
||||
// 刷新失败,跳转到登录页
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
return Promise.reject(new Error('登录已过期,请重新登录'));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('刷新token失败', err);
|
||||
// 清除登录信息
|
||||
Auth.removeToken();
|
||||
Auth.removeUserInfo();
|
||||
|
||||
// 跳转到登录页
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/index'
|
||||
});
|
||||
return Promise.reject(new Error('登录已过期,请重新登录'));
|
||||
});
|
||||
}
|
||||
|
||||
return response.data;
|
||||
|
||||
@@ -53,8 +53,8 @@ class Auth extends Controller
|
||||
public function login()
|
||||
{
|
||||
// 获取登录参数
|
||||
$params = Request::only(['username', 'password', 'is_encrypted']);
|
||||
|
||||
$params = Request::only(['account', 'password', 'typeId']);
|
||||
|
||||
// 参数验证
|
||||
$validate = validate('common/Auth');
|
||||
if (!$validate->scene('login')->check($params)) {
|
||||
@@ -62,16 +62,14 @@ class Auth extends Controller
|
||||
}
|
||||
|
||||
try {
|
||||
// 判断密码是否已加密
|
||||
$isEncrypted = isset($params['is_encrypted']) && $params['is_encrypted'] === true;
|
||||
|
||||
// 调用登录服务
|
||||
$result = $this->authService->login(
|
||||
$params['username'],
|
||||
$params['account'],
|
||||
$params['password'],
|
||||
Request::ip(),
|
||||
$isEncrypted
|
||||
$params['typeId'],
|
||||
Request::ip()
|
||||
);
|
||||
|
||||
return ResponseHelper::success($result, '登录成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage());
|
||||
@@ -85,7 +83,7 @@ class Auth extends Controller
|
||||
public function mobileLogin()
|
||||
{
|
||||
// 获取登录参数
|
||||
$params = Request::only(['mobile', 'code', 'is_encrypted']);
|
||||
$params = Request::only(['account', 'code', 'typeId']);
|
||||
|
||||
// 参数验证
|
||||
$validate = validate('common/Auth');
|
||||
@@ -99,7 +97,7 @@ class Auth extends Controller
|
||||
|
||||
// 调用手机号登录服务
|
||||
$result = $this->authService->mobileLogin(
|
||||
$params['mobile'],
|
||||
$params['account'],
|
||||
$params['code'],
|
||||
Request::ip(),
|
||||
$isEncrypted
|
||||
@@ -118,7 +116,7 @@ class Auth extends Controller
|
||||
public function sendCode()
|
||||
{
|
||||
// 获取参数
|
||||
$params = Request::only(['mobile', 'type']);
|
||||
$params = Request::only(['account', 'type']);
|
||||
|
||||
// 参数验证
|
||||
$validate = validate('common/Auth');
|
||||
@@ -129,7 +127,7 @@ class Auth extends Controller
|
||||
try {
|
||||
// 调用发送验证码服务
|
||||
$result = $this->authService->sendLoginCode(
|
||||
$params['mobile'],
|
||||
$params['account'],
|
||||
$params['type']
|
||||
);
|
||||
return ResponseHelper::success($result, '验证码发送成功');
|
||||
|
||||
@@ -30,134 +30,148 @@ class User extends Model
|
||||
* 创建时间字段
|
||||
* @var string
|
||||
*/
|
||||
protected $createTime = 'create_at';
|
||||
protected $createTime = 'createTime';
|
||||
|
||||
/**
|
||||
* 更新时间字段
|
||||
* @var string
|
||||
*/
|
||||
protected $updateTime = 'update_at';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
/**
|
||||
* 软删除字段
|
||||
* @var string
|
||||
*/
|
||||
protected $deleteTime = 'delete_at';
|
||||
protected $deleteTime = 'deleteTime';
|
||||
|
||||
/**
|
||||
* 隐藏属性
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = ['password', 'delete_at'];
|
||||
protected $hidden = ['passwordMd5', 'passwordLocal', 'deleteTime'];
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
* @var array
|
||||
*/
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'isAdmin' => 'integer',
|
||||
'companyId' => 'integer',
|
||||
'typeId' => 'integer',
|
||||
'lastLoginTime' => 'integer',
|
||||
'status' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer',
|
||||
'deleteTime' => 'integer'
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取管理员用户信息
|
||||
* @param string $username 用户名
|
||||
* @param string $account 账号(手机号)
|
||||
* @param string $password 密码(可能是加密后的)
|
||||
* @param bool $isEncrypted 密码是否已加密
|
||||
* @param int $typeId 身份信息
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getAdminUser($username, $password, $isEncrypted = false)
|
||||
public static function getAdminUser($account, $password, $typeId)
|
||||
{
|
||||
// 查询用户
|
||||
$user = self::where('username', $username)->find();
|
||||
|
||||
$user = self::where('account', $account)
|
||||
->where('typeId', $typeId)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$user) {
|
||||
// 记录日志
|
||||
\think\facade\Log::info('用户不存在', ['username' => $username]);
|
||||
\think\facade\Log::info('用户不存在或已禁用', ['account' => $account]);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 记录密码验证信息
|
||||
\think\facade\Log::info('密码验证', [
|
||||
'username' => $username,
|
||||
'account' => $account,
|
||||
'input_password' => $password,
|
||||
'stored_hash' => $user->password,
|
||||
'is_encrypted' => $isEncrypted,
|
||||
'password_info' => password_get_info($user->password)
|
||||
'stored_hash' => $user->passwordMd5,
|
||||
]);
|
||||
|
||||
// 验证密码
|
||||
$isValid = false;
|
||||
|
||||
if ($isEncrypted) {
|
||||
// 前端已加密,直接比较哈希值
|
||||
// 注意:这里需要确保前端和后端使用相同的加密算法和盐值
|
||||
$storedHash = self::getStoredHash($user->password);
|
||||
$isValid = hash_equals($storedHash, $password);
|
||||
|
||||
\think\facade\Log::info('加密密码验证', [
|
||||
'username' => $username,
|
||||
'stored_hash' => $storedHash,
|
||||
'input_hash' => $password,
|
||||
'is_valid' => $isValid
|
||||
]);
|
||||
} else {
|
||||
// 未加密,使用password_verify验证
|
||||
$isValid = password_verify($password, $user->password);
|
||||
}
|
||||
|
||||
$isValid = password_verify($password, $user->passwordMd5);
|
||||
|
||||
\think\facade\Log::info('密码验证结果', [
|
||||
'username' => $username,
|
||||
'account' => $account,
|
||||
'is_valid' => $isValid,
|
||||
'is_encrypted' => $isEncrypted
|
||||
]);
|
||||
|
||||
if (!$isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新登录信息
|
||||
$user->lastLoginIp = request()->ip();
|
||||
$user->lastLoginTime = time();
|
||||
$user->save();
|
||||
|
||||
// 用手机号当做默认用户名(如果没有设置用户名)
|
||||
$username = $user->username ?: $user->account;
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'name' => $user->username, // 暂时使用username作为name
|
||||
'role' => 'admin', // 暂时固定为admin角色
|
||||
'permissions' => ['*'], // 暂时拥有所有权限
|
||||
'username' => $username,
|
||||
'account' => $user->account,
|
||||
'avatar' => $user->avatar,
|
||||
'isAdmin' => $user->isAdmin,
|
||||
'companyId' => $user->companyId,
|
||||
'typeId' => $user->typeId,
|
||||
'lastLoginIp' => $user->lastLoginIp,
|
||||
'lastLoginTime' => $user->lastLoginTime
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储的哈希值
|
||||
* 用于前端加密密码的验证
|
||||
* @param string $bcryptHash 数据库中存储的bcrypt哈希值
|
||||
* @return string 用于前端验证的哈希值
|
||||
*/
|
||||
protected static function getStoredHash($bcryptHash)
|
||||
{
|
||||
// 这里需要实现与前端相同的加密算法
|
||||
// 例如,如果前端使用SHA256加盐,这里需要提取原始密码并进行相同的处理
|
||||
// 注意:这只是一个示例,实际实现可能需要根据您的具体需求调整
|
||||
|
||||
// 假设我们能够从bcrypt哈希中提取原始密码(实际上这是不可能的,这里只是示例)
|
||||
// 在实际应用中,您需要在用户注册或修改密码时同时存储前端加密的哈希值
|
||||
$originalPassword = '123456'; // 这里应该是从数据库中获取的原始密码
|
||||
$salt = 'yishi_salt_2024'; // 与前端相同的盐值
|
||||
|
||||
// 使用与前端相同的算法
|
||||
return hash('sha256', $originalPassword . $salt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过手机号获取用户信息
|
||||
* @param string $mobile 手机号
|
||||
* @param string $account 手机号
|
||||
* @return array|null
|
||||
*/
|
||||
public static function getUserByMobile($mobile)
|
||||
public static function getUserByMobile($account)
|
||||
{
|
||||
// 查询用户
|
||||
$user = self::where('mobile', $mobile)->find();
|
||||
$user = self::where('account', $account)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
|
||||
if (!$user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 用手机号当做默认用户名(如果没有设置用户名)
|
||||
$username = $user->username ?: $user->account;
|
||||
// 默认头像地址
|
||||
$avatar = $user->avatar ?: '';
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'name' => $user->username, // 暂时使用username作为name
|
||||
'mobile' => $user->mobile,
|
||||
'role' => 'user', // 暂时固定为user角色
|
||||
'permissions' => ['user'], // 暂时拥有用户权限
|
||||
'username' => $username,
|
||||
'account' => $user->account,
|
||||
'avatar' => $avatar,
|
||||
'isAdmin' => $user->isAdmin,
|
||||
'companyId' => $user->companyId,
|
||||
'typeId' => $user->typeId,
|
||||
'lastLoginIp' => $user->lastLoginIp,
|
||||
'lastLoginTime' => $user->lastLoginTime
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户密码
|
||||
* @param string $password 密码
|
||||
* @param bool $isEncrypted 是否已加密
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyPassword($password, $isEncrypted = false)
|
||||
{
|
||||
if ($isEncrypted) {
|
||||
return hash_equals($this->passwordMd5, $password);
|
||||
} else {
|
||||
return $this->passwordMd5 === md5($password);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,22 +31,21 @@ class AuthService
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param string $username 用户名
|
||||
* @param string $account 账号(手机号)
|
||||
* @param string $password 密码(可能是加密后的)
|
||||
* @param string $ip 登录IP
|
||||
* @param bool $isEncrypted 密码是否已加密
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function login($username, $password, $ip, $isEncrypted = false)
|
||||
public function login($account, $password, $typeId, $ip)
|
||||
{
|
||||
// 获取用户信息
|
||||
$user = User::getAdminUser($username, $password, $isEncrypted);
|
||||
|
||||
$user = User::getAdminUser($account, $password, $typeId);
|
||||
|
||||
if (empty($user)) {
|
||||
// 记录登录失败
|
||||
Log::info('登录失败', ['username' => $username, 'ip' => $ip, 'is_encrypted' => $isEncrypted]);
|
||||
throw new \Exception('用户名或密码错误');
|
||||
Log::info('登录失败', ['account' => $account, 'ip' => $ip, 'is_encrypted' => true]);
|
||||
throw new \Exception('账号或密码错误');
|
||||
}
|
||||
|
||||
// 生成JWT令牌
|
||||
@@ -54,7 +53,7 @@ class AuthService
|
||||
$expireTime = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
// 记录登录成功
|
||||
Log::info('登录成功', ['username' => $username, 'ip' => $ip]);
|
||||
Log::info('登录成功', ['account' => $account, 'ip' => $ip]);
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
@@ -65,25 +64,25 @@ class AuthService
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
* @param string $mobile 手机号
|
||||
* @param string $account 手机号
|
||||
* @param string $code 验证码(可能是加密后的)
|
||||
* @param string $ip 登录IP
|
||||
* @param bool $isEncrypted 验证码是否已加密
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function mobileLogin($mobile, $code, $ip, $isEncrypted = false)
|
||||
public function mobileLogin($account, $code, $ip, $isEncrypted = false)
|
||||
{
|
||||
// 验证验证码
|
||||
if (!$this->smsService->verifyCode($mobile, $code, 'login', $isEncrypted)) {
|
||||
Log::info('验证码验证失败', ['mobile' => $mobile, 'ip' => $ip, 'is_encrypted' => $isEncrypted]);
|
||||
if (!$this->smsService->verifyCode($account, $code, 'login', $isEncrypted)) {
|
||||
Log::info('验证码验证失败', ['account' => $account, 'ip' => $ip, 'is_encrypted' => $isEncrypted]);
|
||||
throw new \Exception('验证码错误或已过期');
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
$user = User::getUserByMobile($mobile);
|
||||
$user = User::getUserByMobile($account);
|
||||
if (empty($user)) {
|
||||
Log::info('用户不存在', ['mobile' => $mobile, 'ip' => $ip]);
|
||||
Log::info('用户不存在', ['account' => $account, 'ip' => $ip]);
|
||||
throw new \Exception('用户不存在');
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ class AuthService
|
||||
$expireTime = time() + self::TOKEN_EXPIRE;
|
||||
|
||||
// 记录登录成功
|
||||
Log::info('手机号登录成功', ['mobile' => $mobile, 'ip' => $ip]);
|
||||
Log::info('手机号登录成功', ['account' => $account, 'ip' => $ip]);
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
@@ -103,14 +102,14 @@ class AuthService
|
||||
|
||||
/**
|
||||
* 发送登录验证码
|
||||
* @param string $mobile 手机号
|
||||
* @param string $account 手机号
|
||||
* @param string $type 验证码类型
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function sendLoginCode($mobile, $type)
|
||||
public function sendLoginCode($account, $type)
|
||||
{
|
||||
return $this->smsService->sendCode($mobile, $type);
|
||||
return $this->smsService->sendCode($account, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,12 +13,10 @@ class Auth extends Validate
|
||||
* @var array
|
||||
*/
|
||||
protected $rule = [
|
||||
'username' => 'require|length:3,20',
|
||||
'account' => 'require|mobile',
|
||||
'password' => 'require|length:6,64',
|
||||
'mobile' => 'require|mobile',
|
||||
'code' => 'require|length:4,6',
|
||||
'is_encrypted' => 'boolean',
|
||||
'type' => 'require|in:login,register',
|
||||
'typeId' => 'require|in:1,2',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -26,17 +24,14 @@ class Auth extends Validate
|
||||
* @var array
|
||||
*/
|
||||
protected $message = [
|
||||
'username.require' => '用户名不能为空',
|
||||
'username.length' => '用户名长度必须在3-20个字符之间',
|
||||
'account.require' => '账号不能为空',
|
||||
'account.mobile' => '账号格式不正确(需要是手机号)',
|
||||
'password.require' => '密码不能为空',
|
||||
'password.length' => '密码长度必须在6-64个字符之间',
|
||||
'mobile.require' => '手机号不能为空',
|
||||
'mobile.mobile' => '手机号格式不正确',
|
||||
'code.require' => '验证码不能为空',
|
||||
'code.length' => '验证码长度必须在4-6个字符之间',
|
||||
'is_encrypted.boolean' => '加密标志必须为布尔值',
|
||||
'type.require' => '验证码类型不能为空',
|
||||
'type.in' => '验证码类型不正确',
|
||||
'typeId.require' => '用户类型不能为空',
|
||||
'typeId.in' => '用户类型错误',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -44,9 +39,9 @@ class Auth extends Validate
|
||||
* @var array
|
||||
*/
|
||||
protected $scene = [
|
||||
'login' => ['username', 'password', 'is_encrypted'],
|
||||
'mobile_login' => ['mobile', 'code', 'is_encrypted'],
|
||||
'login' => ['account', 'password', 'typeId'],
|
||||
'mobile_login' => ['account', 'code', 'typeId'],
|
||||
'refresh' => [],
|
||||
'send_code' => ['mobile', 'type'],
|
||||
'send_code' => ['account', 'type'],
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user