first commit
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
23
.hbuilderx/launch.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
// launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version" : "0.0",
|
||||
"configurations" : [
|
||||
{
|
||||
"app-plus" : {
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"default" : {
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"mp-weixin" : {
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
},
|
||||
{
|
||||
"playground" : "standard",
|
||||
"type" : "uni-app:app-android"
|
||||
}
|
||||
]
|
||||
}
|
||||
107
AGENTS.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# AGENTS.md - Guide for Coding Agents
|
||||
|
||||
## Project Overview
|
||||
This is a **uni-app** project (Vue 3 + TypeScript + Vite) for building multi-platform applications (primarily WeChat Mini Program, also supports H5, App, etc.). It uses uview-plus UI component library and is set up with TypeScript.
|
||||
|
||||
## Build & Development Commands
|
||||
|
||||
### Development Servers
|
||||
- `npm run dev:mp-weixin` - Run dev server for WeChat Mini Program
|
||||
- `npm run dev:h5` - Run dev server for H5 (browser)
|
||||
- `npm run dev:app` - Run dev server for App platform
|
||||
- `npm run dev:custom` - Run dev server with custom platform selection
|
||||
|
||||
### Production Builds
|
||||
- `npm run build:mp-weixin` - Build for WeChat Mini Program
|
||||
- `npm run build:h5` - Build for H5
|
||||
- `npm run build:app` - Build for App platform
|
||||
|
||||
### Type Checking
|
||||
- `npm run type-check` - Run TypeScript type checking (vue-tsc --noEmit)
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### Imports
|
||||
- Use path alias `@/*` for imports from `src/` directory
|
||||
- Example: `import { Service } from '@/Service/Service'`
|
||||
- Group imports by: external libraries → internal modules
|
||||
- No specific linter/formatter configured, but follow existing style
|
||||
|
||||
### TypeScript
|
||||
- Use TypeScript for all new code
|
||||
- Define types/interfaces for data models
|
||||
- Use `any` sparingly, prefer proper type definitions
|
||||
- Project uses `"extends": "@vue/tsconfig/tsconfig.json"`
|
||||
|
||||
### Naming Conventions
|
||||
- **Files**: PascalCase for TypeScript classes (e.g., `Service.ts`, `HttpRequest.ts`), kebab-case for Vue components in pages (e.g., `my.vue`, `address.vue`)
|
||||
- **Classes**: PascalCase (e.g., `Service`, `HttpRequest`)
|
||||
- **Methods/Functions**: camelCase (e.g., `GetUserToken()`, `formatDate()`)
|
||||
- **Variables**: camelCase
|
||||
- **Constants**: UPPER_SNAKE_CASE (when applicable)
|
||||
|
||||
### Directory Structure
|
||||
- `src/pages/` - Page components (main pages and subPackages)
|
||||
- `src/components/` - Reusable Vue components
|
||||
- `src/Service/` - API service layer and base config
|
||||
- `src/common/` - Common utilities (Unit, Domain)
|
||||
- `src/common/Unit/` - Helper classes (HttpRequest, StorageAssist, etc.)
|
||||
- `src/common/Domain/` - Domain models (ResultData, etc.)
|
||||
- `src/static/` - Static assets (images, icons, etc.)
|
||||
|
||||
### Error Handling
|
||||
- Use `Service.Request()` for API calls which handles:
|
||||
- Token authentication (adds Bearer token)
|
||||
- 401 (token expired) → clears token, redirects to login
|
||||
- Other error codes (40101, 1004, 40188, 1008)
|
||||
- Use `uni.showToast()` for user feedback via `Service.Msg()`
|
||||
- Use `Service.Alert()` for modal dialogs
|
||||
|
||||
### API Requests
|
||||
- Always use `Service.Request(url, method, data)` for API calls
|
||||
- HTTP methods: 'GET', 'POST', 'PUT'
|
||||
- Request returns `Promise<ResultData>`
|
||||
- `ResultData` has: `code`, `msg`, `data` properties
|
||||
|
||||
### UI Components
|
||||
- Use **uview-plus** (imported as `u-*` components via easycom)
|
||||
- Easycom auto-import configured: `^u-(.*)` → `uview-plus/components/u-$1/u-$1.vue`
|
||||
- Color UI is also available (imported in App.vue)
|
||||
|
||||
### Vue Components
|
||||
- Use `<script setup lang="ts">` for Vue 3 composition API
|
||||
- Use uni-app lifecycle hooks: `onLaunch`, `onShow`, `onHide`, etc.
|
||||
- SCSS is supported for styles
|
||||
|
||||
### Storage
|
||||
- Use `Service.SetStorageCache(key, data)` to set storage
|
||||
- Use `Service.GetStorageCache(key)` to get storage
|
||||
- Use `Service.DelStorageCache(key)` to delete storage
|
||||
- Token is stored with key 'token', user state with 'StateDomain'
|
||||
|
||||
### Navigation
|
||||
- `Service.GoPage(path)` - Navigate to new page (uni.navigateTo)
|
||||
- `Service.GoPageDelse(path)` - Redirect (replace current page, uni.redirectTo)
|
||||
- `Service.GoPageTab(path)` - Switch to tab bar page (uni.switchTab)
|
||||
- `Service.GoPageBack()` - Go back (uni.navigateBack)
|
||||
|
||||
### Key Utilities
|
||||
- `Service.throttle(fn, time)` - Throttle function
|
||||
- `Service.debounce(fn, time)` - Debounce function
|
||||
- `Service.formatDate(time, type)` - Format timestamps
|
||||
- `Service.Msg(message, icon?)` - Show toast
|
||||
- `Service.LoadIng(text)` / `Service.LoadClose()` - Show/hide loading
|
||||
|
||||
## Testing
|
||||
No test framework configured in this project yet.
|
||||
|
||||
## Existing Patterns to Follow
|
||||
- Look at `src/Service/Service.ts` for how API requests are structured
|
||||
- Look at `src/common/Unit/HttpRequest.ts` for lower-level request handling
|
||||
- Check existing pages in `src/pages/` for component structure examples
|
||||
- Use `ResultData` class from `@/common/Domain/ResultData` for API responses
|
||||
|
||||
## Important Notes
|
||||
- This is a multi-platform project - write code compatible with uni-app standards
|
||||
- Be cautious with platform-specific APIs - use uni-app conditional compilation (`#ifdef MP-WEIXIN`, etc.) if needed
|
||||
- Global colors defined in App.vue: --nav-mian (#FF6A00), --nav-vice (#FF7F00), --nav-diluted (#F2C0A3)
|
||||
20
index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||
CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
13365
package-lock.json
generated
Normal file
80
package.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "uni-preset-vue",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev:app": "uni -p app",
|
||||
"dev:app-android": "uni -p app-android",
|
||||
"dev:app-ios": "uni -p app-ios",
|
||||
"dev:custom": "uni -p",
|
||||
"dev:h5": "uni",
|
||||
"dev:h5:ssr": "uni --ssr",
|
||||
"dev:mp-alipay": "uni -p mp-alipay",
|
||||
"dev:mp-baidu": "uni -p mp-baidu",
|
||||
"dev:mp-jd": "uni -p mp-jd",
|
||||
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||
"dev:mp-lark": "uni -p mp-lark",
|
||||
"dev:mp-qq": "uni -p mp-qq",
|
||||
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"dev:mp-xhs": "uni -p mp-xhs",
|
||||
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||
"build:app": "uni build -p app",
|
||||
"build:app-android": "uni build -p app-android",
|
||||
"build:app-ios": "uni build -p app-ios",
|
||||
"build:custom": "uni build -p",
|
||||
"build:h5": "uni build",
|
||||
"build:h5:ssr": "uni build --ssr",
|
||||
"build:mp-alipay": "uni build -p mp-alipay",
|
||||
"build:mp-baidu": "uni build -p mp-baidu",
|
||||
"build:mp-jd": "uni build -p mp-jd",
|
||||
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||
"build:mp-lark": "uni build -p mp-lark",
|
||||
"build:mp-qq": "uni build -p mp-qq",
|
||||
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||
"build:mp-weixin": "uni build -p mp-weixin",
|
||||
"build:mp-xhs": "uni build -p mp-xhs",
|
||||
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||
"type-check": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@climblee/uv-ui": "^1.1.20",
|
||||
"@dcloudio/uni-app": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-components": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-h5": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-4010520240507001",
|
||||
"clipboard": "^2.0.11",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"uview-plus": "^3.3.54",
|
||||
"vue": "^3.4.21",
|
||||
"vue-i18n": "^9.1.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.8",
|
||||
"@dcloudio/uni-automator": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-4010520240507001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4010520240507001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4010520240507001",
|
||||
"@vue/runtime-core": "^3.4.21",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"sass": "1.63.2",
|
||||
"sass-loader": "10.4.1",
|
||||
"typescript": "^4.9.4",
|
||||
"vite": "5.2.8",
|
||||
"vue-tsc": "^1.0.24"
|
||||
}
|
||||
}
|
||||
10
shims-uni.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/// <reference types='@dcloudio/types' />
|
||||
import 'vue'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
|
||||
interface ComponentCustomOptions extends Hooks {
|
||||
|
||||
}
|
||||
}
|
||||
29
src/App.vue
Normal file
@@ -0,0 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { onLaunch, onShow, onHide } from "@dcloudio/uni-app";
|
||||
// import { NvpMerchService } from '@/Service/Nvp/NvpMerchService'
|
||||
onLaunch(() => {
|
||||
console.log("App Launch");
|
||||
});
|
||||
onShow(() => {
|
||||
console.log("App Show");
|
||||
});
|
||||
onHide(() => {
|
||||
console.log("App Hide");
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
@import "uview-plus/index.scss";
|
||||
@import "colorui/main.css";
|
||||
@import "colorui/icon.css";
|
||||
page {
|
||||
--nav-mian: #FF6A00; //全局颜色
|
||||
--nav-vice: #FF7F00; //副颜色
|
||||
--nav-diluted: #F2C0A3; //淡颜色
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
13
src/Service/BaseConfig.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export class BaseConfig {
|
||||
|
||||
protected static servesUrl: string = "http://192.168.0.142:5299";//线下
|
||||
protected static imgUrl: string = "http://192.168.0.142:5299/";
|
||||
protected static mediaUrl: string = "http://192.168.0.142:5299/";
|
||||
protected static payuploadUrl: string = "http://192.168.0.142:5299";
|
||||
|
||||
// protected static servesUrl: string = "http://pt.api.pccsh.cn";
|
||||
// protected static imgUrl: string = "http://pt.api.pccsh.cn";
|
||||
// protected static mediaUrl: string = "http://pt.api.pccsh.cn";
|
||||
// protected static uploadUrl: string = "/TencentCos/GetUpLoadInfo";
|
||||
// protected static payuploadUrl: string = "http://pt.api.pccsh.cn";
|
||||
}
|
||||
344
src/Service/Service.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
import { HttpRequest, StoreAssist, UploadAssist, ResultData } from '@/common/Common';
|
||||
|
||||
import { BaseConfig } from './BaseConfig';
|
||||
export class Service extends BaseConfig {
|
||||
//获取API地址
|
||||
static ApiUrl(path : string) {
|
||||
return `${this.servesUrl}${path}`;
|
||||
}
|
||||
|
||||
//获取图片地址
|
||||
static GetpayImg(path : string) {
|
||||
return path
|
||||
|
||||
if (path.startsWith('http') || path.startsWith('https')) {
|
||||
return path;
|
||||
} else {
|
||||
return `${this.payuploadUrl}${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
//获取icon图片地址
|
||||
static GetIconImg(path : string) {
|
||||
return path
|
||||
|
||||
if (path.startsWith('http') || path.startsWith('https')) {
|
||||
return path;
|
||||
} else {
|
||||
return `${this.payuploadUrl}${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
//获取图片地址
|
||||
static GetMateUrlByImg(path : string) {
|
||||
if (path == null || path == '') {
|
||||
return '/static/none.png';
|
||||
}
|
||||
|
||||
if (path.startsWith('http') || path.startsWith('https')) {
|
||||
return path;
|
||||
} else {
|
||||
return `${this.imgUrl}${path}`;
|
||||
}
|
||||
}
|
||||
//获取音视频地址
|
||||
static GetMateUrlByMedia(path : string) {
|
||||
if (path.startsWith('http') || path.startsWith('https')) {
|
||||
return path;
|
||||
} else {
|
||||
return `${this.mediaUrl}${path}`;
|
||||
}
|
||||
}
|
||||
//获取登录账号token
|
||||
static GetUserToken() {
|
||||
return Service.GetStorageCache('token');
|
||||
}
|
||||
// 获取登录状态
|
||||
static GetUserIsLogin() {
|
||||
var token = this.GetUserToken();
|
||||
if (token == null || token == '') {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//设置登录账户Token
|
||||
static SetUserToken(token : string) {
|
||||
this.SetStorageCache('token', token);
|
||||
}
|
||||
//清理登录账户Token
|
||||
static OffUserToken() {
|
||||
Service.DelStorageCache('token');
|
||||
uni.$emit('ImComOff', 'user');
|
||||
this.ClearUserStateData();
|
||||
}
|
||||
//获取登录账号状态信息
|
||||
static GetUserStateData() {
|
||||
return Service.GetStorageCache('StateDomain');
|
||||
}
|
||||
//设置当前登录账号状态信息
|
||||
static SetUserStateData() {
|
||||
return Service.GetStorageCache('StateDomain');
|
||||
}
|
||||
//清理当前登录账号状态信息
|
||||
static ClearUserStateData() {
|
||||
Service.DelStorageCache('StateDomain');
|
||||
}
|
||||
|
||||
//获取缓存
|
||||
static GetStorageCache(key : string) {
|
||||
return StoreAssist.Get(key);
|
||||
}
|
||||
//删除缓存
|
||||
static DelStorageCache(key : string) {
|
||||
StoreAssist.Delete(key);
|
||||
}
|
||||
//设置缓存
|
||||
static SetStorageCache(key : string, data : any) {
|
||||
StoreAssist.Set(key, data);
|
||||
}
|
||||
|
||||
/*****以下是基础方法调用与拦截器*****/
|
||||
|
||||
static Request(url : string, method : 'GET' | 'POST' | 'PUT' | undefined, data : object | any) {
|
||||
const token = Service.GetUserToken();
|
||||
|
||||
const _url = Service.ApiUrl(url);
|
||||
var result = HttpRequest.RequestWithToken(_url, method, token, data).then((retResult : any) => {
|
||||
if (retResult.statusCode == '200') {
|
||||
var obj = retResult.data;
|
||||
if (obj.code == 401) {
|
||||
//过期
|
||||
this.OffUserToken();
|
||||
|
||||
this.goReLaunch('/pages/login/login')
|
||||
return Promise.reject();
|
||||
} else if (obj.code == 40101) {
|
||||
//失效
|
||||
this.OffUserToken();
|
||||
this.goReLaunch('/pages/login/login');
|
||||
return Promise.reject();
|
||||
} else if (obj.code == 1004) {
|
||||
//资源不存在
|
||||
this.GoPageDelse('/pages/AppSet/404/404');
|
||||
return Promise.reject();
|
||||
// return new ResultData(-1, '', '');
|
||||
} else if (obj.code == 40188) {
|
||||
//无权限
|
||||
|
||||
this.GoPageDelse('/pages/AppSet/40188/40188');
|
||||
return Promise.reject();
|
||||
// return new ResultData(-1, '', '');
|
||||
} else if (obj.code == 1008) {
|
||||
//业务提示
|
||||
return new ResultData(obj.code, obj.msg, obj.data);
|
||||
} else {
|
||||
return new ResultData(obj.code, obj.msg, obj.data);
|
||||
}
|
||||
} else {
|
||||
return new ResultData(-1, '', '');
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
/*****以下是腾讯云oss上传*****/
|
||||
static UpLoadMedia(code : string, fileName : string, desire : string, path : string) {
|
||||
var result = this.Request(this.uploadUrl, 'GET', { code, fileName, desire }).then((retResult) => {
|
||||
if (retResult.code == 0) {
|
||||
var upOk = UploadAssist.Upload(retResult.data.url, path, retResult.data.cosData).then((upRet : any) => {
|
||||
if (upRet.statusCode == 200) {
|
||||
const retData : any = { code: retResult.data.code, file: retResult.data.file, cache: retResult.data.cache };
|
||||
return new ResultData(0, '上传成功!', retData);
|
||||
} else {
|
||||
this.Msg('上传失败!');
|
||||
return new ResultData(-1, '', '');
|
||||
}
|
||||
});
|
||||
return upOk;
|
||||
} else {
|
||||
this.Msg('上传失败!');
|
||||
return new ResultData(-1, retResult.msg, retResult.data);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/***********消息操作**************/
|
||||
static Msg(message : any, icon ?: any) : void {
|
||||
if (icon != null) {
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: icon
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static Alert(msg : string, cb ?: any) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: msg,
|
||||
showCancel: false,
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: res => {
|
||||
if (res.confirm) {
|
||||
cb && cb();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
static LoadIng(text : any) : void {
|
||||
uni.showLoading({
|
||||
title: text,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
static LoadClose() : void {
|
||||
uni.hideLoading();
|
||||
}
|
||||
|
||||
/**********跳转操作*********/
|
||||
|
||||
// 清空页面栈
|
||||
static goReLaunch(path : string) : void {
|
||||
uni.reLaunch({
|
||||
url: path
|
||||
})
|
||||
}
|
||||
|
||||
static GoPageTab(path : string) : void {
|
||||
uni.switchTab({
|
||||
url: path
|
||||
});
|
||||
}
|
||||
|
||||
/**********跳转操作*********/
|
||||
static GoPage(path : string) : void {
|
||||
uni.navigateTo({
|
||||
url: path, //跳转的页面
|
||||
success: function (res) {
|
||||
// 通过eventChannel向被打开页面传送数据
|
||||
}
|
||||
});
|
||||
}
|
||||
/**********跳转并删除当前页面操作*********/
|
||||
static GoPageDelse(path : string) : void {
|
||||
uni.redirectTo({
|
||||
url: path //跳转的页面
|
||||
});
|
||||
}
|
||||
|
||||
/**********返回上一页*********/
|
||||
static GoPageBack() : void {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
}
|
||||
|
||||
/*****获取图片base64*****/
|
||||
static UpLoadMediaBase64(path : string) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
uni.uploadFile({
|
||||
url: 'http://cloud.pccsh.com/DefUp/UploadFileImgBase64', //仅为示例,非真实的接口地址
|
||||
filePath: path,
|
||||
name: 'file',
|
||||
success: (uploadFileRes) => {
|
||||
resolve(uploadFileRes);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/*****获取图片位置信息*****/
|
||||
//获取时间戳
|
||||
static GetTimeSpan(milliSecond : number) {
|
||||
return Date.now() + milliSecond;
|
||||
}
|
||||
|
||||
// 时间戳处理
|
||||
static formatDate(time : any, type : number) : string {
|
||||
const date = new Date(time);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以加1,并用0填充
|
||||
const day = String(date.getDate()).padStart(2, '0'); // 用0填充
|
||||
const hours = String(date.getHours()).padStart(2, '0'); // 用0填充
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0'); // 用0填充
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0'); // 用0填充
|
||||
if (type == 0) {
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
else if (type == 1) {
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
} else if (type == 2) {
|
||||
return `${year}-${month}-${day}`;
|
||||
} else if (type == 3) {
|
||||
return `${hours}:${minutes}`;
|
||||
} else if (type == 4) {
|
||||
return `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
else {
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
}
|
||||
|
||||
/*****节流*****/
|
||||
static throttle(fn : () => void, time : number) {
|
||||
let canRun : boolean = true;
|
||||
return function () {
|
||||
if (!canRun) return;
|
||||
canRun = false;
|
||||
setTimeout(() => {
|
||||
fn(); //可以不执行
|
||||
canRun = true;
|
||||
}, time);
|
||||
};
|
||||
}
|
||||
/*****防抖*****/
|
||||
static debounce<T extends (...args : any[]) => void>(fn : T, time : number) : (...args : Parameters<T>) => void {
|
||||
let timerId : NodeJS.Timeout | null = null;
|
||||
|
||||
return (...args : Parameters<T>) => {
|
||||
if (timerId) {
|
||||
clearTimeout(timerId);
|
||||
}
|
||||
|
||||
timerId = setTimeout(() => {
|
||||
fn(...args); // 执行传入的函数
|
||||
timerId = null; // 清除定时器ID
|
||||
}, time);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 普通图片上传
|
||||
static uploadH5(path, dic, callback) {
|
||||
uni.uploadFile({
|
||||
url: this.payuploadUrl + '/api/Upload/UploadFile',
|
||||
method: "POST",
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + Service.GetUserToken(),
|
||||
},
|
||||
formData: {
|
||||
"path": dic,
|
||||
},
|
||||
filePath: path,
|
||||
name: 'file',
|
||||
success: (data) => {
|
||||
console.log(data);
|
||||
let info = data.data
|
||||
callback(info)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
42
src/Service/api/AddressService.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Service } from '@/Service/Service';
|
||||
export class AddressService {
|
||||
/**
|
||||
* 获取用户收货地址列表
|
||||
* GET /api/Address/GetUserAddressList
|
||||
*/
|
||||
static async GetUserAddressList(page : number) {
|
||||
return await Service.Request("/api/Address/GetUserAddressList", "GET", { page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户全部收货地址列表
|
||||
* GET /api/Address/GetUserAllAddressList
|
||||
*/
|
||||
static async GetUserAllAddressList() {
|
||||
return await Service.Request("/api/Address/GetUserAllAddressList", "GET", {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收货地址详情
|
||||
* GET /api/Address/GetUserAddressInfo
|
||||
*/
|
||||
static async GetUserAddressInfo(addressId : string) {
|
||||
return await Service.Request("/api/Address/GetUserAddressInfo", "GET", { addressId });
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加收货地址
|
||||
* POST /api/Address/AddUserAddress
|
||||
*/
|
||||
static async AddUserAddress(data : Record<string, any>) {
|
||||
return await Service.Request("/api/Address/AddUserAddress", "POST", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除地址
|
||||
* POST /api/Address/DelUserAddress
|
||||
*/
|
||||
static async DelUserAddress(data : Record<string, any>) {
|
||||
return await Service.Request("/api/Address/DelUserAddress", "POST", data);
|
||||
}
|
||||
}
|
||||
18
src/Service/api/AnnouncementService.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Service } from '@/Service/Service';
|
||||
export class AnnouncementService {
|
||||
/**
|
||||
* 获取公告列表
|
||||
* GET /api/Announcement/GetAnnouncementList
|
||||
*/
|
||||
static async GetAnnouncementList(page: number) {
|
||||
return await Service.Request("/api/Announcement/GetAnnouncementList",'GET', { page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公告详情
|
||||
* GET /api/Announcement/GetAnnouncementInfo
|
||||
*/
|
||||
static async GetAnnouncementInfo(id: string) {
|
||||
return await Service.Request( "/api/Announcement/GetAnnouncementInfo",'GET', { id });
|
||||
}
|
||||
}
|
||||
35
src/Service/api/CarService.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Service } from '@/Service/Service';
|
||||
export class CarService {
|
||||
/**
|
||||
* 购物车列表
|
||||
* GET /api/Car/GetUserCarList
|
||||
*/
|
||||
static async GetUserCarList(page: number) {
|
||||
return await Service.Request( "/api/Car/GetUserCarList", 'GET',{ page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加购物车
|
||||
* POST /api/Car/AddUserCar
|
||||
*/
|
||||
static async AddUserCar(goodsId:string,rule:string,count:number) {
|
||||
return await Service.Request( "/api/Car/AddUserCar",'POST', {goodsId,rule,count });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除购物车记录
|
||||
* POST /api/Car/DelUserCar
|
||||
*/
|
||||
static async DelUserCar(par:string,type:number) {
|
||||
return await Service.Request( "/api/Car/DelUserCar",'POST', {par,type});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改购物车数量
|
||||
* POST /api/Car/UpdateCarCount
|
||||
*/
|
||||
static async UpdateCarCount(cgId:string,count:number) {
|
||||
return await Service.Request( "/api/Car/UpdateCarCount",'POST', {cgId, count});
|
||||
}
|
||||
}
|
||||
19
src/Service/api/CollectService.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
import { Service } from '@/Service/Service';
|
||||
export class CollectService {
|
||||
/**
|
||||
* 获取用户收藏列表
|
||||
* GET /api/Collect/GetUserCollectList
|
||||
*/
|
||||
static async GetUserCollectList(page: number) {
|
||||
return await Service.Request("/api/Collect/GetUserCollectList", 'GET',{ page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户收藏列表
|
||||
* POST /api/Collect/AddUserCollect
|
||||
*/
|
||||
static async AddUserCollect(data: Record<string, any>) {
|
||||
return await Service.Request( "/api/Collect/AddUserCollect",'POST', data);
|
||||
}
|
||||
}
|
||||
54
src/Service/api/GoodsService.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Service } from '@/Service/Service';
|
||||
export class GoodsService {
|
||||
/**
|
||||
* 首页接口
|
||||
* GET /api/Goods/GetGoodsList
|
||||
*/
|
||||
static async GetGoodsList(page: number) {
|
||||
return await Service.Request( "/api/Goods/GetGoodsList","GET", { page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索商品接口
|
||||
* GET /api/Goods/GetSerchGoodsList
|
||||
*/
|
||||
static async GetSerchGoodsList(name: string, page: number) {
|
||||
return await Service.Request("/api/Goods/GetSerchGoodsList","GET", { name, page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品详情接口
|
||||
* GET /api/Goods/GetGoodsInfo
|
||||
*/
|
||||
static async GetGoodsInfo(goodsId: string) {
|
||||
return await Service.Request( "/api/Goods/GetGoodsInfo","GET", { goodsId });
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类接口
|
||||
* GET /api/Goods/GetSystemGoodsType
|
||||
* @param type 普通商品/自营商品
|
||||
*/
|
||||
static async GetSystemGoodsType(type: string, page: number) {
|
||||
return await Service.Request( "/api/Goods/GetSystemGoodsType","GET", { type, page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类查询商品接口
|
||||
* GET /api/Goods/GetTypeGoodsList
|
||||
*/
|
||||
static async GetTypeGoodsList(gtId: string, page: number) {
|
||||
return await Service.Request("/api/Goods/GetTypeGoodsList", "GET",{ gtId, page });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 拼团商品
|
||||
* GET /api/Goods/GetTuanGoodsList
|
||||
*/
|
||||
static async GetTuanGoodsList( page: number) {
|
||||
return await Service.Request("/api/Goods/GetTuanGoodsList", "GET",{ page });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
46
src/Service/api/LoginService.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Service } from '@/Service/Service';
|
||||
|
||||
|
||||
export class LoginService {
|
||||
/**
|
||||
* 登录接口
|
||||
* POST /api/Login/Login
|
||||
*/
|
||||
static async Login(phone :string,pwd:string ) {
|
||||
return await Service.Request( "/api/Login/Login",'POST', {phone,pwd});
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录接口
|
||||
* POST /api/Login/PhoneLogin
|
||||
*/
|
||||
static async PhoneLogin(phone :string,smsCode:string ) {
|
||||
return await Service.Request( "/api/Login/PhoneLogin",'POST', {phone,smsCode});
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册账号
|
||||
* POST /api/Login/Register
|
||||
*/
|
||||
static async Register(data : Record<string, any>) {
|
||||
return await Service.Request( "/api/Login/Register",'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 忘记密码
|
||||
* POST /api/Login/ForgetPwd
|
||||
*/
|
||||
static async ForgetPwd(data : Record<string, any>) {
|
||||
return await Service.Request( "/api/Login/ForgetPwd",'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* POST /api/Pub/SendSms
|
||||
*/
|
||||
static async SendSms(phone:string,smsCode:string) {
|
||||
return await Service.Request( "/api/Pub/SendSms",'POST', { phone,smsCode });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
75
src/Service/api/OrderService.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Service } from '@/Service/Service';
|
||||
export class OrderService {
|
||||
/**
|
||||
* 获取微信openId
|
||||
* GET /api/Order/GetOpenId
|
||||
*/
|
||||
static async GetOpenId(code : string) {
|
||||
return await Service.Request("/api/Order/GetOpenId", 'GET', { code });
|
||||
}
|
||||
|
||||
/**
|
||||
* 下单接口
|
||||
* POST /api/Order/AddUserOrder
|
||||
*/
|
||||
static async AddUserOrder(detail:string,isCustoms:number) {
|
||||
return await Service.Request("/api/Order/AddUserOrder", 'POST', {detail,isCustoms });
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改订单地址
|
||||
* POST /api/Order/UpdateOrderAddress
|
||||
*/
|
||||
static async UpdateOrderAddress(data : Record<string, any>) {
|
||||
return await Service.Request("/api/Order/UpdateOrderAddress", 'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
* POST /api/Order/CancelOrder
|
||||
*/
|
||||
static async CancelOrder(data : Record<string, any>) {
|
||||
return await Service.Request("/api/Order/CancelOrder", 'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单支付
|
||||
* POST /api/Order/PayOrder
|
||||
*/
|
||||
static async PayOrder(data : Record<string, any>) {
|
||||
return await Service.Request("/api/Order/PayOrder", 'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户订单接口
|
||||
* GET /api/Order/GetUserOrderList
|
||||
* @param state 空全部/0待付款 1已付款 2已发货 3待评价 4已完成
|
||||
*/
|
||||
static async GetUserOrderList(state : string, page : number) {
|
||||
return await Service.Request("/api/Order/GetUserOrderList", 'GET', { state, page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单详情接口
|
||||
* GET /api/Order/GetOrderInfo
|
||||
*/
|
||||
static async GetOrderInfo(orderId : string) {
|
||||
return await Service.Request("/api/Order/GetOrderInfo", 'GET', { orderId });
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认收货
|
||||
* POST /api/Order/UserOkOrder
|
||||
*/
|
||||
static async UserOkOrder(data : Record<string, any>) {
|
||||
return await Service.Request("/api/Order/UserOkOrder", 'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单评价
|
||||
* POST /api/Order/EvaluateOrder
|
||||
*/
|
||||
static async EvaluateOrder(data : Record<string, any>) {
|
||||
return await Service.Request("/api/Order/EvaluateOrder", 'POST', data);
|
||||
}
|
||||
}
|
||||
112
src/Service/api/UserService.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Service } from '@/Service/Service';
|
||||
export class UserService {
|
||||
/**
|
||||
* 获取个人信息
|
||||
* GET /api/User/GetUserInfo
|
||||
*/
|
||||
static async GetUserInfo() {
|
||||
return await Service.Request( "/api/User/GetUserInfo" ,'GET',{});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户信息
|
||||
* POST /api/User/UpdateUser
|
||||
*/
|
||||
static async UpdateUser(avatar:string,nick:string) {
|
||||
return await Service.Request( "/api/User/UpdateUser", 'POST', { avatar,nick });
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户手机号
|
||||
* POST /api/User/UpdateUserPhone
|
||||
*/
|
||||
static async UpdateUserPhone(phone:string,smsCode:string) {
|
||||
return await Service.Request( "/api/User/UpdateUserPhone", 'POST', {phone,smsCode });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户账户信息
|
||||
* GET /api/User/GetUserAccInfo
|
||||
*/
|
||||
static async GetUserAccInfo() {
|
||||
return await Service.Request( "/api/User/GetUserAccInfo",'GET',{} );
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户账户记录
|
||||
* GET /api/User/GetUserAccLog
|
||||
* @param accType 账户
|
||||
* @param op 0支出1收入
|
||||
*/
|
||||
static async GetUserAccLog(accType: string, op: string, page: number) {
|
||||
return await Service.Request("/api/User/GetUserAccLog",'GET', { accType, op, page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改支付密码
|
||||
* POST /api/User/ForgetPayPwd
|
||||
*/
|
||||
static async ForgetPayPwd(data: Record<string, any>) {
|
||||
return await Service.Request( "/api/User/ForgetPayPwd", 'POST',data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户评价接口
|
||||
* GET /api/User/GetUserEvaluateList
|
||||
*/
|
||||
static async GetUserEvaluateList(source: number, page: number) {
|
||||
return await Service.Request( "/api/User/GetUserEvaluateList",'GET', { source, page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除评论接口
|
||||
* POST /api/User/DelUserEvaluate
|
||||
*/
|
||||
static async DelUserEvaluate(data: Record<string, any>) {
|
||||
return await Service.Request( "/api/User/DelUserEvaluate",'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户提现
|
||||
* POST /api/User/UserWithAcc
|
||||
*/
|
||||
static async UserWithAcc(data: Record<string, any>) {
|
||||
return await Service.Request( "/api/User/UserWithAcc", 'POST', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户提现记录
|
||||
* GET /api/User/GetUserWithList
|
||||
* @param accType moneyAcc/shopAcc
|
||||
*/
|
||||
static async GetUserWithList(accType: string, page: number) {
|
||||
return await Service.Request( "/api/User/GetUserWithList",'GET', { accType, page });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推广列表
|
||||
* GET /api/User/GetUserRemList
|
||||
*/
|
||||
static async GetUserRemList(page: number) {
|
||||
return await Service.Request( "/api/User/GetUserRemList", 'GET', { page });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取推广列表
|
||||
* post /api/Upload/UploadFile
|
||||
*/
|
||||
static async UploadFile(file: string,path:string) {
|
||||
return await Service.Request( "/api/Upload/UploadFile", 'POST', { file,path });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推广列表
|
||||
* post /api/User/TranAccount
|
||||
*/
|
||||
static async TranAccount(amount: number,type:number) {
|
||||
return await Service.Request( "/api/User/TranAccount", 'POST', { amount,type });
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
184
src/colorui/animation.css
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
Animation 微动画
|
||||
基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
|
||||
*/
|
||||
|
||||
/* css 滤镜 控制黑白底色gif的 */
|
||||
.gif-black{
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
.gif-white{
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
|
||||
/* Animation css */
|
||||
[class*=animation-] {
|
||||
animation-duration: .5s;
|
||||
animation-timing-function: ease-out;
|
||||
animation-fill-mode: both
|
||||
}
|
||||
|
||||
.animation-fade {
|
||||
animation-name: fade;
|
||||
animation-duration: .8s;
|
||||
animation-timing-function: linear
|
||||
}
|
||||
|
||||
.animation-scale-up {
|
||||
animation-name: scale-up
|
||||
}
|
||||
|
||||
.animation-scale-down {
|
||||
animation-name: scale-down
|
||||
}
|
||||
|
||||
.animation-slide-top {
|
||||
animation-name: slide-top
|
||||
}
|
||||
|
||||
.animation-slide-bottom {
|
||||
animation-name: slide-bottom
|
||||
}
|
||||
|
||||
.animation-slide-left {
|
||||
animation-name: slide-left
|
||||
}
|
||||
|
||||
.animation-slide-right {
|
||||
animation-name: slide-right
|
||||
}
|
||||
|
||||
.animation-shake {
|
||||
animation-name: shake
|
||||
}
|
||||
|
||||
.animation-reverse {
|
||||
animation-direction: reverse
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0% {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale-up {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(.2)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale-down {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(1.8)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-top {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-bottom {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0)
|
||||
}
|
||||
|
||||
10% {
|
||||
transform: translateX(-9px)
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: translateX(8px)
|
||||
}
|
||||
|
||||
30% {
|
||||
transform: translateX(-7px)
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translateX(6px)
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateX(-5px)
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(4px)
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(-3px)
|
||||
}
|
||||
|
||||
80% {
|
||||
transform: translateX(2px)
|
||||
}
|
||||
|
||||
90% {
|
||||
transform: translateX(-1px)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-left {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(-100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-right {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateX(100%)
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateX(0)
|
||||
}
|
||||
}
|
||||
65
src/colorui/components/cu-custom.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
|
||||
<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
|
||||
<view class="action" @tap="BackPage" v-if="isBack">
|
||||
<text class="cuIcon-back"></text>
|
||||
<slot name="backText"></slot>
|
||||
</view>
|
||||
<view class="content" :style="[{top:StatusBar + 'px'}]">
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
StatusBar: this.StatusBar,
|
||||
CustomBar: this.CustomBar
|
||||
};
|
||||
},
|
||||
name: 'cu-custom',
|
||||
computed: {
|
||||
style() {
|
||||
var StatusBar= this.StatusBar;
|
||||
var CustomBar= this.CustomBar;
|
||||
var bgImage = this.bgImage;
|
||||
var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
|
||||
if (this.bgImage) {
|
||||
style = `${style}background-image:url(${bgImage});`;
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
props: {
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isBack: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
bgImage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
BackPage() {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
1226
src/colorui/icon.css
Normal file
3912
src/colorui/main.css
Normal file
13
src/common/Common.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import {ResultData} from "./Domain/ResultData";
|
||||
import {StoreAssist} from "./Unit/StorageAssist";
|
||||
import {HttpRequest} from "./Unit/HttpRequest";
|
||||
import { StringAssist } from "./Unit/StringAssist";
|
||||
import { UploadAssist } from "./Unit/UploadAssist";
|
||||
|
||||
export {
|
||||
ResultData,
|
||||
HttpRequest,
|
||||
StoreAssist,
|
||||
StringAssist,
|
||||
UploadAssist
|
||||
}
|
||||
10
src/common/Domain/ResultData.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export class ResultData {
|
||||
public code: number=-1;
|
||||
public msg: string="";
|
||||
public data: any;
|
||||
constructor(code:number,msg:string,data:any) {
|
||||
this.code=code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
64
src/common/Unit/HttpRequest.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
export class HttpRequest {
|
||||
|
||||
/***普通请求方法***/
|
||||
static Request(url: string, method: "GET" | "POST" | "PUT" | undefined, data: object | any) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let header = {
|
||||
'content-type': method == 'POST' || method == 'PUT' ? 'application/x-www-form-urlencoded' : 'application/json; charset=utf-8',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
};
|
||||
uni.request({
|
||||
url: url,
|
||||
method: method,
|
||||
data: data,
|
||||
header: header,
|
||||
success(res: any) {
|
||||
if (res.statusCode == "200") {
|
||||
resolve(res.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
resolve(res);
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
//请求失败
|
||||
uni.showToast({
|
||||
title: '无法连接到服务器',
|
||||
icon: 'none'
|
||||
})
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
/***带Token的请求方法***/
|
||||
static RequestWithToken(url: string, method: "GET" | "POST" | "PUT" | undefined, token: string, data: object | any) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let header = {
|
||||
'content-type': method == 'POST' || method == 'PUT' ? 'application/x-www-form-urlencoded' : 'application/json; charset=utf-8',
|
||||
'Authorization': 'Bearer ' + token,//token获取
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
};
|
||||
uni.request({
|
||||
url: url,
|
||||
method: method,
|
||||
data: data,
|
||||
header: header,
|
||||
success(res: any) {
|
||||
resolve(res);
|
||||
},
|
||||
fail(err) {
|
||||
//请求失败
|
||||
uni.showToast({
|
||||
title: '无法连接到服务器',
|
||||
icon: 'none'
|
||||
})
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
14
src/common/Unit/StorageAssist.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export class StoreAssist{
|
||||
static Get(key:string):any
|
||||
{
|
||||
return uni.getStorageSync(key);
|
||||
}
|
||||
static Set(key:string,value:any):void
|
||||
{
|
||||
uni.setStorageSync(key, value);
|
||||
}
|
||||
static Delete(key:string):void
|
||||
{
|
||||
uni.removeStorageSync(key);
|
||||
}
|
||||
}
|
||||
13
src/common/Unit/StoreAssist.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
// import { createPinia, defineStore } from "pinia";
|
||||
// import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
// export class StoreAssist{
|
||||
// private pinia:any=createPinia();
|
||||
// constructor() {
|
||||
// this.pinia.use(piniaPluginPersistedstate);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// }
|
||||
|
||||
|
||||
98
src/common/Unit/StringAssist.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
export class StringAssist {
|
||||
static NoHtml(html: string): string {
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
static PhoneToStr (e:string) {
|
||||
return e.substring(0,3)+'****'+e.substring((e.length-2),(e.length))
|
||||
}
|
||||
// 数量过万处理
|
||||
static NumToStr (sum:number) {
|
||||
if(sum>=10000){
|
||||
return (sum/10000).toFixed('2')+'w'
|
||||
}else{
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 帖子距离现在多久
|
||||
static DiffTimeTostring(dateTime: string): string {
|
||||
let result = 0;
|
||||
let time = Date.parse(dateTime);
|
||||
let timestamp = Date.parse(new Date().toString());
|
||||
if ((timestamp - time) / 1000 < 60) {
|
||||
result = (timestamp - time) / 1000;
|
||||
result = result < 0 ? 0 : result;
|
||||
return result.toFixed(0) + '秒前';
|
||||
} else if ((timestamp - time) / 1000 / 60 < 60) {
|
||||
return ((timestamp - time) / 1000 / 60).toFixed(0) + '分钟前';
|
||||
} else if ((timestamp - time) / 1000 / 60 / 60 < 24) {
|
||||
return ((timestamp - time) / 1000 / 60 / 60).toFixed(0) + '小时前';
|
||||
} else if ((timestamp - time) / 1000 / 60 / 60 / 24 < 31) {
|
||||
return ((timestamp - time) / 1000 / 60 / 60 / 24).toFixed(0) + '天前';
|
||||
} else {
|
||||
return this.formatDate(time, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static formatDate(time:any,type:number):string
|
||||
{
|
||||
const date = new Date(time);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以加1,并用0填充
|
||||
const day = String(date.getDate()).padStart(2, '0'); // 用0填充
|
||||
const hours = String(date.getHours()).padStart(2, '0'); // 用0填充
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0'); // 用0填充
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0'); // 用0填充
|
||||
if(type==0)
|
||||
{
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
else if(type==1)
|
||||
{
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}else if(type==2)
|
||||
{
|
||||
return `${year}-${month}-${day}`;
|
||||
}else if(type==3){
|
||||
return `${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
else{
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 聊天时间显示
|
||||
static ChatTimeTostring(dateTime: string,upTime:string): string {
|
||||
let time = Date.parse(dateTime);
|
||||
let timestamp = Date.parse(upTime);
|
||||
if (( time - timestamp) / 1000 / 60 < 10) {
|
||||
return '0';
|
||||
} else{
|
||||
return this.formatDate(time, 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 去除两侧的空格
|
||||
|
||||
static trim(str:string){
|
||||
const reg = /^\s+|\s+$/g;
|
||||
return str.replace(reg,'');
|
||||
}
|
||||
|
||||
// 计算两个时分秒差值
|
||||
static timesfm(dateTime: string,upTime:string){
|
||||
|
||||
let time = Date.parse('2000-01-01 '+dateTime);
|
||||
let timestamp = Date.parse('2000-01-01 '+upTime);
|
||||
return ((timestamp - time) / 1000 / 60).toFixed(0) + '分钟';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
19
src/common/Unit/UploadAssist.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export class UploadAssist {
|
||||
static Upload(url: string, path: string, fromData: any) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
uni.uploadFile({
|
||||
url: url, //仅为示例,非真实的接口地址
|
||||
filePath: path,
|
||||
name: 'file',
|
||||
formData: fromData,
|
||||
success: (uploadFileRes) => {
|
||||
resolve(uploadFileRes);
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
41
src/components/ImageCropper.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="" v-if="props.show">
|
||||
<qf-image-cropper :src="props.url" :width="props.width" :height="props.height" :radius="0"
|
||||
@crop="handleCrop"></qf-image-cropper>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, reactive } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
// const props = defineProps({
|
||||
// show : string,
|
||||
// // url : string,
|
||||
// // width : number,
|
||||
// // height:number,
|
||||
// // upType:string,
|
||||
// // retFun:Function
|
||||
// });
|
||||
|
||||
|
||||
let props = defineProps(["show", "url", 'width', 'height', 'upType', "retFun", "imgName"]);
|
||||
|
||||
onMounted(() => {
|
||||
console.log(props);
|
||||
})
|
||||
|
||||
|
||||
const handleCrop = (e) => {
|
||||
Service.uploadH5(e.tempFilePath, 'Avatar', rest => {
|
||||
props.retFun({ url: JSON.parse(rest).data.path })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
338
src/components/jin-edit/jin-edit.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<view class="container" :style="{height: height}">
|
||||
<editor
|
||||
class="ql-container"
|
||||
:placeholder="placeholder"
|
||||
:show-img-size="true"
|
||||
:show-img-toolbar="true"
|
||||
:show-img-resize="true"
|
||||
@ready="onEditorReady"
|
||||
id="editor"
|
||||
@statuschange="statuschange"
|
||||
@focus="editFocus"
|
||||
@blur="editBlur"
|
||||
ref="editot"
|
||||
></editor>
|
||||
<!-- 操作工具 -->
|
||||
<view class="tool-view" >
|
||||
<!-- 文字相关操作 -->
|
||||
<view class="font-more" :style="{ height: showMoreTool ? '100rpx' : 0 }">
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="加粗" @click="setBold" :color="showBold ? activeColor : '#666666'"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="斜体" @click="setItalic" :color="showItalic ? activeColor : '#666666'"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="分割线" @click="setIns" :color="showIns ? activeColor : '#666666'"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="标题" @click="setHeader" :color="showHeader ? activeColor : '#666666'"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="居中" @click="setCenter" :color="showCenter ? activeColor : '#666666'"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="居右" @click="setRight" :color="showRight ? activeColor : '#666666'"></jinIcon>
|
||||
</view>
|
||||
<view class="setting-layer-mask" v-if="showSettingLayer" @click="showSetting"></view>
|
||||
<view class="setting-layer" v-if="showSettingLayer">
|
||||
<!-- <view class="single" @click="release(true)">
|
||||
<jinIcon class="icon" type="" ></jinIcon>
|
||||
<view>公开发布</view>
|
||||
</view>
|
||||
<view class="single" @click="release(false)">
|
||||
<jinIcon class="icon" type="" ></jinIcon>
|
||||
<view>暂时保存</view>
|
||||
</view> -->
|
||||
</view>
|
||||
<view class="tool">
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="插入图片" @click="insertImage"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="修改文字样式" @click="showMore" :color="showMoreTool ? activeColor : '#666666'"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="分割线" @click="insertDivider"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="撤销" @click="undo"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="重做" @click="redo"></jinIcon>
|
||||
<jinIcon class="single" type="" font-size="44rpx" title="设置" @click="showSetting"></jinIcon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let _this
|
||||
import videoUrl from '@/common/config.js'
|
||||
import jinIcon from './jin-icons.vue';
|
||||
export default {
|
||||
props: {
|
||||
// 点击图片时显示图片大小控件
|
||||
showImgSize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击图片时显示工具栏控件
|
||||
showImgToolbar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击图片时显示修改尺寸控件
|
||||
showImgResize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 占位符
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '开始输入...'
|
||||
},
|
||||
// 图片上传的地址
|
||||
uploadFileUrl: {
|
||||
type: String,
|
||||
default: '#'
|
||||
},
|
||||
// 上传文件时的name
|
||||
fileKeyName: {
|
||||
type: String,
|
||||
default: 'file'
|
||||
},
|
||||
// 上传图片时,http请求的header
|
||||
header: {
|
||||
type: Object
|
||||
},
|
||||
// 初始化html
|
||||
html: {
|
||||
type: String
|
||||
},
|
||||
// 整个控件的高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '100vh'
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMoreTool: false,
|
||||
showBold: false,
|
||||
showItalic: false,
|
||||
showIns: false,
|
||||
showHeader: false,
|
||||
showCenter: false,
|
||||
showRight: false,
|
||||
showSettingLayer: false,
|
||||
activeColor: '#F56C6C'
|
||||
};
|
||||
},
|
||||
components: {
|
||||
jinIcon
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
onEditorReady(e) {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.ql-container')
|
||||
.fields({
|
||||
size: true,
|
||||
context: true
|
||||
},res => {
|
||||
this.editorCtx = res.context;
|
||||
this.editorCtx.setContents({
|
||||
html: this.html
|
||||
})
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
undo() {
|
||||
this.editorCtx.undo();
|
||||
},
|
||||
// 插入图片
|
||||
insertImage() {
|
||||
_this =this
|
||||
uni.chooseImage({
|
||||
count: 9, //默认9
|
||||
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
|
||||
sourceType: ['album', 'camera'], //从相册选择
|
||||
success: async(res) => {
|
||||
var tempFilePaths = res.tempFilePaths;
|
||||
uni.showLoading({
|
||||
title: '正在上传中...'
|
||||
})
|
||||
for (let temp of tempFilePaths) {
|
||||
// 图片上传服务器
|
||||
await uni.uploadFile({
|
||||
url: this.uploadFileUrl,
|
||||
filePath: temp,
|
||||
name: 'file',
|
||||
header: this.header,
|
||||
formData: {
|
||||
"path": this.fileKeyName,
|
||||
},
|
||||
success: res => {
|
||||
// 上传完成后处理
|
||||
this.editorCtx.insertImage({
|
||||
src: _this.videoUrl+res.data, // 此处需要将图片地址切换成服务器返回的真实图片地址
|
||||
alt: '图片',
|
||||
success: function(e) {}
|
||||
});
|
||||
uni.hideLoading()
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/// 插入分割线
|
||||
insertDivider() {
|
||||
this.editorCtx.insertDivider();
|
||||
},
|
||||
redo() {
|
||||
this.editorCtx.redo();
|
||||
},
|
||||
showMore() {
|
||||
this.showMoreTool = !this.showMoreTool;
|
||||
this.editorCtx.setContents()
|
||||
},
|
||||
setBold() {
|
||||
this.showBold = !this.showBold;
|
||||
this.editorCtx.format('bold');
|
||||
},
|
||||
setItalic() {
|
||||
this.showItalic = !this.showItalic;
|
||||
this.editorCtx.format('italic');
|
||||
},
|
||||
checkStatus(name, detail, obj) {
|
||||
if (detail.hasOwnProperty(name)) {
|
||||
this[obj] = true;
|
||||
} else {
|
||||
this[obj] = false;
|
||||
}
|
||||
},
|
||||
statuschange(e) {
|
||||
var detail = e.detail;
|
||||
this.checkStatus('bold', detail, 'showBold');
|
||||
this.checkStatus('italic', detail, 'showItalic');
|
||||
this.checkStatus('ins', detail, 'showIns');
|
||||
this.checkStatus('header', detail, 'showHeader');
|
||||
if (detail.hasOwnProperty('align')) {
|
||||
if (detail.align == 'center') {
|
||||
this.showCenter = true;
|
||||
this.showRight = false;
|
||||
} else if (detail.align == 'right') {
|
||||
this.showCenter = false;
|
||||
this.showRight = true;
|
||||
} else {
|
||||
this.showCenter = false;
|
||||
this.showRight = false;
|
||||
}
|
||||
} else {
|
||||
this.showCenter = false;
|
||||
this.showRight = false;
|
||||
}
|
||||
},
|
||||
setIns() {
|
||||
this.showIns = !this.showIns;
|
||||
this.editorCtx.format('ins');
|
||||
},
|
||||
setHeader() {
|
||||
this.showHeader = !this.showHeader;
|
||||
this.editorCtx.format('header', this.showHeader ? 'H2' : false);
|
||||
},
|
||||
setCenter() {
|
||||
this.showCenter = !this.showCenter;
|
||||
this.editorCtx.format('align', this.showCenter ? 'center' : false);
|
||||
},
|
||||
setRight() {
|
||||
this.showRight = !this.showRight;
|
||||
this.editorCtx.format('align', this.showRight ? 'right' : false);
|
||||
},
|
||||
showSetting() {
|
||||
this.release(true)
|
||||
// this.showSettingLayer = !this.showSettingLayer;
|
||||
},
|
||||
async editFocus(e) {
|
||||
},
|
||||
editBlur(e) {
|
||||
},
|
||||
release(isPublic) {
|
||||
this.showSettingLayer = false;
|
||||
this.editorCtx.getContents({
|
||||
success: res => {
|
||||
Object.assign(res, {
|
||||
isPublic: isPublic
|
||||
})
|
||||
this.$emit('editOk', res);
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
/* padding-top: 30rpx; */
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
line-height: 150%;
|
||||
font-size: 34rpx;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
width: calc(100% - 60rpx);
|
||||
margin: 0 auto;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
margin-top: 30rpx;
|
||||
/* padding-bottom: 5rpx; */
|
||||
}
|
||||
.tool-view{
|
||||
width: 100vw;
|
||||
background: #eee;
|
||||
/* margin-top: 20px; */
|
||||
}
|
||||
.tool {
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.font-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 100%;
|
||||
background: rgb(235, 235, 235);
|
||||
overflow: hidden;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.setting-layer {
|
||||
position: absolute;
|
||||
bottom: 100rpx;
|
||||
background: #fff;
|
||||
width: 250rpx;
|
||||
right: 20rpx;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
.setting-layer .single {
|
||||
height: 80rpx;
|
||||
font-size: 32rpx;
|
||||
padding: 0 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 80rpx;
|
||||
flex-direction: row;
|
||||
color: #666;
|
||||
}
|
||||
.setting-layer .single .icon {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.setting-layer-mask{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
48
src/components/jin-edit/jin-icons.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="icon" :style="{color: color, fontSize: fontSize}" v-html="type" @click="toclick"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#666666'
|
||||
},
|
||||
fontSize: {
|
||||
type: String,
|
||||
default: '34rpx'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toclick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.content{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'jin';
|
||||
/** 阿里巴巴矢量图标库的字体库地址,可以替换自己的字体库地址 **/
|
||||
src: url('https://at.alicdn.com/t/font_1491431_6m7ltjo8wi.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.icon {
|
||||
font-family: jin !important;
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
64
src/components/jin-edit/readme.md
Normal file
@@ -0,0 +1,64 @@
|
||||
## jin-edit 基于editor的富文本编辑器
|
||||
|
||||
### 兼容性
|
||||
|
||||
这是一个uni-app的通用组件,兼容微信小程序端、安卓端、ios端(未测试)、H5端。作者因没有ios设备无法对ios端进行测试,其他端测试无问题。
|
||||
|
||||
微信小程序 | APP | H5
|
||||
:--: | :--: | :--:
|
||||
√ | √ | √
|
||||
|
||||
我的HbuilderX版本2.6.7,不同的版本可能会造成不兼容的问题。
|
||||
|
||||
### 使用方式
|
||||
|
||||
1. 将此组件进入你的项目中的 /components/ 目录中
|
||||
2. 在某个页面中使用该插件
|
||||
- 在 `script` 中引用组件
|
||||
```javascript
|
||||
import jinEdit from '../../components/jin-edit/jin-edit.vue';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
components: {
|
||||
jinEdit
|
||||
},
|
||||
methods: {
|
||||
// 点击发布
|
||||
editOk(res) {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- 在 `template` 中使用组件
|
||||
```html
|
||||
<jinEdit placeholder="请输入内容" @editOk="editOk" uploadFileUrl="/#"></jinEdit>
|
||||
```
|
||||
|
||||
### Demo
|
||||
|
||||
[uni-jin(一个uni-app组件集合)](https://github.com/wangjinxin613/uni-jin)
|
||||
|
||||
### 参数
|
||||
|
||||
属性 | 类型 | 默认值 | 说明
|
||||
:--: | :--: | :--: | :--:
|
||||
showImgSize | Boolean | false | 点击图片时显示图片大小控件
|
||||
showImgToolbar | Boolean | false | 点击图片时显示工具栏控件
|
||||
showImgResize | Boolean | false | 点击图片时显示修改尺寸控件
|
||||
placeholder | String | '' | 编辑器占位符
|
||||
uploadFileUrl | String | '#' | 图片上传的服务器地址
|
||||
fileKeyName | String | 'file' | 图片上传时的name
|
||||
header | Object | - | 图片上传http请求的header
|
||||
html | String | - | 初始化的html
|
||||
|
||||
### 方法
|
||||
|
||||
方法名 | 参数 | 说明
|
||||
:--: | :--: | :--:
|
||||
editOk | e={html,text,delta,isPublic} | 点击发布按钮触发
|
||||
|
||||
以上
|
||||
299
src/components/liy-select/liy-select.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<view class="liy-select-fixed" :class="openClass">
|
||||
<view class="liy-search-warp">
|
||||
<view class="liy-search-input">
|
||||
<view class="lsi-warp">
|
||||
<image src="../../static/liy-select/images/search.png" mode="widthFix"
|
||||
class="lsi-icon"></image>
|
||||
<input class="lsi-input" v-model="keyword" placeholder="请输入搜索内容" @input="getParamsList" />
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="scroll-Y" @scrolltolower="lower">
|
||||
<view class="liy-search-list" v-if="outPutList.length > 0">
|
||||
<view class="liy-search-li" v-for="(item,index) in outPutList" :key="index"
|
||||
@click="mapSelectMenu(item,index)">
|
||||
<view class="liy-search-left">
|
||||
<view class="liy-search-title" v-if="titleKey">{{item[titleKey]}}
|
||||
</view>
|
||||
<view class="liy-search-title" v-else>未选择标题
|
||||
</view>
|
||||
<view class="liy-search-desc" v-if="subtitleKey">{{item[subtitleKey]}}</view>
|
||||
</view>
|
||||
<view class="liy-search-icon" v-if="mapSelectIndex == index">
|
||||
<image class="lsi-icon" src="../../static/liy-select/images/check_mark.png"
|
||||
mode="widthFix"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="liy-loading">
|
||||
<image class="liy-loading-img" src="../../static/liy-select/images/complete.png"
|
||||
mode="widthFix"></image>
|
||||
<view class="liy-loading-text">加载完成</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="liy-void">
|
||||
<image class="liy-void-img" src="../../static/liy-select/images/void.png"
|
||||
mode="widthFix"></image>
|
||||
<view class="liy-void-text">未查询到内容</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="liy-overlay" @click="closeOverlay" v-if="open == true"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name:'liy-select',
|
||||
props:{
|
||||
list:{
|
||||
type:Array,
|
||||
default:[]
|
||||
},
|
||||
titleKey:{
|
||||
type:[String,null],
|
||||
default: null
|
||||
},
|
||||
subtitleKey:{
|
||||
type:[String,null],
|
||||
default:null
|
||||
},
|
||||
open:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mapSelectIndex: null,
|
||||
keyword: "",
|
||||
outPutList: [],
|
||||
openClass:""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list (newList, oldList) {
|
||||
this.getParamsList();
|
||||
},
|
||||
open(newOpen,oldOpen){
|
||||
if(newOpen == true){
|
||||
this.openClass = "show";
|
||||
}else{
|
||||
this.openClass = "hide";
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getParamsList();
|
||||
},
|
||||
methods: {
|
||||
mapSelectMenu(item,index) {
|
||||
this.mapSelectIndex = index;
|
||||
this.$emit("change",item,index)
|
||||
this.$emit("close");
|
||||
},
|
||||
getParamsList() {
|
||||
var selectList = this.list;
|
||||
this.mapSelectIndex = null;
|
||||
if (!this.keyword) {
|
||||
this.outPutList = selectList;
|
||||
|
||||
return false;
|
||||
}
|
||||
var arr = [];
|
||||
for (var i = 0; i < selectList.length; i++) {
|
||||
let item = selectList[i];
|
||||
if (item[this.titleKey].indexOf(this.keyword) > -1) {
|
||||
arr.push(item);
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
this.outPutList = arr;
|
||||
},
|
||||
lower() {
|
||||
|
||||
},
|
||||
closeOverlay() {
|
||||
this.$emit("close");
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.liy-overlay {
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: ease-out;
|
||||
position: fixed;
|
||||
inset: 0px;
|
||||
z-index: 100;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.liy-select-fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 150;
|
||||
transition: transform .5s ease-in-out;
|
||||
transform: translateY(calc(100% + 40rpx));
|
||||
&.show {
|
||||
animation: showHandler 0.3s forwards;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
animation: hideHandler 0.3s forwards;
|
||||
}
|
||||
|
||||
.liy-search-warp {
|
||||
background: #fff;
|
||||
border-radius: 10rpx 10rpx 0 0;
|
||||
|
||||
.liy-search-input {
|
||||
padding: 30rpx 40rpx;
|
||||
font-size: 28rpx;
|
||||
|
||||
.lsi-warp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 2rpx solid #e3e3e3;
|
||||
padding: 16rpx 20rpx;
|
||||
border-radius: 50rpx;
|
||||
|
||||
.lsi-icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
}
|
||||
|
||||
.lsi-input {
|
||||
width: 90%;
|
||||
padding-left: 16rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.liy-search-list {
|
||||
.liy-search-li {
|
||||
padding: 20rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1rpx solid #f7f7f7;
|
||||
min-height: 50rpx;
|
||||
|
||||
.liy-search-left {
|
||||
width: 90%;
|
||||
|
||||
.liy-search-title {
|
||||
font-size: 28rpx;
|
||||
line-height: 36rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.liy-search-desc {
|
||||
padding-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 36rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.liy-search-icon {
|
||||
.lsi-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.scroll-Y {
|
||||
height: 500rpx;
|
||||
}
|
||||
|
||||
.liy-loading {
|
||||
color: #333;
|
||||
font-size: 26rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20rpx 0;
|
||||
|
||||
.liy-loading-img {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
// animation: turn 3s linear infinite;
|
||||
}
|
||||
|
||||
.liy-loading-text {
|
||||
padding-left: 12rpx;
|
||||
padding-right: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes turn {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
25% {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
75% {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes showHandler {
|
||||
0% {
|
||||
transform: translateY(calc(100% + 40rpx));
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hideHandler {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
|
||||
100% {
|
||||
transform: translateY(calc(100% + 40rpx));
|
||||
}
|
||||
}
|
||||
|
||||
.liy-void {
|
||||
text-align: center;
|
||||
padding-top: 60rpx;
|
||||
|
||||
.liy-void-img {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
.liy-void-text {
|
||||
padding-top: 30rpx;
|
||||
font-size: 26rpx;
|
||||
color: #909090;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
8
src/env.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
13
src/main.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createSSRApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
import uviewPlus from 'uview-plus'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App);
|
||||
|
||||
app.use(uviewPlus)
|
||||
return {
|
||||
app,
|
||||
};
|
||||
}
|
||||
145
src/manifest.json
Normal file
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"name" : "",
|
||||
"appid" : "__UNI__06C2D6A",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.8",
|
||||
"versionCode" : 108,
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
"compatible" : {
|
||||
"ignoreVersion" : true //true表示忽略版本检查提示框,HBuilderX1.9.0及以上版本支持
|
||||
},
|
||||
"usingComponents" : true,
|
||||
"nvueStyleCompiler" : "uni-app",
|
||||
"compilerVersion" : 3,
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : false,
|
||||
"waiting" : false,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {
|
||||
"Barcode" : {},
|
||||
"Maps" : {},
|
||||
"Geolocation" : {},
|
||||
"Share" : {}
|
||||
},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
],
|
||||
"minSdkVersion" : 25,
|
||||
"targetSdkVersion" : 25,
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"ios" : {
|
||||
"idfa" : false,
|
||||
"dSYMs" : false
|
||||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {
|
||||
"ad" : {},
|
||||
"maps" : {
|
||||
"amap" : {
|
||||
"name" : "amapZAvZjTHj",
|
||||
"appkey_ios" : "3caf9e6f01b0085be1e75e0d0e281fe7",
|
||||
"appkey_android" : "3caf9e6f01b0085be1e75e0d0e281fe7"
|
||||
}
|
||||
},
|
||||
"geolocation" : {
|
||||
"amap" : {
|
||||
"name" : "amapZAvZjTHj",
|
||||
"__platform__" : [ "android" ],
|
||||
"appkey_ios" : "",
|
||||
"appkey_android" : "3caf9e6f01b0085be1e75e0d0e281fe7"
|
||||
}
|
||||
},
|
||||
"share" : {}
|
||||
},
|
||||
"icons" : {
|
||||
"android" : {
|
||||
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||
},
|
||||
"ios" : {
|
||||
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||
"ipad" : {
|
||||
"app" : "unpackage/res/icons/76x76.png",
|
||||
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||
"notification" : "unpackage/res/icons/20x20.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||
"settings" : "unpackage/res/icons/29x29.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||
},
|
||||
"iphone" : {
|
||||
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"uniStatistics" : {
|
||||
"enable" : false
|
||||
},
|
||||
"vueVersion" : "3",
|
||||
"h5" : {
|
||||
"sdkConfigs" : {
|
||||
"maps" : {
|
||||
"qqmap" : {
|
||||
"key" : "7DIBZ-K4HCJ-ZR2FE-FOOOP-SALFT-RLFYW"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"id": "liy-select",
|
||||
"name": "可搜索的下拉选择框",
|
||||
"displayName": "可搜索的下拉选择框",
|
||||
"version": "1.0.0",
|
||||
"description": "通过搜索关键字可以进行 下拉筛选",
|
||||
"keywords": [
|
||||
"uni-ui|select"
|
||||
],
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
]
|
||||
}
|
||||
}
|
||||
270
src/pages.json
Normal file
@@ -0,0 +1,270 @@
|
||||
{
|
||||
"easycom": {
|
||||
// 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175
|
||||
"custom": {
|
||||
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
|
||||
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
|
||||
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue"
|
||||
}
|
||||
},
|
||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||
{
|
||||
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "首页",
|
||||
"navigationBarBackgroundColor": "#36394D",
|
||||
"navigationStyle": "custom",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationStyle": "custom",
|
||||
"backgroundColor": "#FFF7ED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/register",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"backgroundColor": "#FFF7ED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/forgotPassword",
|
||||
"style": {
|
||||
"navigationBarTitleText": "忘记密码",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"backgroundColor": "#FFF7ED"
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"path": "pages/index/class",
|
||||
"style": {
|
||||
"navigationBarTitleText": "分类",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/index/shop",
|
||||
"style": {
|
||||
"navigationBarTitleText": "购物车"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/index/my",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/changgePassword",
|
||||
"style": {
|
||||
"navigationBarTitleText": "修改密码"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/changePhone",
|
||||
"style": {
|
||||
"navigationBarTitleText": "修改手机号"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/changePay",
|
||||
"style": {
|
||||
"navigationBarTitleText": "支付密码"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "v派商家",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
|
||||
"subPackages": [{
|
||||
"root": "pages/userFunc",
|
||||
"pages": [{
|
||||
"path": "webView",
|
||||
"style": {
|
||||
"navigationBarTitleText": "功能页"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "notice",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公告"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "set",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "addressList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的地址"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "addAddress",
|
||||
"style": {
|
||||
"navigationBarTitleText": "添加地址"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "noticeList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "公告列表"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "goodsSearch",
|
||||
"style": {
|
||||
"navigationBarTitleText": "搜索"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "searchList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "搜索内容"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "collect",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的收藏"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "promote",
|
||||
"style": {
|
||||
"navigationBarTitleText": "推广"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "comment",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的评价"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "goComment",
|
||||
"style": {
|
||||
"navigationBarTitleText": "评价"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "setuserData",
|
||||
"style": {
|
||||
"navigationBarTitleText": "设置"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "walletRecord",
|
||||
"style": {
|
||||
"navigationBarTitleText": "收支明细"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "withDrow",
|
||||
"style": {
|
||||
"navigationBarTitleText": "提现"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "transfer",
|
||||
"style": {
|
||||
"navigationBarTitleText": "转账"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "withdrowList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "提现记录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "wallet",
|
||||
"style": {
|
||||
"navigationBarTitleText": "钱包"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "Groupsharing",
|
||||
"style": {
|
||||
"navigationBarTitleText": "拼单商品"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/webView",
|
||||
"pages": [{
|
||||
"path": "goodsDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "商品详情",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orderList",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "orderDetail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#6B7280",
|
||||
"selectedColor": "#F97316",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"height": "50px",
|
||||
"list": [{
|
||||
"pagePath": "pages/index/index",
|
||||
"iconPath": "static/tab/01.png",
|
||||
"selectedIconPath": "static/tab/02.png",
|
||||
"text": "主页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/index/class",
|
||||
"iconPath": "static/tab/class.png",
|
||||
"selectedIconPath": "static/tab/classed.png",
|
||||
"text": "分类"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/index/shop",
|
||||
"iconPath": "static/tab/shop.png",
|
||||
"selectedIconPath": "static/tab/shoped.png",
|
||||
"text": "购物车"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/index/my",
|
||||
"iconPath": "static/tab/user.png",
|
||||
"selectedIconPath": "static/tab/usered.png",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
540
src/pages/index/class.vue
Normal file
@@ -0,0 +1,540 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 切换栏 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item" :class="{ active: activeTab === 0 }" @click="switchTab(0)">
|
||||
<text>普通商品</text>
|
||||
</view>
|
||||
<view class="tab-item" :class="{ active: activeTab === 1 }" @click="switchTab(1)">
|
||||
<text>自营商品</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 骨架屏区域 - 数据加载时显示 -->
|
||||
<view v-if="isLoading" class="skeleton-container" style="padding-top: 80rpx; height: 100vh;">
|
||||
<!-- 左侧分类栏骨架屏 -->
|
||||
<view class="skeleton-left-sidebar">
|
||||
<view v-for="i in 8" :key="i" class="skeleton-sidebar-item">
|
||||
<view class="skeleton-item skeleton-shimmer"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧内容区域骨架屏 -->
|
||||
<view class="skeleton-right-content">
|
||||
<!-- 商品列表骨架 -->
|
||||
<view class="skeleton-goods-list">
|
||||
<view v-for="i in 4" :key="i" class="skeleton-goods-item">
|
||||
<view class="skeleton-goods-img skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-goods-info">
|
||||
<view class="skeleton-goods-name skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-goods-name skeleton-item skeleton-shimmer short"></view>
|
||||
<view class="skeleton-goods-sales skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-goods-bottom">
|
||||
<view class="skeleton-goods-price skeleton-item skeleton-shimmer"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 实际内容区域 - 数据加载完成后显示 -->
|
||||
<view v-else class="category-container" style="padding-top: 80rpx; height: 100vh;">
|
||||
<!-- 左侧分类栏 -->
|
||||
<scroll-view scroll-y class="left-sidebar" :style="{ height: `calc(100vh - 80rpx)` }">
|
||||
<view @click="changeClass(classIndex)" v-for="(classItem, classIndex) in classList" :key="classIndex"
|
||||
:class="{ active: classIndex === nowClass, noactive: classIndex !== nowClass }"
|
||||
class="sidebar-item">
|
||||
{{ classItem?.gtName }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<scroll-view scroll-y class="right-content" :style="{ height: `calc(100vh - 80rpx)` }"
|
||||
@scrolltolower="loadMoreGoods" lower-threshold="100">
|
||||
<view class="goods-list">
|
||||
<view class="goods-item" @click="Service.GoPage('/pages/webView/goodsDetail?goodsId='+item.goodsId)"
|
||||
v-for="(item, index) in goodsList" :key="index">
|
||||
<img :src="Service.GetMateUrlByImg(item.goodsImg)" class="goods-img" alt="" />
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.goodsName }}</view>
|
||||
<view class="goods-sales">已售{{ item.sale }}+</view>
|
||||
<view class="goods-bottom">
|
||||
<view class="goods-price">
|
||||
<text class="new-price">¥{{ item.price }}</text>
|
||||
<text class="old-price">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<up-loadmore :status="status" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { Service } from '@/Service/Service'
|
||||
import { ref } from "vue";
|
||||
import { GoodsService } from '@/Service/api/GoodsService'
|
||||
|
||||
// ==================== 搜索和分类相关变量 ====================
|
||||
let searchWord = ref() // 搜索关键词
|
||||
let nowClass = ref<number>(0) // 当前选中的分类索引
|
||||
|
||||
let loading = ref(false) // 加载状态
|
||||
let noMore = ref(false) // 是否没有更多数据
|
||||
|
||||
let activeTab = ref<number>(0)
|
||||
|
||||
// ==================== 分页相关变量 ====================
|
||||
let status = ref('nomore') // 加载状态:loadmore/loading/nomore
|
||||
let page = ref(1) // 当前页码
|
||||
|
||||
// ==================== 数据列表变量 ====================
|
||||
let classList = ref<Array<any>>([]) // 分类列表
|
||||
let goodsList = ref<Array<any>>([]) // 商品列表
|
||||
|
||||
// ==================== 骨架屏相关变量 ====================
|
||||
let isLoading = ref(true) // 控制骨架屏显示/隐藏的状态
|
||||
|
||||
// ==================== 生命周期钩子 ====================
|
||||
onLoad(() => {
|
||||
// 页面加载时获取分类数据
|
||||
GetSystemGoodsType()
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
// 页面显示时的逻辑
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
// 滚动到底部时加载更多商品
|
||||
getList()
|
||||
})
|
||||
|
||||
// ==================== 数据加载方法 ====================
|
||||
|
||||
/**
|
||||
* 初始化商品数据
|
||||
* 清空现有商品列表,重置页码,开始加载第一页
|
||||
*/
|
||||
const getData = () => {
|
||||
goodsList.value = []
|
||||
status.value = 'loadmore'
|
||||
page.value = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载商品列表数据
|
||||
* 根据当前选中的分类获取商品
|
||||
*/
|
||||
const getList = () => {
|
||||
// 防止重复加载
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
|
||||
// 调用API获取分类商品
|
||||
GoodsService.GetTypeGoodsList(classList.value[nowClass.value].gtId, page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
goodsList.value = [...goodsList.value, ...res.data.goodsList]
|
||||
status.value = res.data.goodsList.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统商品分类
|
||||
* 同时加载第一页的商品数据
|
||||
*/
|
||||
const GetSystemGoodsType = () => {
|
||||
GoodsService.GetSystemGoodsType( activeTab.value==0?'普通商品':'自营商品', page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
classList.value = res.data.goodsType
|
||||
goodsList.value = [...goodsList.value, ...res.data.goodsList]
|
||||
status.value = res.data.goodsList.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
// 数据加载完成,隐藏骨架屏
|
||||
isLoading.value = false
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
// 出错时也隐藏骨架屏
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换分类
|
||||
* @param val 分类索引
|
||||
*/
|
||||
const changeClass = (val : number) => {
|
||||
nowClass.value = val
|
||||
console.log(nowClass.value);
|
||||
getData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换 Tab(保单商品/自营商品)
|
||||
*/
|
||||
const switchTab = (val: number) => {
|
||||
if (activeTab.value === val) return
|
||||
activeTab.value = val
|
||||
nowClass.value = 0
|
||||
goodsList.value=[]
|
||||
page.value=1
|
||||
GetSystemGoodsType()
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到底部加载更多商品
|
||||
*/
|
||||
const loadMoreGoods = () => {
|
||||
getList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// ==================== 基础样式 ====================
|
||||
page {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* ===== 切换栏 ===== */
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
top: 0rpx;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
padding: 24rpx 0;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
position: relative;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: var(--nav-mian);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 4rpx;
|
||||
background: var(--nav-mian);
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.category-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.left-sidebar {
|
||||
width: 200rpx;
|
||||
background-color: #F5F5F5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
padding: 30rpx 20rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.right-content {
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.banner {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.goods-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
width: 100%;
|
||||
margin-bottom: 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.goods-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.new-price {
|
||||
font-weight: 700;
|
||||
color: var(--nav-mian);
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.old-price {
|
||||
margin-left: 10rpx;
|
||||
font-size: 22rpx;
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.goods-sales {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #fff;
|
||||
color: var(--nav-mian);
|
||||
font-weight: 600;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.noactive {
|
||||
background-color: transparent;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.border-20 {
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.padding-20 {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
// ==================== 骨架屏样式 ====================
|
||||
|
||||
// 骨架屏容器 - 左右布局
|
||||
.skeleton-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 骨架屏基础元素样式 - 渐变灰色占位块
|
||||
.skeleton-item {
|
||||
// 使用柔和的渐变背景,从浅灰到稍深的灰色
|
||||
background: linear-gradient(135deg,
|
||||
#f8f8f8 0%,
|
||||
#f0f0f0 50%,
|
||||
#e8e8e8 100%);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
// ==================== 闪光动画效果 ====================
|
||||
// 使用CSS动画实现骨架屏的闪光效果
|
||||
.skeleton-shimmer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// 创建闪光层
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%; // 从左侧开始
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// 使用多层渐变创造更丰富的闪光效果
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.3) 20%, // 淡入
|
||||
rgba(255, 255, 255, 0.6) 40%, // 最亮
|
||||
rgba(255, 255, 255, 0.4) 60%, // 稍暗
|
||||
rgba(255, 255, 255, 0.2) 80%, // 淡出
|
||||
transparent 100%);
|
||||
animation: skeleton-shimmer 2s infinite; // 循环播放动画,稍慢一点更自然
|
||||
}
|
||||
|
||||
// 添加一个额外的渐变层,创造更深的视觉层次
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(255, 255, 255, 0.1) 0%,
|
||||
transparent 50%,
|
||||
rgba(0, 0, 0, 0.03) 100%);
|
||||
pointer-events: none; // 不影响点击
|
||||
}
|
||||
}
|
||||
|
||||
// 闪光动画关键帧
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%); // 起始位置
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(100%); // 结束位置
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 左侧分类栏骨架屏 ====================
|
||||
.skeleton-left-sidebar {
|
||||
width: 200rpx;
|
||||
background-color: #F5F5F5;
|
||||
flex-shrink: 0;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.skeleton-sidebar-item {
|
||||
padding: 30rpx 20rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.skeleton-sidebar-item .skeleton-item {
|
||||
width: 120rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
|
||||
// ==================== 右侧内容区域骨架屏 ====================
|
||||
.skeleton-right-content {
|
||||
flex: 1;
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 商品列表骨架
|
||||
.skeleton-goods-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-goods-item {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-goods-img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.skeleton-goods-info {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.skeleton-goods-name {
|
||||
width: 100%;
|
||||
height: 28rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&.short {
|
||||
width: 60%;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-goods-sales {
|
||||
width: 100rpx;
|
||||
height: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.skeleton-goods-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.skeleton-goods-price {
|
||||
width: 140rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
</style>
|
||||
832
src/pages/index/index.vue
Normal file
@@ -0,0 +1,832 @@
|
||||
<template>
|
||||
<view class="home-page">
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-box" @click="Service.GoPage('/pages/userFunc/goodsSearch')">
|
||||
<u-icon name="search" color="#999" size="18"></u-icon>
|
||||
<view class="search-center">
|
||||
<text class="search-placeholder">搜索宝贝</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主内容 -->
|
||||
<view class="main-content">
|
||||
<!-- 轮播图 -->
|
||||
<view class="swiper-wrap">
|
||||
<up-swiper :list="swiperList" height="200" indicator indicatorMode="dot" circular radius="16"
|
||||
bgColor="#ffffff"></up-swiper>
|
||||
</view>
|
||||
|
||||
<!-- 公告栏 -->
|
||||
<view class="notice-bar">
|
||||
<up-notice-bar :text="gonggao.fubiaoti " bgColor='#fff' ></up-notice-bar>
|
||||
<text class="notice-more" @click="Service.GoPage('/pages/userFunc/noticeList')">更多</text>
|
||||
</view>
|
||||
|
||||
<!-- 爆单商品横向滚动 -->
|
||||
<view v-if="scrollList.length>0" class="section-card preferred-section">
|
||||
<view class="section-header">
|
||||
<view class="header-left">
|
||||
<view class="section-icon">
|
||||
<u-icon name="heart-fill" color="#fff" size="16"></u-icon>
|
||||
</view>
|
||||
<text class="section-title">报单商品</text>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-x class="preferred-scroll" show-scrollbar="false">
|
||||
<view class="preferred-item" v-for="(item, index) in scrollList" :key="index"
|
||||
@click="goGoodsDetail(item.goodsId)">
|
||||
<view class="preferred-img-wrap">
|
||||
<image class="preferred-img" :src="item.goodsImg" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="preferred-name">{{ item.goodsName }}</view>
|
||||
<view class="preferred-price-row">
|
||||
<text class="preferred-price">¥{{ item.price }}</text>
|
||||
<text class="preferred-old">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<!-- 商品瀑布流 -->
|
||||
<view class="goods-waterfall">
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in leftList" :key="index"
|
||||
@click="goGoodsDetail(item.goodsId)">
|
||||
<view class="goods-img-wrap">
|
||||
<image class="goods-img" :src="item.goodsImg" mode="widthFix"></image>
|
||||
<view class="goods-label">{{ item.goodsType }}</view>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.goodsName }}</view>
|
||||
<view class="goods-price-row">
|
||||
<view class="price-wrap">
|
||||
<text class="goods-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
<text class="goods-old">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
<text class="goods-sold">{{ item.sale }}人付款</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in rightList" :key="index"
|
||||
@click="goGoodsDetail(item.goodsId)">
|
||||
<view class="goods-img-wrap">
|
||||
<image class="goods-img" :src="item.goodsImg" mode="widthFix"></image>
|
||||
<view class="goods-label">{{ item.goodsType }}</view>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.goodsName }}</view>
|
||||
<view class="goods-tags" v-if="item.tags && item.tags.length">
|
||||
<text class="tag-text" v-for="(tag, tIndex) in item.tags" :key="tIndex">{{ tag }}</text>
|
||||
</view>
|
||||
<view class="goods-price-row">
|
||||
<view class="price-wrap">
|
||||
<text class="goods-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
<text class="goods-old">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
<text class="goods-sold">{{ item.sale }}人付款</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 加载更多 -->
|
||||
<view v-if="leftList.length>0" class="load-more">
|
||||
<up-loadmore :status="status" />
|
||||
</view>
|
||||
<view v-else class="empty-state">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/null.png')" class="empty-image" mode="aspectFit"></image>
|
||||
<text class="empty-title">暂无商品</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="bottom-placeholder"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { GoodsService } from '@/Service/api/GoodsService'
|
||||
|
||||
// 搜索值
|
||||
let searchValue = ref('');
|
||||
let status = ref('nomore');
|
||||
|
||||
// 轮播图数据
|
||||
let swiperList = ref<Array<any>>([]);
|
||||
// 优选商品数据
|
||||
let scrollList =ref<Array<any>>([]);
|
||||
// 商品数据
|
||||
let leftList = ref<Array<any>>([]);
|
||||
let rightList = ref<Array<any>>([]);
|
||||
//公告数据
|
||||
let gonggao=ref<any>({});
|
||||
|
||||
let page = ref(1) // 当前页码
|
||||
|
||||
onLoad(() => {
|
||||
getData()
|
||||
});
|
||||
|
||||
|
||||
const getData = () => {
|
||||
rightList.value = []
|
||||
leftList.value = []
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载商品列表数据
|
||||
* 包含轮播图、优选商品和商品列表
|
||||
*/
|
||||
const getList = () => {
|
||||
// 防止重复加载
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
|
||||
// 调用API获取商品数据
|
||||
GoodsService.GetGoodsList( page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
// 第一页时加载轮播图
|
||||
if (page.value == 1) {
|
||||
swiperList.value=[]
|
||||
res.data.bannerList.map((item : any) => {
|
||||
swiperList.value.push(Service.GetIconImg(item.url))
|
||||
})
|
||||
}
|
||||
// 第一页时加载优选商品
|
||||
if (page.value == 1) {
|
||||
scrollList.value = res.data.customsGoods
|
||||
}
|
||||
|
||||
// 第一页时加载优选商品
|
||||
if (page.value == 1) {
|
||||
gonggao.value = res.data.gonggao
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 将商品列表分为左右两列
|
||||
res.data.goodsList.map((item : any, index : any) => {
|
||||
if (index % 2 == 1) {
|
||||
rightList.value.push(item)
|
||||
} else {
|
||||
leftList.value.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
// 更新加载状态
|
||||
status.value = res.data.goodsList.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
|
||||
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const goGoodsDetail = (goodsId : number) => {
|
||||
Service.GoPage('/pages/webView/goodsDetail?goodsId=' + goodsId);
|
||||
// Service.GoPage('/pages/webView/goodsDetail?goodsId=' + goodsId);
|
||||
};
|
||||
|
||||
const onCategoryClick = (item : any) => {
|
||||
Service.Msg('点击了 ' + item.name);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.home-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
padding: 20rpx 30rpx;
|
||||
padding-top: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 40rpx;
|
||||
padding: 14rpx 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 12rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.search-hot {
|
||||
font-size: 20rpx;
|
||||
color: #FF6A00;
|
||||
background: rgba(255, 106, 0, 0.1);
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.search-msg {
|
||||
position: relative;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
.msg-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: #FF3D3D;
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 主内容 */
|
||||
.main-content {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
/* 轮播图 */
|
||||
.swiper-wrap {
|
||||
margin-top: 10rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 106, 0, 0.15);
|
||||
}
|
||||
|
||||
/* 公告栏 */
|
||||
.notice-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 0 20rpx;
|
||||
margin-top: 20rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.notice-scroll {
|
||||
flex: 1;
|
||||
margin-left: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notice-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notice-more {
|
||||
font-size: 22rpx;
|
||||
color: #FF6A00;
|
||||
margin-left: 12rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 分类宫格 */
|
||||
.category-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx 10rpx 20rpx;
|
||||
margin-top: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.category-item {
|
||||
width: 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.category-item:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.category-icon-wrapper {
|
||||
width: 84rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: 24rpx;
|
||||
color: #444;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 活动入口 */
|
||||
.activity-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
flex: 1;
|
||||
border-radius: 20rpx;
|
||||
padding: 20rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 160rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.activity-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.activity-desc {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
margin-top: 6rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: inline-block;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.activity-img {
|
||||
position: absolute;
|
||||
bottom: -10rpx;
|
||||
right: -10rpx;
|
||||
width: 110rpx;
|
||||
height: 110rpx;
|
||||
opacity: 0.25;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.activity-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #FF3D3D;
|
||||
font-size: 18rpx;
|
||||
font-weight: bold;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 0 20rpx 0 12rpx;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
/* 优选商品 */
|
||||
.section-card {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
margin-top: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(to right, #FF3D3D, #FF6A00);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-left: 6rpx;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-more {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.preferred-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.preferred-item {
|
||||
display: inline-block;
|
||||
width: 240rpx;
|
||||
margin-right: 20rpx;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.preferred-img-wrap {
|
||||
position: relative;
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.preferred-img {
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
border-radius: 16rpx;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.preferred-discount {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, #FF3D3D, #FF6A00);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 16rpx 0 16rpx 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.preferred-name {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
margin-top: 12rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.preferred-price-row {
|
||||
margin-top: 8rpx;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.preferred-price {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #FF3D3D;
|
||||
}
|
||||
|
||||
.preferred-old {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.preferred-progress {
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 12rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #FF3D3D, #FF6A00);
|
||||
border-radius: 6rpx;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 18rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
/* 猜你喜欢 */
|
||||
.guess-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 36rpx;
|
||||
margin-bottom: 24rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.guess-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(to right, #FF6A00, #FF9A56);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 106, 0, 0.3);
|
||||
}
|
||||
|
||||
.guess-line {
|
||||
width: 60rpx;
|
||||
height: 2rpx;
|
||||
background: linear-gradient(to right, transparent, #ddd);
|
||||
}
|
||||
|
||||
.guess-title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 商品瀑布流 */
|
||||
.goods-waterfall {
|
||||
margin-top: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.waterfall-col {
|
||||
width: 48.5%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.goods-card {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.goods-card:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.goods-img-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.goods-label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(135deg, #FF6A00, #FF4500);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 6rpx 14rpx;
|
||||
border-radius: 0 0 16rpx 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.goods-rank {
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
left: 12rpx;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: #FFD700;
|
||||
font-size: 20rpx;
|
||||
font-weight: bold;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
backdrop-filter: blur(4rpx);
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
padding: 18rpx;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.goods-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10rpx;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 20rpx;
|
||||
color: #FF6A00;
|
||||
border: 1rpx solid rgba(255, 106, 0, 0.3);
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 8rpx;
|
||||
background: rgba(255, 106, 0, 0.05);
|
||||
}
|
||||
|
||||
.goods-price-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.price-wrap {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 24rpx;
|
||||
color: #FF3D3D;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.price-num {
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.goods-old {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.goods-sold {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.goods-shop {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12rpx;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.shop-tag {
|
||||
font-size: 18rpx;
|
||||
color: #fff;
|
||||
background: linear-gradient(to right, #FF3D3D, #FF6A00);
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.shop-name {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 加载更多 */
|
||||
.load-more {
|
||||
padding: 30rpx 0;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 80rpx 40rpx;
|
||||
margin: 20rpx 30rpx 0;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 260rpx;
|
||||
height: 260rpx;
|
||||
margin-bottom: 16rpx;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.bottom-placeholder {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
545
src/pages/index/my.vue
Normal file
@@ -0,0 +1,545 @@
|
||||
<template>
|
||||
<view class="user-page">
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-info-card">
|
||||
<view class="user-header">
|
||||
<view class="user-info">
|
||||
<view class="avatar-wrapper">
|
||||
<image :src="Service.GetMateUrlByImg(info.avatar) "
|
||||
class="avatar"></image>
|
||||
</view>
|
||||
<view class="user-details">
|
||||
<text class="username">{{ info.nick || '用户昵称' }}</text>
|
||||
<text class="member-rights">{{ phone(info.phone) || '138****8888' }}</text>
|
||||
</view>
|
||||
<view @click="Service.GoPage('/pages/userFunc/setuserData')" class="edit-profile">
|
||||
<text>编辑</text>
|
||||
<up-icon name="arrow-right" color="#fff" size="10"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<!-- 我的订单 -->
|
||||
<view class="section-card" style="margin-top: 24rpx;">
|
||||
<view class="section-header">
|
||||
<view class="section-title-wrap">
|
||||
<text class="section-title">我的订单</text>
|
||||
</view>
|
||||
<view @click="Service.GoPage('/pages/webView/orderList?type=0')" class="view-all">
|
||||
<text>全部订单</text>
|
||||
<view class="view-all-arrow">
|
||||
<up-icon name="arrow-right" color="#fff" size="10"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="order-types">
|
||||
<view @click="Service.GoPage('/pages/webView/orderList?type=' + (index + 1))"
|
||||
v-for="(item, index) in orderList" :key="index" class="order-type" hover-class="order-type-active">
|
||||
<view class="order-icon-wrapper">
|
||||
<view class="order-icon-bg" :class="'order-icon-bg--' + index">
|
||||
<image :src="Service.GetpayImg(item.img)" class="order-icon"></image>
|
||||
</view>
|
||||
<view v-if="item.count > 0" class="order-badge">{{ item.count > 99 ? '99+' : item.count }}
|
||||
</view>
|
||||
</view>
|
||||
<text class="order-text">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能网格 -->
|
||||
<view class="section-card">
|
||||
<view class="section-header">
|
||||
<view class="section-title-wrap">
|
||||
<text class="section-title">我的功能</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="service-list">
|
||||
<view @click="goPage(item.path)" class="service-list-item" v-for="(item, index) in myService"
|
||||
:key="index" hover-class="service-item-active">
|
||||
<view class="service-list-left">
|
||||
<view class="service-list-icon-wrap" :class="'service-list-icon-wrap--' + index">
|
||||
<image :src="Service.GetpayImg(item.img)" class="order-icon"></image>
|
||||
</view>
|
||||
<text class="service-list-text">{{ item.name }}</text>
|
||||
</view>
|
||||
<up-icon name="arrow-right" color="#D1D5DB" size="14"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="" style="width: 100%; height: 50rpx;" >
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from "@/Service/Service"
|
||||
import { UserService } from "@/Service/api/UserService"
|
||||
|
||||
|
||||
let info = ref<any>({})
|
||||
let walletAmount = ref<string>('0.00')
|
||||
let collectCount = ref<number>(0)
|
||||
let promoteCount = ref<number>(0)
|
||||
|
||||
let orderList = ref([
|
||||
{
|
||||
img: '/static/icon/my/pay.png',
|
||||
name: '待付款',
|
||||
count: 0
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/send.png',
|
||||
name: '待发货',
|
||||
count: 0
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/get.png',
|
||||
name: '待收货',
|
||||
count: 0
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/message.png',
|
||||
name: '待评价',
|
||||
count: 0
|
||||
}
|
||||
])
|
||||
|
||||
let myService = ref([
|
||||
{
|
||||
img: '/static/icon/my/service/card.png',
|
||||
name: '钱包',
|
||||
path: '/pages/userFunc/wallet'
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/service/ping.png',
|
||||
name: '爆品团',
|
||||
path: '/pages/userFunc/Groupsharing'
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/service/promote.png',
|
||||
name: '我的推广',
|
||||
path: '/pages/userFunc/promote'
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/service/select.png',
|
||||
name: '收藏夹',
|
||||
path: '/pages/userFunc/collect'
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/service/location.png',
|
||||
name: '收货地址',
|
||||
path: '/pages/userFunc/addressList',
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/service/comment.png',
|
||||
name: '我的评价',
|
||||
path: '/pages/userFunc/comment',
|
||||
},
|
||||
{
|
||||
img: '/static/icon/my/service/set.png',
|
||||
name: '设置',
|
||||
path: '/pages/userFunc/set',
|
||||
}
|
||||
])
|
||||
|
||||
onShow(() => {
|
||||
getData()
|
||||
})
|
||||
|
||||
const getData = () => {
|
||||
|
||||
|
||||
UserService.GetUserInfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
info.value = res.data.info
|
||||
orderList.value[0].count = res.data.dfk || 0
|
||||
orderList.value[1].count = res.data.dfh || 0
|
||||
orderList.value[2].count = res.data.dsh || 0
|
||||
orderList.value[3].count = res.data.dpj || 0
|
||||
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const phone = (data : any) => {
|
||||
if (!data) return ''
|
||||
return String(data).slice(0, 3) + '****' + String(data).slice(-4)
|
||||
}
|
||||
|
||||
const goPage = (path : any) => {
|
||||
if (path) {
|
||||
Service.GoPage(path)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background-color: #F8F9FA;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ===== 用户信息模块 ===== */
|
||||
.user-info-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 60rpx 30rpx 0;
|
||||
padding: 30rpx;
|
||||
background: var(--nav-vice);
|
||||
backdrop-filter: blur(12rpx);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.12);
|
||||
}
|
||||
|
||||
.user-header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
flex-shrink: 0;
|
||||
padding: 4rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.35);
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
background-color: #f5f5f5;
|
||||
border: 3rpx solid rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.user-details {
|
||||
margin-left: 24rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
letter-spacing: 1rpx;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.member-rights {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin-top: 10rpx;
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.edit-profile {
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
flex-shrink: 0;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 30rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
backdrop-filter: blur(8rpx);
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.edit-profile:active {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* ===== 数据概览 ===== */
|
||||
.stats-bar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
margin: 24rpx 30rpx 0;
|
||||
padding: 30rpx 0;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(12rpx);
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.12);
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
flex: 1;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
.stats-item:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.stats-num {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
letter-spacing: 0.5rpx;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stats-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background: linear-gradient(180deg, transparent, #E5E7EB, transparent);
|
||||
}
|
||||
|
||||
/* ===== 卡片区域 ===== */
|
||||
.section-card {
|
||||
background-color: #fff;
|
||||
margin: 24rpx 30rpx 0;
|
||||
border-radius: 28rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 28rpx 20rpx;
|
||||
}
|
||||
|
||||
.section-title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.section-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 14rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.order-section-icon {
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
}
|
||||
|
||||
.service-section-icon {
|
||||
background: linear-gradient(135deg, #3B82F6, #60A5FA);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.view-all {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
|
||||
.view-all:active {
|
||||
transform: scale(0.95);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.view-all-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ===== 订单区域 ===== */
|
||||
.order-types {
|
||||
display: flex;
|
||||
padding: 10rpx 0 ;
|
||||
}
|
||||
|
||||
.order-type {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
.order-type-active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.order-icon-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.order-icon-bg {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
transition: transform 0.2s;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.order-icon-bg--0 {
|
||||
background: linear-gradient(135deg, #FFF7ED, #FFEDD5);
|
||||
}
|
||||
|
||||
.order-icon-bg--1 {
|
||||
background: linear-gradient(135deg, #EFF6FF, #DBEAFE);
|
||||
}
|
||||
|
||||
.order-icon-bg--2 {
|
||||
background: linear-gradient(135deg, #ECFDF5, #D1FAE5);
|
||||
}
|
||||
|
||||
.order-icon-bg--3 {
|
||||
background: linear-gradient(135deg, #FDF2F8, #FCE7F3);
|
||||
}
|
||||
|
||||
.order-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.order-badge {
|
||||
position: absolute;
|
||||
top: -4rpx;
|
||||
right: -8rpx;
|
||||
min-width: 32rpx;
|
||||
height: 32rpx;
|
||||
padding: 0 8rpx;
|
||||
background: linear-gradient(135deg, #FF6A00, #FF4500);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
line-height: 32rpx;
|
||||
text-align: center;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 106, 0, 0.4);
|
||||
border: 2rpx solid #fff;
|
||||
}
|
||||
|
||||
.order-text {
|
||||
font-size: 24rpx;
|
||||
color: #4B5563;
|
||||
margin-top: 14rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ===== 服务列表 ===== */
|
||||
.service-list {
|
||||
padding: 0 20rpx 20rpx;
|
||||
}
|
||||
|
||||
.service-list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 16rpx;
|
||||
transition: all 0.2s;
|
||||
border-bottom: 1rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.service-list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.service-list-item:active {
|
||||
background: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.service-list-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.service-list-icon-wrap {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 18rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.service-list-icon-wrap--0 {
|
||||
background: linear-gradient(135deg, #FFF1F2, #FFE4E6);
|
||||
}
|
||||
|
||||
.service-list-icon-wrap--1 {
|
||||
background: linear-gradient(135deg, #F0FDF4, #DCFCE7);
|
||||
}
|
||||
|
||||
.service-list-icon-wrap--2 {
|
||||
background: linear-gradient(135deg, #F5F3FF, #EDE9FE);
|
||||
}
|
||||
|
||||
.service-list-icon-wrap--3 {
|
||||
background: linear-gradient(135deg, #F0F9FF, #E0F2FE);
|
||||
}
|
||||
|
||||
.service-list-icon-wrap--4 {
|
||||
background: linear-gradient(135deg, #FFFBEB, #FDE68A);
|
||||
}
|
||||
|
||||
.service-list-icon-wrap--5 {
|
||||
background: linear-gradient(135deg, #ECFEFF, #CFFAFE);
|
||||
}
|
||||
|
||||
.service-list-text {
|
||||
font-size: 26rpx;
|
||||
color: #1F2937;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.service-item-active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
</style>
|
||||
522
src/pages/index/shop.vue
Normal file
@@ -0,0 +1,522 @@
|
||||
<template>
|
||||
<view class="cart-container">
|
||||
|
||||
<!-- 购物车标题栏 -->
|
||||
<view class="cart-header">
|
||||
<text class="cart-title">我的购物车</text>
|
||||
<text v-if='!isDele' @click="isDele=true" class="cart-manage">管理</text>
|
||||
<text v-if='isDele' @click="isDele=false" class="cart-manage">退出管理</text>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 购物车商品列表 -->
|
||||
<scroll-view scroll-y class="cart-items" :show-scrollbar="false">
|
||||
<view class="cart-item" v-for="(item, index) in cartData" :key="item.cgId">
|
||||
<view class="" @click.stop="selectItem(index)"
|
||||
:style="{ 'background-color': selectedIds.includes(item.cgId) ? '#2979ff' : '', 'border': selectedIds.includes(item.cgId) ? '' : '1rpx solid #e2e2e2' }"
|
||||
style="display: flex; align-items: center; justify-content: center; width: 34rpx; height: 34rpx; border-radius: 4rpx;">
|
||||
<up-icon name="checkbox-mark" color="#fff" size="14"></up-icon>
|
||||
</view>
|
||||
<image :src="Service.GetMateUrlByImg(item.goodsImg)" mode="scaleToFill" class="item-image">
|
||||
</image>
|
||||
<view class="item-info" style="position: relative;">
|
||||
<view class="" style="display: flex; justify-content: space-between;">
|
||||
<text class="item-title" style="flex: 1;">{{ item.goodsName }}</text>
|
||||
<image v-if="isDele" @click.stop="deleteItem(item)"
|
||||
:src="Service.GetpayImg('/static/icon/shop/dele.png')"
|
||||
style="width: 32rpx; height: 32rpx;" />
|
||||
</view>
|
||||
<view class="">
|
||||
<text v-for="(ruleIndex, ruleItem) in item.rule.split('-')" :key="ruleItem"
|
||||
class="item-spec">{{ ruleIndex }}</text>
|
||||
</view>
|
||||
<view class="item-actions">
|
||||
<text class="item-price">¥{{ item.price.toFixed(2) }}</text>
|
||||
<view class="stepper">
|
||||
<view class="stepper-btn" :class="{ disabled: item.count <= 1 }"
|
||||
@click.stop="decreaseCount(item)">
|
||||
-
|
||||
</view>
|
||||
<view class="stepper-input">{{ item.count }}</view>
|
||||
<view class="stepper-btn" @click.stop="increaseCount(item)">
|
||||
+
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空购物车提示 -->
|
||||
<view v-if="cartData.length === 0" class="empty-cart">
|
||||
<image :src="Service.GetpayImg('/static/dele/empty-cart.png')" mode="aspectFit" class="empty-image">
|
||||
</image>
|
||||
<text class="empty-text">购物车空空如也</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部空间,确保内容不被底部结算栏遮挡 -->
|
||||
<view class="bottom-space"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部结算栏 -->
|
||||
<view class="checkout-bar">
|
||||
<view v-if="!isDele" class="select-all" @click="toggleSelectAll">
|
||||
<view class=""
|
||||
:style="{ 'background-color': isAllSelected ? '#2979ff' : '', 'border': isAllSelected ? '' : '1rpx solid #e2e2e2' }"
|
||||
style="display: flex; align-items: center; justify-content: center; width: 34rpx; height: 34rpx; border-radius: 4rpx;">
|
||||
<up-icon name="checkbox-mark" color="#fff" size="14"></up-icon>
|
||||
</view>
|
||||
<text class="select-all-text">全选</text>
|
||||
</view>
|
||||
<view v-if="!isDele" class="total-info">
|
||||
<view class="total-price">
|
||||
<text class="total-text">合计:</text>
|
||||
<text class="price-text">¥{{ totalPrice.toFixed(2) }}</text>
|
||||
</view>
|
||||
<text class="total-count">共{{ totalCount }}件</text>
|
||||
</view>
|
||||
<view v-if="!isDele" class="checkout-btn" @click="handleCheckout">
|
||||
<text class="checkout-text">结算({{ selectedCount }})</text>
|
||||
</view>
|
||||
|
||||
<view v-if="isDele" style="width: 100%;" class="delete-btn" @click="deleteSelectedItems">
|
||||
<view class=""
|
||||
style=" padding: 20rpx 30rpx; border-radius: 10rpx; font-size: 22rpx; background-color: red; color: #fff; display: flex; align-items: center; justify-content: center; ">
|
||||
删除选中({{ selectedCount }})
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import { CarService } from '@/Service/api/CarService';
|
||||
import { OrderService } from '@/Service/api/OrderService';
|
||||
|
||||
let isDele = ref(false)
|
||||
|
||||
let status = ref('nomore')
|
||||
let page = ref(1)
|
||||
|
||||
let selectedIds = ref<string[]>([])
|
||||
|
||||
// 购物车数据结构
|
||||
const cartData = ref<Array<any>>([])
|
||||
|
||||
|
||||
|
||||
// 计算属性:选中商品列表
|
||||
const selectedItems = computed(() => {
|
||||
return cartData.value.filter(item => selectedIds.value.includes(item.cgId))
|
||||
})
|
||||
|
||||
// 计算属性:选中商品总数
|
||||
const selectedCount = computed(() => {
|
||||
return selectedItems.value.length
|
||||
})
|
||||
|
||||
// 计算属性:选中商品总数量
|
||||
const totalCount = computed(() => {
|
||||
return selectedItems.value.reduce((sum, item) => sum + item.count, 0)
|
||||
})
|
||||
|
||||
// 计算属性:选中商品总价
|
||||
const totalPrice = computed(() => {
|
||||
return selectedItems.value.reduce((sum, item) => sum + item.count * item.price, 0)
|
||||
})
|
||||
|
||||
// 计算属性:是否全选
|
||||
const isAllSelected = computed(() => {
|
||||
return cartData.value.length > 0 && selectedIds.value.length === cartData.value.length
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
selectedIds.value = []
|
||||
getData()
|
||||
})
|
||||
|
||||
// 获取商品
|
||||
const getData = () => {
|
||||
cartData.value = []
|
||||
status.value = 'loadmore'
|
||||
page.value = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
CarService.GetUserCarList(page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
cartData.value = [...cartData.value, ...res.data.list]
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 选择/取消选择单个商品
|
||||
const selectItem = (index: number) => {
|
||||
const cgId = cartData.value[index].cgId
|
||||
const idx = selectedIds.value.indexOf(cgId)
|
||||
if (idx > -1) {
|
||||
selectedIds.value.splice(idx, 1)
|
||||
} else {
|
||||
selectedIds.value.push(cgId)
|
||||
}
|
||||
}
|
||||
|
||||
// 全选/取消全选
|
||||
const toggleSelectAll = () => {
|
||||
if (isAllSelected.value) {
|
||||
selectedIds.value = []
|
||||
} else {
|
||||
selectedIds.value = cartData.value.map(item => item.cgId)
|
||||
}
|
||||
}
|
||||
|
||||
// 增加商品数量
|
||||
const increaseCount = (shop : any) => {
|
||||
shop.count++
|
||||
updateItemCount(shop.cgId, shop.count)
|
||||
}
|
||||
|
||||
// 减少商品数量
|
||||
const decreaseCount = (shop : any) => {
|
||||
if (shop.count <= 1) {
|
||||
return
|
||||
}
|
||||
shop.count--
|
||||
updateItemCount(shop.cgId, shop.count)
|
||||
}
|
||||
|
||||
// 更新商品数量
|
||||
const updateItemCount = (cgId : string, count : number) => {
|
||||
CarService.UpdateCarCount(cgId, count).then(res => {
|
||||
if (res.code == 0) {
|
||||
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除单个商品
|
||||
const deleteItem = (goods : any) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该商品吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
CarService.DelUserCar(goods.goodsId, 1).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg(res.data)
|
||||
getData()
|
||||
selectedIds.value = []
|
||||
isDele.value = false
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 删除选中商品
|
||||
const deleteSelectedItems = () => {
|
||||
if (selectedIds.value.length === 0) {
|
||||
Service.Msg('请选择要删除的商品')
|
||||
return
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `确定要删除选中的${selectedIds.value.length}件商品吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
const goodsIds = selectedItems.value.map(item => item.goodsId).join(',')
|
||||
CarService.DelUserCar(goodsIds, 2).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg(res.data)
|
||||
selectedIds.value = []
|
||||
getData()
|
||||
isDele.value = false
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
isDele.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 结算按钮点击处理
|
||||
const handleCheckout = () => {
|
||||
if (selectedCount.value === 0) {
|
||||
Service.Msg('请选择要结算的商品')
|
||||
return
|
||||
}
|
||||
const data = selectedItems.value.map(item => ({
|
||||
goodsId: item.goodsId,
|
||||
goodsImg: item.goodsImg,
|
||||
goodsName: item.goodsName,
|
||||
rule: item.rule,
|
||||
count: item.count,
|
||||
price: item.price
|
||||
}))
|
||||
|
||||
OrderService.AddUserOrder(JSON.stringify(data), 0).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.GoPage('/pages/webView/orderDetail?orderId=' + res.data.orderId)
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cart-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 购物车标题栏 */
|
||||
.cart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
background-color: #ffffff;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.cart-title {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.cart-manage {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* 购物车商品列表 */
|
||||
.cart-items {
|
||||
flex: 1;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
/* 商品项 */
|
||||
.cart-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
margin: 0 20rpx 10rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.item-image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
margin-left: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
flex: 1;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.item-spec {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
margin-right: 10rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.item-price {
|
||||
font-size: 32rpx;
|
||||
color: #ff6600;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 手写步进器样式 */
|
||||
.stepper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stepper-btn {
|
||||
width: 56rpx;
|
||||
height: 52rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 36rpx;
|
||||
color: #333;
|
||||
background-color: #f8f8f8;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.stepper-btn:active {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.stepper-btn.disabled {
|
||||
color: #ccc;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.stepper-input {
|
||||
width: 80rpx;
|
||||
height: 56rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
border-left: 1rpx solid #e0e0e0;
|
||||
border-right: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
/* 空购物车 */
|
||||
.empty-cart {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 底部空间 */
|
||||
.bottom-space {
|
||||
height: 220rpx;
|
||||
}
|
||||
|
||||
/* 底部结算栏 */
|
||||
.checkout-bar {
|
||||
position: fixed;
|
||||
bottom: 0rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #ffffff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.select-all-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.total-info {
|
||||
flex: 1;
|
||||
margin-left: 32rpx;
|
||||
}
|
||||
|
||||
.total-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.total-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.price-text {
|
||||
font-size: 36rpx;
|
||||
color: #ff6600;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.total-count {
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.checkout-btn {
|
||||
width: 200rpx;
|
||||
height: 72rpx;
|
||||
background-color: #ff6600;
|
||||
border-radius: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.checkout-text {
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
284
src/pages/login/changePay.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<view class="set-paypwd-page">
|
||||
<view class="set-paypwd-container">
|
||||
<view class="set-paypwd-header">
|
||||
<text class="page-title">设置支付密码</text>
|
||||
<text class="page-subtitle">请输入手机号和验证码设置支付密码</text>
|
||||
</view>
|
||||
|
||||
<view class="set-paypwd-form">
|
||||
<view class="input-item">
|
||||
<up-icon name="phone" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.phone" class="input-field" type="number" maxlength="11"
|
||||
placeholder="请输入手机号" />
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<up-icon name="error-circle" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.code" class="input-field code-input" type="number" maxlength="6"
|
||||
placeholder="请输入验证码" />
|
||||
<button class="code-btn" :disabled="counting" @click="getCode">
|
||||
{{ counting ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<up-icon name="lock" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.password" class="input-field" :type="showPassword ? 'text' : 'password'"
|
||||
maxlength="6" placeholder="请输入支付密码(6位数字)" />
|
||||
<view class="password-toggle" @click="togglePassword">
|
||||
<up-icon :name="showPassword ? 'eye' : 'eye-off'" size="18" color="#999"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<up-icon name="lock" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.confirmPassword" class="input-field" :type="showConfirmPassword ? 'text' : 'password'"
|
||||
maxlength="6" placeholder="请确认支付密码" />
|
||||
<view class="password-toggle" @click="toggleConfirmPassword">
|
||||
<up-icon :name="showConfirmPassword ? 'eye' : 'eye-off'" size="18" color="#999"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="confirm-btn" @click="confirmSet">
|
||||
确认设置
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { LoginService } from "@/Service/api/LoginService"
|
||||
import { UserService } from "@/Service/api/UserService"
|
||||
|
||||
const counting = ref(false);
|
||||
const countdown = ref(60);
|
||||
const showPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
|
||||
const passwordForm = ref({
|
||||
phone: '',
|
||||
code: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
});
|
||||
|
||||
const togglePassword = () => {
|
||||
showPassword.value = !showPassword.value;
|
||||
};
|
||||
|
||||
const toggleConfirmPassword = () => {
|
||||
showConfirmPassword.value = !showConfirmPassword.value;
|
||||
};
|
||||
|
||||
const getCode = () => {
|
||||
if (!passwordForm.value.phone) {
|
||||
Service.Msg('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(passwordForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
counting.value = true;
|
||||
LoginService.SendSms(passwordForm.value.phone, 'ForgetPayPwd').then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('验证码已发送!');
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
counting.value = false;
|
||||
countdown.value = 60;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const confirmSet = () => {
|
||||
if (!passwordForm.value.phone) {
|
||||
Service.Msg('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(passwordForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordForm.value.code) {
|
||||
Service.Msg('请输入验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordForm.value.password) {
|
||||
Service.Msg('请输入支付密码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordForm.value.password.length !== 6) {
|
||||
Service.Msg('支付密码必须是6位数字');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(passwordForm.value.password)) {
|
||||
Service.Msg('支付密码只能是数字');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordForm.value.confirmPassword) {
|
||||
Service.Msg('请确认支付密码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordForm.value.password !== passwordForm.value.confirmPassword) {
|
||||
Service.Msg('两次密码输入不一致');
|
||||
return;
|
||||
}
|
||||
|
||||
Service.LoadIng('设置中...');
|
||||
let data = {
|
||||
phone: passwordForm.value.phone,
|
||||
code: passwordForm.value.code,
|
||||
pwd: passwordForm.value.password,
|
||||
npwd: passwordForm.value.confirmPassword
|
||||
}
|
||||
|
||||
UserService.ForgetPayPwd(data).then(res => {
|
||||
Service.LoadClose();
|
||||
if(res.code == 0){
|
||||
Service.Msg('支付密码设置成功!');
|
||||
setTimeout(() => {
|
||||
Service.GoPageBack()
|
||||
}, 1000)
|
||||
}else{
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.set-paypwd-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.set-paypwd-container {
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.1);
|
||||
}
|
||||
|
||||
.set-paypwd-header {
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.set-paypwd-form {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 28rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
margin-left: 20rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
padding: 0 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 30rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
214
src/pages/login/changePhone.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<view class="change-phone-page">
|
||||
<view class="change-phone-container">
|
||||
<view class="change-phone-header">
|
||||
<text class="page-title">修改手机号</text>
|
||||
<text class="page-subtitle">请输入新的手机号码进行绑定</text>
|
||||
</view>
|
||||
|
||||
<view class="change-phone-form">
|
||||
<view class="input-item">
|
||||
<up-icon name="phone" size="20" color="#999"></up-icon>
|
||||
<input v-model="phoneForm.phone" class="input-field" type="number" maxlength="11"
|
||||
placeholder="请输入新手机号" />
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<up-icon name="error-circle" size="20" color="#999"></up-icon>
|
||||
<input v-model="phoneForm.code" class="input-field code-input" type="number" maxlength="6"
|
||||
placeholder="请输入验证码" />
|
||||
<button class="code-btn" :disabled="counting" @click="getCode">
|
||||
{{ counting ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<button class="confirm-btn" @click="confirmBind">
|
||||
确认绑定
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { LoginService } from "@/Service/api/LoginService"
|
||||
import { UserService } from "@/Service/api/UserService"
|
||||
|
||||
const counting = ref(false);
|
||||
const countdown = ref(60);
|
||||
|
||||
const phoneForm = ref({
|
||||
phone: '',
|
||||
code: ''
|
||||
});
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
});
|
||||
|
||||
const getCode = () => {
|
||||
if (!phoneForm.value.phone) {
|
||||
Service.Msg('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(phoneForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
counting.value = true;
|
||||
LoginService.SendSms(phoneForm.value.phone, 'UpdatePhone').then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('验证码已发送!');
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
counting.value = false;
|
||||
countdown.value = 60;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const confirmBind = () => {
|
||||
if (!phoneForm.value.phone) {
|
||||
Service.Msg('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(phoneForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!phoneForm.value.code) {
|
||||
Service.Msg('请输入验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
Service.LoadIng('绑定中...');
|
||||
|
||||
UserService.UpdateUserPhone(phoneForm.value.phone,phoneForm.value.code).then(res=>{
|
||||
if(res.code==0){
|
||||
Service.LoadClose();
|
||||
Service.Msg('手机号修改成功');
|
||||
Service.GoPageBack();
|
||||
}else{
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.change-phone-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.change-phone-container {
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.1);
|
||||
}
|
||||
|
||||
.change-phone-header {
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.change-phone-form {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 28rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
margin-left: 20rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
padding: 0 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 30rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
276
src/pages/login/changgePassword.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<view class="change-password-page">
|
||||
<view class="change-password-container">
|
||||
<view class="change-password-header">
|
||||
<text class="page-title">修改密码</text>
|
||||
<text class="page-subtitle">请输入手机号和验证码设置新密码</text>
|
||||
</view>
|
||||
|
||||
<view class="change-password-form">
|
||||
<view class="input-item">
|
||||
<up-icon name="phone" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.phone" class="input-field" type="number" maxlength="11"
|
||||
placeholder="请输入手机号" />
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<up-icon name="error-circle" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.code" class="input-field code-input" type="number" maxlength="6"
|
||||
placeholder="请输入验证码" />
|
||||
<button class="code-btn" :disabled="counting" @click="getCode">
|
||||
{{ counting ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<up-icon name="lock" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.password" class="input-field" :type="showPassword ? 'text' : 'password'"
|
||||
placeholder="请输入新密码" />
|
||||
<view class="password-toggle" @click="togglePassword">
|
||||
<up-icon :name="showPassword ? 'eye' : 'eye-off'" size="18" color="#999"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<up-icon name="lock" size="20" color="#999"></up-icon>
|
||||
<input v-model="passwordForm.confirmPassword" class="input-field" :type="showConfirmPassword ? 'text' : 'password'"
|
||||
placeholder="请确认新密码" />
|
||||
<view class="password-toggle" @click="toggleConfirmPassword">
|
||||
<up-icon :name="showConfirmPassword ? 'eye' : 'eye-off'" size="18" color="#999"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="confirm-btn" @click="confirmChange">
|
||||
确认修改
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { LoginService } from "@/Service/api/LoginService"
|
||||
|
||||
|
||||
const counting = ref(false);
|
||||
const countdown = ref(60);
|
||||
const showPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
|
||||
const passwordForm = ref({
|
||||
phone: '',
|
||||
code: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
});
|
||||
|
||||
const togglePassword = () => {
|
||||
showPassword.value = !showPassword.value;
|
||||
};
|
||||
|
||||
const toggleConfirmPassword = () => {
|
||||
showConfirmPassword.value = !showConfirmPassword.value;
|
||||
};
|
||||
|
||||
const getCode = () => {
|
||||
if (!passwordForm.value.phone) {
|
||||
Service.Msg('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(passwordForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
counting.value = true;
|
||||
LoginService.SendSms(passwordForm.value.phone, 'ForgetPwd').then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('验证码已发送!');
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
counting.value = false;
|
||||
countdown.value = 60;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const confirmChange = () => {
|
||||
if (!passwordForm.value.phone) {
|
||||
Service.Msg('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(passwordForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordForm.value.code) {
|
||||
Service.Msg('请输入验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordForm.value.password) {
|
||||
Service.Msg('请输入新密码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordForm.value.password.length < 6) {
|
||||
Service.Msg('密码长度不能少于6位');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passwordForm.value.confirmPassword) {
|
||||
Service.Msg('请确认新密码');
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordForm.value.password !== passwordForm.value.confirmPassword) {
|
||||
Service.Msg('两次密码输入不一致');
|
||||
return;
|
||||
}
|
||||
|
||||
Service.LoadIng('修改中...');
|
||||
let data={
|
||||
phone:passwordForm.value.phone,
|
||||
code:passwordForm.value.code,
|
||||
pwd:passwordForm.value.password,
|
||||
npwd:passwordForm.value.confirmPassword
|
||||
}
|
||||
|
||||
LoginService.ForgetPwd(data).then(res=>{
|
||||
if(res.code==0){
|
||||
Service.LoadClose();
|
||||
Service.Msg('密码修改成功!');
|
||||
Service.GoPageBack()
|
||||
}else{
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.change-password-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.change-password-container {
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.1);
|
||||
}
|
||||
|
||||
.change-password-header {
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.change-password-form {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 28rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
margin-left: 20rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
padding: 0 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 30rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
295
src/pages/login/forgotPassword.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<view class="forgot-password-page">
|
||||
<view class="forgot-password-container">
|
||||
<view class="forgot-password-header">
|
||||
<!-- <image :src="Service.GetIconImg('/static/logo/logo.png')" class="logo"></image> -->
|
||||
<text class="app-title">忘记密码</text>
|
||||
<text class="app-subtitle">请验证您的身份</text>
|
||||
</view>
|
||||
|
||||
<view class="forgot-password-form">
|
||||
<view class="input-item">
|
||||
<u-icon name="phone" size="20" color="#999"></u-icon>
|
||||
<input v-model="formData.phone" class="input-field" type="number" maxlength="11"
|
||||
placeholder="请输入手机号" />
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<u-icon name="error-circle" size="20" color="#999"></u-icon>
|
||||
<input v-model="formData.code" class="input-field code-input" type="number" maxlength="6"
|
||||
placeholder="请输入验证码" />
|
||||
<button class="code-btn" :disabled="counting" @click="getCode">
|
||||
{{ counting ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<u-icon name="lock" size="20" color="#999"></u-icon>
|
||||
<input v-model="formData.newPassword" class="input-field" type="password"
|
||||
:password="showNewPassword" placeholder="请设置新密码" />
|
||||
<u-icon :name="showNewPassword ? 'eye-off' : 'eye'" size="20" color="#999"
|
||||
@click="toggleNewPassword"></u-icon>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<u-icon name="lock" size="20" color="#999"></u-icon>
|
||||
<input v-model="formData.confirmPassword" class="input-field" type="password"
|
||||
:password="showConfirmPassword" placeholder="请确认新密码" />
|
||||
<u-icon :name="showConfirmPassword ? 'eye-off' : 'eye'" size="20" color="#999"
|
||||
@click="toggleConfirmPassword"></u-icon>
|
||||
</view>
|
||||
|
||||
<button class="submit-btn" @click="handleSubmit">
|
||||
确认修改
|
||||
</button>
|
||||
|
||||
<view class="forgot-password-footer">
|
||||
<text class="footer-text">想起密码了?</text>
|
||||
<text class="footer-link" @click="goToLogin">返回登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { LoginService } from "@/Service/api/LoginService"
|
||||
|
||||
|
||||
const showNewPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
const counting = ref(false);
|
||||
const countdown = ref(60);
|
||||
|
||||
const formData = ref({
|
||||
phone: '',
|
||||
code: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
});
|
||||
|
||||
const toggleNewPassword = () => {
|
||||
showNewPassword.value = !showNewPassword.value;
|
||||
};
|
||||
|
||||
const toggleConfirmPassword = () => {
|
||||
showConfirmPassword.value = !showConfirmPassword.value;
|
||||
};
|
||||
|
||||
const getCode = () => {
|
||||
if (!formData.value.phone) {
|
||||
Service.Msg('请输入手机号!');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(formData.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号!');
|
||||
return;
|
||||
}
|
||||
|
||||
counting.value = true;
|
||||
LoginService.SendSms(formData.value.phone,'ForgetPwd').then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('验证码已发送!');
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
counting.value = false;
|
||||
countdown.value = 60;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (!formData.value.phone) {
|
||||
Service.Msg('请输入手机号!');
|
||||
return;
|
||||
}
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(formData.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号!');
|
||||
return;
|
||||
}
|
||||
if (!formData.value.code) {
|
||||
Service.Msg('请输入验证码!');
|
||||
return;
|
||||
}
|
||||
if (!formData.value.newPassword) {
|
||||
Service.Msg('请设置新密码!');
|
||||
return;
|
||||
}
|
||||
if (formData.value.newPassword.length < 6) {
|
||||
Service.Msg('密码长度不能少于6位!');
|
||||
return;
|
||||
}
|
||||
if (!formData.value.confirmPassword) {
|
||||
Service.Msg('请确认新密码!');
|
||||
return;
|
||||
}
|
||||
if (formData.value.newPassword !== formData.value.confirmPassword) {
|
||||
Service.Msg('两次输入的密码不一致!');
|
||||
return;
|
||||
}
|
||||
|
||||
Service.LoadIng('修改中...');
|
||||
|
||||
let data={
|
||||
phone:formData.value.phone,
|
||||
code:formData.value.code,
|
||||
pwd:formData.value.newPassword,
|
||||
npwd:formData.value.confirmPassword
|
||||
}
|
||||
|
||||
LoginService.ForgetPwd(data).then(res=>{
|
||||
if(res.code==0){
|
||||
Service.LoadClose();
|
||||
Service.Msg('密码修改成功!');
|
||||
Service.GoPageDelse('/pages/login/login');
|
||||
}else{
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const goToLogin = () => {
|
||||
Service.GoPageBack();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.forgot-password-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.forgot-password-container {
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.1);
|
||||
}
|
||||
|
||||
.forgot-password-header {
|
||||
text-align: center;
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.app-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.forgot-password-form {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 28rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
margin-left: 20rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
padding: 0 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 30rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
font-size: 28rpx;
|
||||
color: #F97316;
|
||||
font-weight: 600;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
336
src/pages/login/login.vue
Normal file
@@ -0,0 +1,336 @@
|
||||
<template>
|
||||
<view class="login-page">
|
||||
<view class="login-container">
|
||||
<view class="login-header">
|
||||
<!-- <image :src="Service.GetIconImg('/static/logo/logo.png')" class="logo"></image> -->
|
||||
<view class="app-title">欢迎登录</view>
|
||||
</view>
|
||||
|
||||
<view class="login-tabs">
|
||||
<view class="tab-item" :class="{ active: currentTab === 0 }" @click="switchTab(0)">
|
||||
账户密码登录
|
||||
</view>
|
||||
<view class="tab-item" :class="{ active: currentTab === 1 }" @click="switchTab(1)">
|
||||
手机号登录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-form">
|
||||
<view v-if="currentTab === 0" class="form-section">
|
||||
<view class="input-item">
|
||||
<u-icon name="account" size="20" color="#999"></u-icon>
|
||||
<input v-model="passwordForm.username" class="input-field" type="text" placeholder="请输入手机号" />
|
||||
</view>
|
||||
<view class="input-item">
|
||||
<u-icon name="lock" size="20" color="#999"></u-icon>
|
||||
<input v-model="passwordForm.password" class="input-field"
|
||||
:type="!showPassword?'password':'text'" placeholder="请输入密码" />
|
||||
<u-icon :name="!showPassword ? 'eye-off' : 'eye'" size="20" color="#999"
|
||||
@click="togglePassword"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="currentTab === 1" class="form-section">
|
||||
<view class="input-item">
|
||||
<u-icon name="phone" size="20" color="#999"></u-icon>
|
||||
<input v-model="phoneForm.phone" class="input-field" type="number" maxlength="11"
|
||||
placeholder="请输入手机号" />
|
||||
</view>
|
||||
<view class="input-item">
|
||||
<u-icon name="error-circle" size="20" color="#999"></u-icon>
|
||||
<input v-model="phoneForm.code" class="input-field code-input" type="number" maxlength="6"
|
||||
placeholder="请输入验证码" />
|
||||
<button class="code-btn" :disabled="counting" @click="getCode">
|
||||
{{ counting ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<button class="login-btn" @click="handleLogin">
|
||||
登 录
|
||||
</button>
|
||||
|
||||
<view class="login-footer">
|
||||
<text class="footer-text" @click="goToRegister">注册账户</text>
|
||||
<text class="footer-text" @click="goToForgotPassword">忘记密码?</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { LoginService } from "@/Service/api/LoginService"
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
|
||||
const currentTab = ref(0);
|
||||
const showPassword = ref(false);
|
||||
const counting = ref(false);
|
||||
const countdown = ref(60);
|
||||
|
||||
const passwordForm = ref({
|
||||
username: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
const phoneForm = ref({
|
||||
phone: '',
|
||||
code: ''
|
||||
});
|
||||
|
||||
|
||||
onLoad((data:any) => {
|
||||
console.log('onload',data);
|
||||
|
||||
})
|
||||
|
||||
const switchTab = (index : number) => {
|
||||
currentTab.value = index;
|
||||
};
|
||||
|
||||
const togglePassword = () => {
|
||||
showPassword.value = !showPassword.value;
|
||||
};
|
||||
|
||||
const getCode = () => {
|
||||
if (!phoneForm.value.phone) {
|
||||
Service.Msg('请输入手机号!');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(phoneForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号!');
|
||||
return;
|
||||
}
|
||||
|
||||
counting.value = true;
|
||||
|
||||
|
||||
|
||||
LoginService.SendSms(phoneForm.value.phone, 'Login').then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('验证码已发送!');
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
counting.value = false;
|
||||
countdown.value = 60;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleLogin = () => {
|
||||
if (currentTab.value === 0) {
|
||||
if (!passwordForm.value.username) {
|
||||
Service.Msg('请输入手机号!');
|
||||
return;
|
||||
}
|
||||
if (!passwordForm.value.password) {
|
||||
Service.Msg('请输入密码!');
|
||||
return;
|
||||
}
|
||||
|
||||
Service.LoadIng('登录中...');
|
||||
LoginService.Login(passwordForm.value.username, passwordForm.value.password).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.LoadClose();
|
||||
Service.Msg('登录成功!');
|
||||
Service.SetUserToken(res.data.token)
|
||||
Service.GoPageTab('/pages/index/index')
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
if (!phoneForm.value.phone) {
|
||||
Service.Msg('请输入手机号!');
|
||||
return;
|
||||
}
|
||||
if (!phoneForm.value.code) {
|
||||
Service.Msg('请输入验证码!');
|
||||
return;
|
||||
}
|
||||
|
||||
Service.LoadIng('登录中...');
|
||||
LoginService.PhoneLogin(phoneForm.value.phone, phoneForm.value.code).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.LoadClose();
|
||||
Service.Msg('登录成功!');
|
||||
// Service.SetUserToken(res.data.token)
|
||||
let local = 'http://pt.pccsh.cn/#/pages/index/index';
|
||||
window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + 'wxf892fef3811b3ded' + "&redirect_uri=" + local + "&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const goToRegister = () => {
|
||||
Service.GoPage('/pages/login/register');
|
||||
};
|
||||
|
||||
const goToForgotPassword = () => {
|
||||
Service.GoPage('/pages/login/forgotPassword');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.1);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.login-tabs {
|
||||
display: flex;
|
||||
margin-bottom: 50rpx;
|
||||
border-bottom: 2rpx solid #F3F4F6;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 24rpx 0;
|
||||
font-size: 30rpx;
|
||||
color: #6B7280;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #F97316;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -2rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 80rpx;
|
||||
height: 4rpx;
|
||||
background: linear-gradient(90deg, #F97316, #FB923C);
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
.form-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
margin-left: 20rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
|
||||
&:disabled {
|
||||
background: #D1D5DB;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
padding: 0 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 40rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 26rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
</style>
|
||||
323
src/pages/login/register.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<template>
|
||||
<view class="register-page">
|
||||
<view class="register-container">
|
||||
<view class="register-header">
|
||||
<!-- <image :src="Service.GetIconImg('/static/logo/logo.png')" class="logo"></image> -->
|
||||
<text class="app-title">用户注册</text>
|
||||
<text class="app-subtitle">创建您的账户</text>
|
||||
</view>
|
||||
|
||||
<view class="register-form">
|
||||
<view class="input-item">
|
||||
<u-icon name="phone" size="20" color="#999"></u-icon>
|
||||
<input v-model="registerForm.phone" class="input-field" type="number" maxlength="11"
|
||||
placeholder="请输入手机号" />
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<u-icon name="error-circle" size="20" color="#999"></u-icon>
|
||||
<input v-model="registerForm.code" class="input-field code-input" type="number" maxlength="6"
|
||||
placeholder="请输入验证码" />
|
||||
<button class="code-btn" :disabled="counting" @click="getCode">
|
||||
{{ counting ? `${countdown}s` : '获取验证码' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="input-item">
|
||||
<u-icon name="lock" size="20" color="#999"></u-icon>
|
||||
<input v-model="registerForm.password" class="input-field" placeholder="请设置密码" />
|
||||
</view>
|
||||
<view class="input-item">
|
||||
<u-icon name="man-add" size="20" color="#999"></u-icon>
|
||||
<input v-model="registerForm.id" :disabled='isInput' type="number" class="input-field" placeholder="推荐人手机号可为空" />
|
||||
</view>
|
||||
<view class="input-item">
|
||||
<u-icon name="man-add" size="20" color="#999"></u-icon>
|
||||
<input v-model="registerForm.payPwd" maxlength="6" type="number" class="input-field"
|
||||
placeholder="请设置支付密码" />
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<button class="register-btn" @click="handleRegister">
|
||||
立即注册
|
||||
</button>
|
||||
|
||||
<view class="register-footer">
|
||||
<text class="footer-text">已有账号?</text>
|
||||
<text class="footer-link" @click="goToLogin">立即登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { LoginService } from "@/Service/api/LoginService"
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
const showPassword = ref(false);
|
||||
|
||||
const counting = ref(false);
|
||||
const countdown = ref(60);
|
||||
|
||||
let visible = ref(false)
|
||||
let isInput=ref(false)
|
||||
const registerForm = ref({
|
||||
phone: '',
|
||||
code: '',
|
||||
password: '',
|
||||
id: '',
|
||||
payPwd: ''
|
||||
});
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
registerForm.value.id = new URLSearchParams(location.hash.split('?')[1] || '').get('par') ?? '';
|
||||
if(registerForm.value.id){
|
||||
isInput.value=true
|
||||
}else{
|
||||
isInput.value=false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
const togglePassword = () => {
|
||||
showPassword.value = !showPassword.value;
|
||||
};
|
||||
|
||||
const getCode = () => {
|
||||
if (!registerForm.value.phone) {
|
||||
Service.Msg('请输入手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(registerForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
counting.value = true;
|
||||
LoginService.SendSms(registerForm.value.phone, 'Register').then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('验证码已发送!');
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
clearInterval(timer);
|
||||
counting.value = false;
|
||||
countdown.value = 60;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
||||
const handleRegister = () => {
|
||||
if (!registerForm.value.phone) {
|
||||
Service.Msg('请输入手机号!');
|
||||
return;
|
||||
}
|
||||
const phoneReg = /^1[3-9]\d{9}$/;
|
||||
if (!phoneReg.test(registerForm.value.phone)) {
|
||||
Service.Msg('请输入正确的手机号!');
|
||||
return;
|
||||
}
|
||||
if (!registerForm.value.code) {
|
||||
Service.Msg('请输入验证码!');
|
||||
return;
|
||||
}
|
||||
if (!registerForm.value.password) {
|
||||
Service.Msg('请设置密码!');
|
||||
return;
|
||||
}
|
||||
if (!registerForm.value.payPwd) {
|
||||
Service.Msg('请设置密码!');
|
||||
return;
|
||||
}
|
||||
if (registerForm.value.password.length < 6) {
|
||||
Service.Msg('密码长度不能少于6位!');
|
||||
return;
|
||||
}
|
||||
if (registerForm.value.payPwd.length < 6) {
|
||||
Service.Msg('支付密码长度不能少于6!');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Service.LoadIng('注册中...');
|
||||
let data = {
|
||||
phone: registerForm.value.phone,
|
||||
code: registerForm.value.code,
|
||||
pwd: registerForm.value.password,
|
||||
npwd: registerForm.value.password,
|
||||
remPhone: registerForm.value.id,
|
||||
payPwd: registerForm.value.payPwd
|
||||
}
|
||||
LoginService.Register(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.LoadClose();
|
||||
Service.Msg('注册成功');
|
||||
Service.SetUserToken(res.data.token)
|
||||
Service.GoPageTab('/pages/index/index');
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
const goToLogin = () => {
|
||||
Service.GoPageDelse('/pages/login/login')
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.register-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.register-container {
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.1);
|
||||
}
|
||||
|
||||
.register-header {
|
||||
text-align: center;
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.app-title {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.app-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.register-form {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 28rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.area-display {
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.area-display:empty::before {
|
||||
content: '请选择所在地区';
|
||||
color: #9CA3AF;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
margin-left: 20rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 26rpx;
|
||||
border-radius: 12rpx;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
width: 100%;
|
||||
padding: 0 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
margin-top: 30rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.register-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
font-size: 28rpx;
|
||||
color: #F97316;
|
||||
font-weight: 600;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
</style>
|
||||
361
src/pages/userFunc/Groupsharing.vue
Normal file
@@ -0,0 +1,361 @@
|
||||
<template>
|
||||
<view class="group-goods-page">
|
||||
<!-- 页面顶部 -->
|
||||
<view class="page-header">
|
||||
<image :src="Service.GetIconImg('/static/goods/pingtuan.png')" mode="widthFix" style="width: 100%; border-radius: 20rpx;" alt="" />
|
||||
</view>
|
||||
|
||||
<!-- 拼单商品列表 -->
|
||||
<view class="goods-waterfall" v-if="goodsList.length > 0">
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in leftList" :key="index" @click="goGroupDetail(item)">
|
||||
<view class="goods-img-wrap">
|
||||
<image class="goods-img" :src="item.goodsImg" mode="widthFix"></image>
|
||||
<view class="group-badge">拼团</view>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.goodsName }}</view>
|
||||
<view class="goods-price-row">
|
||||
<view class="price-wrap">
|
||||
<text class="group-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
<text class="original-price">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
<view class="sold-count">已售 {{ item.sale }}</view>
|
||||
</view>
|
||||
<view class="go-group-btn" @click.stop="goGroupDetail(item)">
|
||||
<text class="btn-text">去拼单</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in rightList" :key="index" @click="goGroupDetail(item)">
|
||||
<view class="goods-img-wrap">
|
||||
<image class="goods-img" :src="item.goodsImg" mode="widthFix"></image>
|
||||
<view class="group-badge">拼团</view>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<view class="goods-name">{{ item.goodsName }}</view>
|
||||
<view class="goods-price-row">
|
||||
<view class="price-wrap">
|
||||
<text class="group-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
<text class="original-price">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
<view class="sold-count">已售 {{ item.sale }}</view>
|
||||
</view>
|
||||
<view class="go-group-btn" @click.stop="goGroupDetail(item)">
|
||||
<text class="btn-text">去拼单</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else class="empty-state">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/null.png')" class="empty-image" mode="aspectFit"></image>
|
||||
<text class="empty-text">暂无拼单商品</text>
|
||||
<text class="empty-desc">稍后再来看看吧</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { GoodsService } from '@/Service/api/GoodsService'
|
||||
|
||||
|
||||
// 商品列表
|
||||
const goodsList = ref<Array<any>>([]);
|
||||
|
||||
// 左右两列
|
||||
const leftList = computed(() => {
|
||||
return goodsList.value.filter((_, i) => i % 2 === 0);
|
||||
});
|
||||
|
||||
const rightList = computed(() => {
|
||||
return goodsList.value.filter((_, i) => i % 2 === 1);
|
||||
});
|
||||
let page = ref(1) // 当前页码
|
||||
let status = ref('nomore');
|
||||
|
||||
onLoad(() => {
|
||||
uni.setNavigationBarTitle({
|
||||
title: '拼单商品'
|
||||
});
|
||||
getData()
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
goodsList.value = []
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
getList()
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载商品列表数据
|
||||
* 包含轮播图、优选商品和商品列表
|
||||
*/
|
||||
const getList = () => {
|
||||
// 防止重复加载
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
|
||||
// 调用API获取商品数据
|
||||
GoodsService.GetTuanGoodsList( page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
goodsList.value=[...goodsList.value,...res.data.list]
|
||||
// 更新加载状态
|
||||
status.value = res.data.goodsList.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 查看拼单详情
|
||||
const goGroupDetail = (item : any) => {
|
||||
// TODO: 替换为实际的拼单商品详情路径
|
||||
Service.GoPage('/pages/webView/goodsDetail?goodsId=' + item.goodsId);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.group-goods-page {
|
||||
min-height: 100vh;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 页面顶部 */
|
||||
.page-header {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
background: linear-gradient(135deg, #FF6A00, #FF7F00);
|
||||
border-radius: 20rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 106, 0, 0.2);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 40rpx;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
/* 瀑布流 */
|
||||
.goods-waterfall {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.waterfall-col {
|
||||
width: 48.5%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.goods-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.goods-card:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.goods-img-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.group-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(135deg, #FF6A00, #FF4500);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
padding: 6rpx 14rpx;
|
||||
border-radius: 0 0 16rpx 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
padding: 18rpx;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.goods-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10rpx;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
font-size: 20rpx;
|
||||
color: #FF6A00;
|
||||
border: 1rpx solid rgba(255, 106, 0, 0.3);
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 8rpx;
|
||||
background: rgba(255, 106, 0, 0.05);
|
||||
}
|
||||
|
||||
/* 拼团进度 */
|
||||
.group-progress {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 12rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 6rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #FF3D3D, #FF6A00);
|
||||
border-radius: 6rpx;
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
margin-top: 6rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 价格 */
|
||||
.goods-price-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
.price-wrap {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.group-price {
|
||||
font-size: 24rpx;
|
||||
color: #FF3D3D;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.price-num {
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.sold-count {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 去拼单按钮 */
|
||||
.go-group-btn {
|
||||
margin-top: 16rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 32rpx;
|
||||
background: linear-gradient(to right, #FF6A00, #FF7F00);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(255, 106, 0, 0.25);
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 26rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 160rpx 40rpx;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 260rpx;
|
||||
height: 260rpx;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 24rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
</style>
|
||||
339
src/pages/userFunc/addAddress.vue
Normal file
@@ -0,0 +1,339 @@
|
||||
<template>
|
||||
<view class="add-address-page">
|
||||
<view class="page-header">
|
||||
<text class="page-title">{{ !addressId ? '新增地址' : '修改地址' }}</text>
|
||||
<text class="page-desc">请准确填写您的收货信息</text>
|
||||
</view>
|
||||
|
||||
<view class="form-card">
|
||||
<up-form labelPosition="top" :model="userData" labelWidth='200rpx' ref="form1">
|
||||
<up-form-item label="收货人" prop="userInfo.name" ref="item1">
|
||||
<view class="input-wrap">
|
||||
<up-icon name="account" color="#FF6A00" size="18"></up-icon>
|
||||
<up-input v-model="userData.name" placeholder="请输入收货人姓名" border="none"
|
||||
:customStyle="{ flex: 1, paddingLeft: '12rpx' }"></up-input>
|
||||
</view>
|
||||
</up-form-item>
|
||||
<up-form-item label="手机号" prop="userInfo.name" ref="item1">
|
||||
<view class="input-wrap">
|
||||
<up-icon name="phone" color="#FF6A00" size="18"></up-icon>
|
||||
<up-input v-model="userData.phone" placeholder="请输入手机号码" border="none"
|
||||
:customStyle="{ flex: 1, paddingLeft: '12rpx' }" type="number" maxlength="11"></up-input>
|
||||
</view>
|
||||
</up-form-item>
|
||||
<up-form-item label="所在地区" prop="userInfo.name" ref="item1">
|
||||
<view class="region-picker" @click="visible=true">
|
||||
<view class="region-left">
|
||||
<up-icon name="map" color="#FF6A00" size="18"></up-icon>
|
||||
<text v-if="!userData.procince" class="region-placeholder">请选择省/市/区</text>
|
||||
<text v-else class="region-value">{{ userData.procince }} {{ userData.city }} {{ userData.region }}</text>
|
||||
</view>
|
||||
<up-icon name="arrow-right" color="#C0C4CC" size="16"></up-icon>
|
||||
</view>
|
||||
</up-form-item>
|
||||
<up-form-item label="详细地址" prop="userInfo.name" ref="item1">
|
||||
<view class="textarea-wrap">
|
||||
<up-textarea v-model="userData.detail" placeholder="请输入街道、楼栋、门牌号等详细地址" border="none"
|
||||
:customStyle="{ flex: 1, paddingLeft: '12rpx', backgroundColor: 'transparent' }"></up-textarea>
|
||||
</view>
|
||||
</up-form-item>
|
||||
</up-form>
|
||||
</view>
|
||||
|
||||
<view class="switch-card">
|
||||
<view class="switch-row">
|
||||
<view class="switch-left">
|
||||
<view class="switch-icon-wrap">
|
||||
<up-icon name="checkmark-circle" color="#FF6A00" size="18"></up-icon>
|
||||
</view>
|
||||
<view class="switch-text-wrap">
|
||||
<text class="switch-label">设为默认地址</text>
|
||||
<text class="switch-desc">下次下单时会默认使用此地址</text>
|
||||
</view>
|
||||
</view>
|
||||
<up-switch v-model="userData.switchValue" size='20' activeColor='var(--nav-mian)' @change="switchChange">
|
||||
</up-switch>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bottom-bar">
|
||||
<up-button @click="save()" color='var(--nav-mian)' shape='circle'
|
||||
:customStyle="{ width: '90%', margin: '0 auto', height: '88rpx', fontSize: '32rpx', fontWeight: '600' }"
|
||||
:text="!addressId?'保存地址':'修改地址'"></up-button>
|
||||
</view>
|
||||
|
||||
<piaoyi-cityPicker column="3" default-value="411002" :mask-close-able="true" @confirm="areaConfirm"
|
||||
@cancel="areaCancel" :visible="visible" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Service } from "@/Service/Service"
|
||||
import { AddressService } from "@/Service/api/AddressService"
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
|
||||
let visible = ref(false)
|
||||
|
||||
let userData = ref({
|
||||
name: '',
|
||||
detail: '',
|
||||
switchValue: false,
|
||||
phone: '',
|
||||
procince: '',
|
||||
city: '',
|
||||
region: '',
|
||||
})
|
||||
|
||||
let addressId = ref('')
|
||||
onLoad((data: any) => {
|
||||
if (data.addressId) {
|
||||
addressId.value = data.addressId
|
||||
getData()
|
||||
}
|
||||
})
|
||||
|
||||
const getData = () => {
|
||||
AddressService.GetUserAddressInfo(addressId.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
userData.value.name = res.data.addressInfo.realName
|
||||
userData.value.phone = res.data.addressInfo.phone
|
||||
userData.value.procince = res.data.addressInfo.province
|
||||
userData.value.city = res.data.addressInfo.city
|
||||
userData.value.region = res.data.addressInfo.region
|
||||
userData.value.detail = res.data.addressInfo.address
|
||||
userData.value.switchValue = res.data.addressInfo.isDefault === 1 ? true : false
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
if (!userData.value.name) {
|
||||
Service.Msg('请填写姓名!')
|
||||
return
|
||||
}
|
||||
if (!userData.value.phone) {
|
||||
Service.Msg('请填写手机号!')
|
||||
return
|
||||
}
|
||||
if (!userData.value.procince) {
|
||||
Service.Msg('请选择地址!')
|
||||
return
|
||||
}
|
||||
if (!userData.value.detail) {
|
||||
Service.Msg('请输入详细地址!')
|
||||
return
|
||||
}
|
||||
|
||||
let data = {
|
||||
addressId: addressId.value,
|
||||
name: userData.value.name,
|
||||
phone: userData.value.phone,
|
||||
province: userData.value.procince,
|
||||
city: userData.value.city,
|
||||
region: userData.value.region,
|
||||
address: userData.value.detail,
|
||||
isDefault: userData.value.switchValue ? 1 : 0
|
||||
}
|
||||
|
||||
AddressService.AddUserAddress(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg(!addressId.value ? '添加成功!' : '修改成功!')
|
||||
setTimeout(() => {
|
||||
Service.GoPageBack()
|
||||
}, 1000)
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const switchChange = (e: any) => {
|
||||
userData.value.switchValue = e
|
||||
}
|
||||
|
||||
const areaConfirm = (e: any) => {
|
||||
visible.value = false
|
||||
userData.value.procince = e.provinceName
|
||||
userData.value.city = e.cityName
|
||||
userData.value.region = e.areaName
|
||||
}
|
||||
|
||||
const areaCancel = (e: any) => {
|
||||
visible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
.add-address-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding-bottom: 160rpx;
|
||||
}
|
||||
|
||||
/* ===== 页面头部 ===== */
|
||||
.page-header {
|
||||
padding: 40rpx 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
margin-top: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ===== 表单卡片 ===== */
|
||||
.form-card {
|
||||
background: #fff;
|
||||
margin: 0 30rpx;
|
||||
border-radius: 28rpx;
|
||||
padding: 24rpx 24rpx 10rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* ===== 输入框 ===== */
|
||||
.input-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F8F9FA;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
margin-top: 12rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.input-wrap:focus-within {
|
||||
border-color: var(--nav-mian);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.region-picker {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #F8F9FA;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-top: 12rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.region-picker:active {
|
||||
background-color: #F0F0F0;
|
||||
}
|
||||
|
||||
.region-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.region-placeholder {
|
||||
color: #C0C4CC;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.region-value {
|
||||
color: #1F2937;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.textarea-wrap {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
background-color: #F8F9FA;
|
||||
border-radius: 16rpx;
|
||||
padding: 0rpx 24rpx;
|
||||
margin-top: 12rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.textarea-wrap:focus-within {
|
||||
border-color: var(--nav-mian);
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* ===== 默认地址开关 ===== */
|
||||
.switch-card {
|
||||
background: #fff;
|
||||
margin: 24rpx 30rpx 0;
|
||||
border-radius: 28rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.switch-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.switch-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.switch-icon-wrap {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 16rpx;
|
||||
background: linear-gradient(135deg, #FFF7ED, #FFEDD5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.switch-desc {
|
||||
font-size: 22rpx;
|
||||
color: #9CA3AF;
|
||||
margin-top: 4rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ===== 底部按钮 ===== */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 20rpx 0;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
box-shadow: 0 -4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 99;
|
||||
}
|
||||
</style>
|
||||
221
src/pages/userFunc/addressList.vue
Normal file
@@ -0,0 +1,221 @@
|
||||
<template>
|
||||
<view class="address-manager">
|
||||
<!-- 地址列表 -->
|
||||
<view class="address-list">
|
||||
<!-- 地址项1 - 默认地址 -->
|
||||
<view v-for="(item,index) in addresses" :key="index" class="address-item">
|
||||
<view class="address-content">
|
||||
<view class="name-phone">
|
||||
<text class="name">{{item.realName }}</text>
|
||||
<text class="phone">{{phone(item.phone) }}</text>
|
||||
<view v-if="item.isDefault==1" class="default-tag">默认</view>
|
||||
</view>
|
||||
<text class="address">{{ item.province+item.city+item.region+item.address }}</text>
|
||||
</view>
|
||||
<view class="" style="display: flex;align-items: center; gap: 15rpx; ">
|
||||
<up-icon @click="Service.GoPage('/pages/userFunc/addAddress?addressId='+item.addressId)"
|
||||
name="edit-pen" size="20"></up-icon>
|
||||
<up-icon @click="dele(item.addressId)" name="trash" size="20"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<up-loadmore :status="status" />
|
||||
<view class="" style="width: 100%; height: 120rpx;">
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 新增地址按钮 -->
|
||||
<view class=""
|
||||
style=" background-color: #fff; border-top: 1rpx solid #e2e2e2; position: fixed; left: 0; bottom: 0; width: 100vw; height: 110rpx; ">
|
||||
<up-button @click="Service.GoPage('/pages/userFunc/addAddress')" color='var(--nav-mian)' shape='circle'
|
||||
style="width: 90%; margin: 15rpx auto 0; " text="新增地址"></up-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { Service } from "@/Service/Service"
|
||||
import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { AddressService } from "@/Service/api/AddressService"
|
||||
|
||||
// 地址数据
|
||||
const addresses = ref<Array<any>>([
|
||||
]);
|
||||
|
||||
let page = ref(1)
|
||||
let status = ref('nomore')
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
getData()
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
const getData = () => {
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
addresses.value = []
|
||||
getList()
|
||||
}
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
AddressService.GetUserAddressList(page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
addresses.value = [...addresses.value, ...res.data.list]
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const phone = (data : any) => {
|
||||
return String(data).slice(0, 3) + '****' + String(data).slice(-4)
|
||||
}
|
||||
|
||||
const dele = (id : any) => {
|
||||
uni.showModal({
|
||||
title: '提示', // 对话框标题
|
||||
content: '是否删除该地址', // 显示的内容
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
AddressService.DelUserAddress(id).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('删除成功!')
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
} else if (res.cancel) {
|
||||
console.log('用户点击取消');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.address-manager {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
/* 提示信息样式 */
|
||||
.tip-bar {
|
||||
background-color: #fff8e6;
|
||||
padding: 20rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.info-icon {
|
||||
color: #ff9c07;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 26rpx;
|
||||
color: #ff9c07;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 地址列表样式 */
|
||||
.address-list {
|
||||
|
||||
.address-item {
|
||||
border-radius: 15rpx;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
margin-top: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.address-content {
|
||||
flex: 1;
|
||||
|
||||
.name-phone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
|
||||
.name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.phone {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.default-tag {
|
||||
background-color: #ff6b00;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 2rpx 15rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.address {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
color: #999;
|
||||
margin-top: 5rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 新增地址按钮样式 */
|
||||
.add-btn {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 100rpx;
|
||||
background-color: #ff6b00;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
257
src/pages/userFunc/collect.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
|
||||
<view class="content">
|
||||
<view v-if="productFavorites.length > 0" class="goods-waterfall">
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in leftList" :key="index"
|
||||
@click="Service.GoPage('/pages/webView/goodsDetail?goodsId='+item.goodsId)">
|
||||
<view class="goods-img-wrap">
|
||||
<image :src="Service.GetMateUrlByImg(item.goodsImg)" mode="widthFix" class="goods-img"></image>
|
||||
<view class="favorite-icon-wrap" @click.stop="cancleCollect(item.goodsId)">
|
||||
<up-icon name="heart-fill" color="#FF6A00" size="16"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<text class="goods-name">{{ item.goodsName }}</text>
|
||||
<view class="goods-price-row">
|
||||
<text class="goods-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
<text class="goods-old">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in rightList" :key="index"
|
||||
@click="Service.GoPage('/pages/webView/goodsDetail?goodsId='+item.goodsId)">
|
||||
<view class="goods-img-wrap">
|
||||
<image :src="Service.GetMateUrlByImg(item.goodsImg)" mode="widthFix" class="goods-img"></image>
|
||||
<view class="favorite-icon-wrap" @click.stop="cancleCollect(item.goodsId)">
|
||||
<up-icon name="heart-fill" color="#FF6A00" size="16"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<text class="goods-name">{{ item.goodsName }}</text>
|
||||
<view class="goods-price-row">
|
||||
<text class="goods-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
<text class="goods-old">¥{{ item.maxPrice }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-state">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/null.png')" class="empty-image"></image>
|
||||
<text class="empty-text">暂无收藏商品</text>
|
||||
</view>
|
||||
|
||||
<up-loadmore v-if="productFavorites.length > 0" style="margin-top: 12rpx;" :status="status" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { Service } from "@/Service/Service"
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
|
||||
import { CollectService } from "@/Service/api/CollectService"
|
||||
|
||||
let status = ref('nomore')
|
||||
let page = ref(1)
|
||||
let productFavorites = ref<Array<any>>([])
|
||||
|
||||
let leftList = computed(() => {
|
||||
return productFavorites.value.filter((_, i) => i % 2 === 0)
|
||||
})
|
||||
|
||||
let rightList = computed(() => {
|
||||
return productFavorites.value.filter((_, i) => i % 2 === 1)
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
getData()
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
const getData = () => {
|
||||
productFavorites.value = []
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
getList()
|
||||
}
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
CollectService.GetUserCollectList(page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
productFavorites.value = [...productFavorites.value, ...res.data.list]
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const cancleCollect = (id: any) => {
|
||||
CollectService.AddUserCollect({ goodsId: id }).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg('已取消收藏')
|
||||
getData()
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page {
|
||||
padding: 20rpx 0;
|
||||
min-height: 100vh;
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 30rpx 30rpx 20rpx;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.page-count {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 24rpx 30rpx;
|
||||
}
|
||||
|
||||
/* ===== 瀑布流 ===== */
|
||||
.goods-waterfall {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.waterfall-col {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.goods-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.goods-card:active {
|
||||
transform: scale(0.97);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.goods-img-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.favorite-icon-wrap {
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
right: 12rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
padding: 14rpx 16rpx 18rpx;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 26rpx;
|
||||
color: #1F2937;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.goods-price-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-weight: 700;
|
||||
font-size: 28rpx;
|
||||
color: #FF6A00;
|
||||
}
|
||||
|
||||
.price-num {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.goods-old {
|
||||
font-size: 22rpx;
|
||||
color: #C0C4CC;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* ===== 空状态 ===== */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 160rpx 20rpx;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #C0C4CC;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
322
src/pages/userFunc/comment.vue
Normal file
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 评价分类标签 -->
|
||||
<view class="evaluation-tabs">
|
||||
<up-tabs :list="tabList" :current="currentTab" @click="handleTabChange" :scrollable='false'
|
||||
lineColor='var(--nav-mian)' lineWidth='50'
|
||||
:activeStyle="{color: 'var(--nav-mian)',fontWeight: 'bold',transform: 'scale(1.05)'}"
|
||||
:inactiveStyle="{color: '#606266',transform: 'scale(1)'}"
|
||||
itemStyle=" padding-left: 30rpx; padding-right: 30rpx; height: 34px;"></up-tabs>
|
||||
</view>
|
||||
|
||||
<!-- 评价列表 -->
|
||||
<view class="evaluation-list">
|
||||
<view class="evaluation-item" v-for="(item, index) in evaluations" :key="index">
|
||||
<!-- 商品信息 -->
|
||||
|
||||
<view class="" style=" display: flex;align-items: start;">
|
||||
<view class="goods-info">
|
||||
<image :src="Service.GetMateUrlByImg(item.goodsImg)" mode="aspectFill" class="goods-image">
|
||||
</image>
|
||||
</view>
|
||||
|
||||
<!-- 评价内容 -->
|
||||
<view class="evaluation-content">
|
||||
<view class="" style="display: flex; align-items: start;">
|
||||
<view class="goods-details">
|
||||
<text class="goods-name">{{ item.goodsName }}</text>
|
||||
</view>
|
||||
<u-icon name="trash" size="40rpx" color="#CCCCCC" class="delete-icon"
|
||||
@click="deleteEvaluation(item)"></u-icon>
|
||||
</view>
|
||||
<!-- 星级评分 -->
|
||||
<view class="" style="display: flex; align-items: baseline; gap: 10rpx;">
|
||||
<up-rate v-model="item.source" style="margin-top: 15rpx;" gutter='2' size="28rpx"
|
||||
:allowHalf='true' active-color="#FF8C00" :readonly="true"></up-rate>
|
||||
<view class="" style="color:#FF8C00 ; font-size: 24rpx; font-weight: bold;">
|
||||
{{Number(item.source).toFixed(1) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="">
|
||||
<!-- 评价文字 -->
|
||||
<view class="" style="padding: 20rpx; background-color: #f1f1f1; border-radius: 20rpx;">
|
||||
<text class="evaluation-text">{{ item.remark }}</text>
|
||||
|
||||
<!-- 评价图片 -->
|
||||
<view class="evaluation-images" v-if="item.imgs">
|
||||
<image :src="Service.GetMateUrlByImg(img)" mode="aspectFill" class="evaluation-img"
|
||||
v-for="(img, imgIndex) in JSON.parse(item.imgs)" :key="imgIndex"></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评价信息 -->
|
||||
<view class="evaluation-meta" >
|
||||
<view class="">
|
||||
|
||||
</view>
|
||||
<text class="evaluation-date">{{ Service.formatDate(item.addTime, 1) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="evaluations.length === 0">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/null.png')" />
|
||||
<text class="empty-text">暂无评价</text>
|
||||
</view>
|
||||
|
||||
<up-loadmore v-if="evaluations.length !== 0" :status="status" />
|
||||
<view class="" style="width: 100%; height: 80rpx;">
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service'
|
||||
import { UserService } from "@/Service/api/UserService"
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
|
||||
|
||||
// 评价分类标签
|
||||
const tabList = ref([
|
||||
{ name: '全部' },
|
||||
{ name: '好评' },
|
||||
{ name: '中评' },
|
||||
{ name: '差评' }
|
||||
]);
|
||||
|
||||
// 当前选中的标签
|
||||
const currentTab = ref(0);
|
||||
let page = ref(1);
|
||||
let status = ref('nomore');
|
||||
let viewCount = ref(0);
|
||||
|
||||
// 评价列表数据
|
||||
const evaluations = ref<Array<any>>([]);
|
||||
|
||||
onLoad(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
// 处理标签切换
|
||||
const handleTabChange = (index : any) => {
|
||||
currentTab.value = index.index;
|
||||
getData();
|
||||
};
|
||||
|
||||
const getData = () => {
|
||||
evaluations.value = [];
|
||||
page.value = 1;
|
||||
status.value = 'loadmore';
|
||||
getList();
|
||||
};
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return;
|
||||
}
|
||||
status.value = 'loading';
|
||||
|
||||
let source = 0;
|
||||
if (currentTab.value == 1) source = 1;
|
||||
else if (currentTab.value == 2) source = 2;
|
||||
else if (currentTab.value == 3) source = 3;
|
||||
|
||||
UserService.GetUserEvaluateList(source, page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
evaluations.value = [...evaluations.value, ...res.data.list];
|
||||
if (res.data.viewCount) {
|
||||
viewCount.value = res.data.viewCount;
|
||||
}
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore';
|
||||
page.value++;
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 删除评价
|
||||
const deleteEvaluation = (item : any) => {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这条评价吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
UserService.DelUserEvaluate(item.geId).then(res=>{
|
||||
if (res.code == 0) {
|
||||
Service.Msg(res.data)
|
||||
getData()
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 浏览统计样式 */
|
||||
.view-stats {
|
||||
padding: 20rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.stats-text {
|
||||
font-size: 26rpx;
|
||||
color: #4B5563;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
/* 评价分类标签样式 */
|
||||
.evaluation-tabs {
|
||||
background-color: #ffffff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 评价列表样式 */
|
||||
.evaluation-list {
|
||||
padding: 0 30rpx 20rpx;
|
||||
}
|
||||
|
||||
.evaluation-item {
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
}
|
||||
|
||||
/* 商品信息样式 */
|
||||
.goods-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.goods-image {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.goods-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
margin-bottom: 10rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
/* 评价内容样式 */
|
||||
.evaluation-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.evaluation-text {
|
||||
font-size: 26rpx;
|
||||
color: #333333;
|
||||
line-height: 48rpx;
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* 评价图片样式 */
|
||||
.evaluation-images {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.evaluation-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
/* 评价信息样式 */
|
||||
.evaluation-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
|
||||
.evaluation-date {
|
||||
font-size: 26rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.evaluation-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hot-tag {
|
||||
font-size: 26rpx;
|
||||
color: #FF6600;
|
||||
margin-right: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
font-size: 26rpx;
|
||||
color: #FF6600;
|
||||
}
|
||||
|
||||
/* 空状态样式 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 120rpx;
|
||||
|
||||
img {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
359
src/pages/userFunc/goComment.vue
Normal file
@@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<view class="review-page">
|
||||
|
||||
|
||||
<!-- 商品信息 -->
|
||||
<view v-for="(item,index) in orderDetail" :key="index" class="product-info">
|
||||
<image :src="Service.GetMateUrlByImg(item.goodsImg)" class="product-image"></image>
|
||||
<view class="product-details">
|
||||
<text class="product-name">{{ item.goodsName }}</text>
|
||||
<view class="" style=" display: flex; align-items: baseline; gap: 12rpx;">
|
||||
规格:
|
||||
<text v-for="(ruleItem,ruleIndex) in item.rule.split('-')" :key="ruleIndex"
|
||||
class="product-attributes">{{ruleItem }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评分区域 -->
|
||||
<view class="rating-section">
|
||||
<view class="rating-item">
|
||||
<text class="rating-label">评价星级</text>
|
||||
<view class="" style="display: flex; align-items: end;">
|
||||
<up-rate count="5" v-model="source" activeColor='#F97316'></up-rate>
|
||||
<text style="color: #F97316; font-weight: 600; margin-left: 15rpx;">{{source.toFixed(1)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评价心得 -->
|
||||
<view class="review-content-section">
|
||||
<text class="section-title">评价心得</text>
|
||||
<up-textarea v-model="reviewContent" placeholder="请输入内容" count></up-textarea>
|
||||
</view>
|
||||
|
||||
<!-- 上传图片 -->
|
||||
<view class="upload-section">
|
||||
<text class="section-title">上传图片(选填,最多9张)</text>
|
||||
<view class="upload-container">
|
||||
<view @click="upLoadImg()" class="upload-item add-item">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/comment/add.png')" class="add-icon"></image>
|
||||
</view>
|
||||
|
||||
<view class="" v-for="(imgItem,imgIndex) in commentImgs" :key="imgIndex" style="position: relative;">
|
||||
<image :src="Service.GetMateUrlByImg(imgItem)" class="upload-img"></image>
|
||||
<image @click="closeImg(imgItem)" :src="Service.GetpayImg('/static/userFunc/comment/close.png')"
|
||||
style="position: absolute; top: -6rpx; right: -10rpx; width: 20rpx; height: 20rpx; " alt="" />
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="action-buttons">
|
||||
<up-button @click="submitReview" :customStyle='{borderRadius: "12rpx"}' color='#FF6A00' text="发布评价"></up-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import { OrderService } from '@/Service/api/OrderService';
|
||||
|
||||
let commentImgs = ref([])
|
||||
// 评价内容
|
||||
let reviewContent = ref('');
|
||||
let source = ref(5)
|
||||
let orderDetail = ref<Array<any>>([])
|
||||
let orderId = ref('')
|
||||
onLoad((data : any) => {
|
||||
orderId.value = data.orderId
|
||||
getData()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
|
||||
})
|
||||
|
||||
|
||||
const getData = () => {
|
||||
OrderService.GetOrderInfo(orderId.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
orderDetail.value = res.data.orderDetail
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const upLoadImg = () => {
|
||||
uni.chooseImage({
|
||||
count: 9, // 最多选择1张图片
|
||||
sizeType: ['original', 'compressed'], // 支持原图和压缩图
|
||||
sourceType: ['album', 'camera'], // 可从相册选择或使用相机拍照
|
||||
success: function (res) {
|
||||
let path = res.tempFiles[0].path
|
||||
Service.uploadH5(path, 'Avatar', data => {
|
||||
commentImgs.value.push(JSON.parse(data).data.path)
|
||||
})
|
||||
},
|
||||
fail: function (err) {
|
||||
console.error('选择失败:', err.errMsg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const closeImg = (item:never) => {
|
||||
let index = commentImgs.value.indexOf(item)
|
||||
commentImgs.value.splice(index, 1)
|
||||
console.log(commentImgs.value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 提交评价
|
||||
const submitReview = () => {
|
||||
if( reviewContent.value=='' ){
|
||||
Service.Msg('评论不能为空!')
|
||||
return
|
||||
}
|
||||
let data={
|
||||
orderId:orderId.value,
|
||||
source:source.value,
|
||||
sign:reviewContent.value,
|
||||
img:JSON.stringify(commentImgs.value)
|
||||
}
|
||||
OrderService.EvaluateOrder(data).then(res=>{
|
||||
if(res.code==0){
|
||||
Service.Msg(res.data)
|
||||
setTimeout(()=>{
|
||||
Service.GoPageBack()
|
||||
},1000)
|
||||
}else{
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 页面基础样式 */
|
||||
.review-page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 商品信息 */
|
||||
.product-info {
|
||||
display: flex;
|
||||
padding: 30rpx;
|
||||
background-color: #fff;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.product-details {
|
||||
margin-left: 24rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 40rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.product-attributes {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
/* 评分区域 */
|
||||
.rating-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.rating-item {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.rating-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.rating-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
width: 160rpx;
|
||||
}
|
||||
|
||||
.stars-container {
|
||||
display: flex;
|
||||
margin-left: 20rpx;
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.rating-value {
|
||||
font-size: 28rpx;
|
||||
color: #ff7d00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 评价标签 */
|
||||
.tags-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #4B5563;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.tags-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
background-color: #f5f5f5;
|
||||
padding: 10rpx 30rpx;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.tag-item.active {
|
||||
background-color: #FF6A00;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 评价心得 */
|
||||
.review-content-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.review-input {
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
border: 1rpx solid #eee;
|
||||
border-radius: 16rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.character-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
/* 上传图片 */
|
||||
.upload-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.upload-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.upload-item {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.add-item {
|
||||
background-color: #F9FAFB;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1rpx dashed #D1D5DB;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.upload-img {
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
/* 匿名评价 */
|
||||
.anonymous-section {
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.anonymous-checkbox {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.anonymous-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
padding: 20rpx 30rpx;
|
||||
gap: 20rpx;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
</style>
|
||||
192
src/pages/userFunc/goodsSearch.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 搜索框区域 -->
|
||||
<view class="search-container">
|
||||
<up-search @search="searchData() " v-model="searchText" :background="'#f5f5f5'" :showAction='false'
|
||||
:input-style="{ fontSize: '28rpx', height: '70rpx' }" </up-search>
|
||||
</view>
|
||||
|
||||
<!-- 搜索历史 -->
|
||||
<view class="search-history">
|
||||
<view class="history-header">
|
||||
<text class="history-title">搜索历史</text>
|
||||
<text class="clear-btn" @click="clearHistory">清除</text>
|
||||
</view>
|
||||
<view v-if="searchHistory.length>0" class="history-tags">
|
||||
<view @click="Service.GoPage('/pages/userFunc/searchList?name=' + item)" class="history-tags-item"
|
||||
v-for="(item, index) in searchHistory" :key="index">
|
||||
{{item}}
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="history-tags" style="font-size:28rpx ; color: #4B5563;">
|
||||
暂无搜索历史
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
// 搜索文本
|
||||
const searchText = ref('');
|
||||
|
||||
// 搜索历史
|
||||
const searchHistory = ref<Array<any>>([]);
|
||||
|
||||
onLoad(() => {
|
||||
|
||||
})
|
||||
onShow(() => {
|
||||
if (Service.GetStorageCache('search')) {
|
||||
searchHistory.value = Service.GetStorageCache('search')
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// 清除搜索历史
|
||||
const clearHistory = () => {
|
||||
searchHistory.value = [];
|
||||
Service.SetStorageCache('search', searchHistory.value)
|
||||
};
|
||||
|
||||
const searchData = () => {
|
||||
let data = Service.GetStorageCache('search')
|
||||
if (searchHistory.value.includes(searchText.value)) {
|
||||
console.log(1111);
|
||||
Service.GoPage('/pages/userFunc/searchList?name=' + searchText.value)
|
||||
return
|
||||
}
|
||||
if (data.length > 6) {
|
||||
searchHistory.value.shift()
|
||||
searchHistory.value.push(searchText.value)
|
||||
Service.SetStorageCache('search', searchHistory.value)
|
||||
} else {
|
||||
searchHistory.value.push(searchText.value)
|
||||
Service.SetStorageCache('search', searchHistory.value)
|
||||
}
|
||||
Service.GoPage('/pages/userFunc/searchList?name=' + searchText.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page {
|
||||
background-color: #fff;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 搜索框样式 */
|
||||
.search-container {
|
||||
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.search-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
/* 搜索历史样式 */
|
||||
.search-history {
|
||||
padding: 20rpx 24rpx 20rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #e2e2e2;
|
||||
border-top: 1rpx solid #e2e2e2;
|
||||
}
|
||||
|
||||
.history-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.history-title {
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.history-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.history-tags-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8rpx 30rpx;
|
||||
border-radius: 30rpx;
|
||||
margin-right: 15rpx;
|
||||
background-color: #F3F4F6;
|
||||
color: #4B5563;
|
||||
margin-top: 15rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 推荐区域样式 */
|
||||
.recommendation-section {
|
||||
background-color: #ffffff;
|
||||
padding: 30rpx 24rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
/* 商品网格样式 */
|
||||
.goods-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 24rpx;
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 10rpx 2rpx #e2e2e2;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 100%;
|
||||
height: 360rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
flex: 1;
|
||||
padding: 15rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 40rpx;
|
||||
margin-bottom: 10rpx;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
padding: 15rpx;
|
||||
font-size: 32rpx;
|
||||
color: #ff4d4f;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
61
src/pages/userFunc/notice.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<view class="box">
|
||||
<view class="" >
|
||||
<view class="" style="font-weight: bold; font-size: 34rpx;" >
|
||||
{{ data.title}}
|
||||
</view>
|
||||
<view class="" style="font-size: 30rpx;" >
|
||||
{{ data.fubiaoti}}
|
||||
</view>
|
||||
<view class="" style="margin-top: 4rpx; font-size: 24rpx; color: #9f9f9f;" >
|
||||
{{ Service.formatDate(data.addTime,1) }}
|
||||
</view>
|
||||
</view>
|
||||
<rich-text :nodes="notes" style="margin-top: 20rpx;" ></rich-text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from '@/Service/Service';
|
||||
import { AnnouncementService } from "@/Service/api/AnnouncementService"
|
||||
|
||||
let data=ref<any>({})
|
||||
let notes = ref('')
|
||||
|
||||
let id=ref('')
|
||||
|
||||
onLoad((data:any) => {
|
||||
id.value=data.id
|
||||
getData( )
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
});
|
||||
|
||||
|
||||
const getData=()=>{
|
||||
AnnouncementService.GetAnnouncementInfo(id.value).then(res=>{
|
||||
notes.value=res.data.info.content
|
||||
data.value=res.data.info
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page{
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.box {
|
||||
padding: 20rpx;
|
||||
:deep(img) {
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
282
src/pages/userFunc/noticeList.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<view class="notice-list-page">
|
||||
<view class="notice-list-section">
|
||||
<view v-if="topAnnoun.length>0" class="section-wrap">
|
||||
<view class="section-header">
|
||||
<view class="section-title-wrap">
|
||||
<view class="section-dot"></view>
|
||||
<text class="section-title">置顶公告</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="notice-list">
|
||||
<view v-for="(item, index) in topAnnoun" :key="index" class="notice-card" @click="goToNoticeDetail(item)">
|
||||
<view class="notice-image-wrapper">
|
||||
<image :src="Service.GetMateUrlByImg(item.img)" class="notice-image" mode="aspectFill"></image>
|
||||
<view class="notice-tag">置顶</view>
|
||||
</view>
|
||||
<view class="notice-info">
|
||||
<text class="notice-title">{{ item.title }}</text>
|
||||
<text class="notice-subtitle">{{ item.fubiaoti }}</text>
|
||||
<view class="notice-meta">
|
||||
<text class="notice-time">{{ Service.formatDate(item.addTime, 1) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-wrap">
|
||||
<view class="section-header">
|
||||
<view class="section-title-wrap">
|
||||
<view class="section-dot"></view>
|
||||
<text class="section-title">公告列表</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="noticeList.length > 0" class="notice-list">
|
||||
<view v-for="(item, index) in noticeList" :key="index" class="notice-card" @click="goToNoticeDetail(item)">
|
||||
<view class="notice-image-wrapper">
|
||||
<image :src="Service.GetMateUrlByImg(item.img)" class="notice-image" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view class="notice-info">
|
||||
<text class="notice-title">{{ item.title }}</text>
|
||||
<text class="notice-subtitle">{{ item.fubiaoti }}</text>
|
||||
<view class="notice-meta">
|
||||
<text class="notice-time">{{ Service.formatDate(item.addTime, 1) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-state">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/null.png')" class="empty-image"></image>
|
||||
<text class="empty-text">暂无公告</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="load-more" v-if="noticeList.length > 0">
|
||||
<up-loadmore :status="status" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from '@/Service/Service';
|
||||
import { AnnouncementService } from "@/Service/api/AnnouncementService"
|
||||
|
||||
let noticeList = ref<any[]>([]);
|
||||
let topAnnoun = ref<Array<any>>([])
|
||||
let page = ref(1)
|
||||
let status = ref('nomore')
|
||||
|
||||
onLoad(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
});
|
||||
onReachBottom(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
const getData = () => {
|
||||
noticeList.value = []
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
getList()
|
||||
};
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
AnnouncementService.GetAnnouncementList(page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (page.value == 1) {
|
||||
topAnnoun.value = res.data.topAnnoun
|
||||
}
|
||||
|
||||
noticeList.value = [...noticeList.value, ...res.data.list]
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goToNoticeDetail = (item: any) => {
|
||||
Service.GoPage('/pages/userFunc/notice?id=' + item.announcementId)
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.notice-list-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 40rpx;
|
||||
background: #F8F9FA;
|
||||
}
|
||||
|
||||
/* ===== 列表区域 ===== */
|
||||
.notice-list-section {
|
||||
padding: 30rpx 30rpx 0;
|
||||
}
|
||||
|
||||
.section-wrap {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 0 0 24rpx;
|
||||
}
|
||||
|
||||
.section-title-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
.section-dot {
|
||||
width: 8rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 4rpx;
|
||||
background: linear-gradient(180deg, var(--nav-mian), var(--nav-vice));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
/* ===== 公告卡片 ===== */
|
||||
.notice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.notice-card:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 1rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.notice-image-wrapper {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
flex-shrink: 0;
|
||||
margin-right: 20rpx;
|
||||
position: relative;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, rgba(255, 106, 0, 0.06), rgba(255, 127, 0, 0.04));
|
||||
}
|
||||
|
||||
.notice-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.notice-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 18rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #FF6A00, #FF4500);
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 10rpx 0 20rpx 0;
|
||||
line-height: 1.2;
|
||||
z-index: 1;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.notice-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
padding: 6rpx 0;
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1F2937;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.notice-subtitle {
|
||||
font-size: 24rpx;
|
||||
color: #9CA3AF;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.notice-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.notice-time {
|
||||
font-size: 22rpx;
|
||||
color: #C0C4CC;
|
||||
}
|
||||
|
||||
/* ===== 空状态 ===== */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #C0C4CC;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ===== 加载更多 ===== */
|
||||
.load-more {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
485
src/pages/userFunc/promote.vue
Normal file
@@ -0,0 +1,485 @@
|
||||
<template>
|
||||
<view class="promotion-page">
|
||||
|
||||
|
||||
<!-- <view class="stats-section">
|
||||
<view class="stats-card">
|
||||
<view class="stats-decoration-left"></view>
|
||||
<view class="stats-decoration-right"></view>
|
||||
<view class="stats-content">
|
||||
<view class="stat-item" @click="openPromotePopup">
|
||||
<up-icon name="share" size='24' color='#FF6A00' bold='true'></up-icon>
|
||||
<text class="stat-label">推广码</text>
|
||||
</view>
|
||||
<view class="stat-divider"></view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-value">{{ totalMembers }}</text>
|
||||
<text class="stat-label">推广</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view class="content-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">推广会员列表</text>
|
||||
<text class="section-count">共 {{ totalMembers }} 人</text>
|
||||
</view>
|
||||
|
||||
<view v-if="membersList.length > 0" class="members-list">
|
||||
<view v-for="(item, index) in membersList" :key="index" class="member-card">
|
||||
<view class="member-card-bg"></view>
|
||||
<view class="member-content">
|
||||
<view class="member-avatar-wrapper">
|
||||
<image :src="Service.GetMateUrlByImg(item.avatar)" class="member-avatar" mode="aspectFill">
|
||||
</image>
|
||||
</view>
|
||||
<view class="member-info">
|
||||
<view class="member-name-row">
|
||||
<text class="member-name">{{ item.nick || '神秘用户' }}</text>
|
||||
<view class="join-tag">
|
||||
<text class="join-text">{{ item.conLev>0?'铁粉':'粉丝' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="" style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<view class="member-phone">
|
||||
<text class="phone-text">{{ item.phone || '暂无手机号' }}</text>
|
||||
</view>
|
||||
<text class="time-value">{{ Service.formatDate(item.addTime, 1) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-state">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/null.png')" class="empty-image"></image>
|
||||
<text class="empty-text">暂无推广人员</text>
|
||||
<text class="empty-desc">邀请好友加入,获得积分奖励</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="membersList.length > 0 && status !== 'nomore'" class="load-more">
|
||||
<up-loadmore :status="status"></up-loadmore>
|
||||
</view>
|
||||
|
||||
<up-popup :show="showPromotePopup" mode="center" round="16" @close="closePromotePopup"
|
||||
:closeOnClickOverlay="true">
|
||||
<view class="popup-content" style="border-radius: 20rpx;">
|
||||
<view class="popup-close" @click="closePromotePopup">
|
||||
<up-icon name="close" size="18" color="#999"></up-icon>
|
||||
</view>
|
||||
<view class="" style="display: flex; align-items: center; gap: 10rpx; ">
|
||||
|
||||
<view class="qrcode-wrapper">
|
||||
<up-qrcode cid="ex1" :size="180" :val="qrcodeUrl"></up-qrcode>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<text class="popup-tip">扫描二维码即可加入</text>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad, onReachBottom } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from '@/Service/Service';
|
||||
import { UserService } from "@/Service/api/UserService"
|
||||
|
||||
let page = ref(1);
|
||||
let status = ref('nomore');
|
||||
let totalMembers = ref(0);
|
||||
|
||||
let showPromotePopup = ref(false);
|
||||
let qrcodeUrl = ref('');
|
||||
let membersList = ref<any[]>([]);
|
||||
|
||||
onLoad(() => {
|
||||
getData();
|
||||
geyUrl()
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
});
|
||||
onReachBottom(() => {
|
||||
getMembersList();
|
||||
});
|
||||
|
||||
|
||||
|
||||
const geyUrl = () => {
|
||||
// UserService.GetUserRemUrl().then(res => {
|
||||
// if (res.code == 0) {
|
||||
// qrcodeUrl.value = res.data.url
|
||||
// } else {
|
||||
// Service.Msg(res.msg)
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
const getData = () => {
|
||||
membersList.value = []
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
getMembersList()
|
||||
}
|
||||
const getMembersList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return;
|
||||
}
|
||||
status.value = 'loading';
|
||||
UserService.GetUserRemList(page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (page.value == 1) {
|
||||
totalMembers.value = res.data.total.value
|
||||
}
|
||||
|
||||
membersList.value = [...membersList.value, ...res.data.remList]
|
||||
status.value = res.data.remList.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
};
|
||||
|
||||
const openPromotePopup = () => {
|
||||
showPromotePopup.value = true;
|
||||
};
|
||||
|
||||
const closePromotePopup = () => {
|
||||
showPromotePopup.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background: linear-gradient(180deg, #FFF7ED 0%, #FFF5EB 50%, #F5F5F5 100%);
|
||||
}
|
||||
|
||||
|
||||
.stats-section {
|
||||
padding: 20rpx 30rpx 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.stats-card {
|
||||
background: linear-gradient(145deg, #FFFFFF 0%, #FFF9F2 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
box-shadow: 0 8rpx 30rpx rgba(255, 106, 0, 0.12);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stats-decoration-left {
|
||||
position: absolute;
|
||||
top: -40rpx;
|
||||
left: -40rpx;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(255, 106, 0, 0.08), rgba(255, 127, 0, 0.03));
|
||||
}
|
||||
|
||||
.stats-decoration-right {
|
||||
position: absolute;
|
||||
bottom: -30rpx;
|
||||
right: -30rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, rgba(255, 127, 0, 0.06), rgba(255, 106, 0, 0.02));
|
||||
}
|
||||
|
||||
.stats-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #FF6A00;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stat-divider {
|
||||
width: 1rpx;
|
||||
height: 60rpx;
|
||||
background: rgba(255, 106, 0, 0.2);
|
||||
}
|
||||
|
||||
.content-section {
|
||||
padding: 30rpx 20rpx 0;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-count {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.members-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.member-card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.member-card-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
background: linear-gradient(135deg, rgba(255, 106, 0, 0.05), rgba(255, 127, 0, 0.02));
|
||||
border-radius: 50%;
|
||||
transform: translate(30%, -30%);
|
||||
}
|
||||
|
||||
.member-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 28rpx 30rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.member-avatar-wrapper {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.avatar-border {
|
||||
position: absolute;
|
||||
top: -3rpx;
|
||||
left: -3rpx;
|
||||
right: -3rpx;
|
||||
bottom: -3rpx;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid linear-gradient(135deg, #FF6A00, #FF7F00);
|
||||
background: linear-gradient(135deg, #FF6900, #FF7F00);
|
||||
z-index: -1;
|
||||
padding: 3rpx;
|
||||
}
|
||||
|
||||
.member-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.member-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
}
|
||||
|
||||
.join-tag {
|
||||
background: linear-gradient(135deg, #FF6A00, #FF7F00);
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.join-text {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.member-phone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.phone-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.member-time {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.time-label {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.time-value {
|
||||
font-size: 24rpx;
|
||||
color: #FF6A00;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.reward-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
padding: 16rpx 20rpx;
|
||||
background: linear-gradient(145deg, #FFF4E6 0%, #FFF9F2 100%);
|
||||
border-radius: 12rpx;
|
||||
margin-left: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.reward-label {
|
||||
font-size: 20rpx;
|
||||
color: #FF9800;
|
||||
}
|
||||
|
||||
.reward-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: #FF6A00;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80rpx 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
margin-bottom: 30rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
width: 560rpx;
|
||||
padding: 50rpx 40rpx;
|
||||
background: linear-gradient(180deg, #FFFFFF 0%, #FFF9F2 100%);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
right: 20rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
;
|
||||
}
|
||||
|
||||
.popup-text {
|
||||
font-size: 30rpx;
|
||||
color: #FF6A00;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.qrcode-wrapper {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 20rpx;
|
||||
box-shadow: 0 8rpx 30rpx rgba(255, 106, 0, 0.15);
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.qrcode-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.popup-tip {
|
||||
font-size: 24rpx;
|
||||
color: #FF7F00;
|
||||
}
|
||||
</style>
|
||||
235
src/pages/userFunc/searchList.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<view class="search-page">
|
||||
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<view class="search-content">
|
||||
<view v-if="leftList.length > 0" class="goods-waterfall">
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in leftList" :key="index"
|
||||
@click="Service.GoPage('/pages/webView/goodsDetail?goodsId='+item.goodsId)">
|
||||
<view class="goods-img-wrap">
|
||||
<image :src="Service.GetMateUrlByImg(item.goodsImg)" mode="widthFix" class="goods-img"></image>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<text class="goods-name">{{ item.goodsName }}</text>
|
||||
<view class="goods-price-row">
|
||||
<text class="goods-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="waterfall-col">
|
||||
<view class="goods-card" v-for="(item, index) in rightList" :key="index"
|
||||
@click="Service.GoPage('/pages/webView/goodsDetail?goodsId='+item.goodsId)">
|
||||
<view class="goods-img-wrap">
|
||||
<image :src="Service.GetMateUrlByImg(item.goodsImg)" mode="widthFix" class="goods-img"></image>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<text class="goods-name">{{ item.goodsName }}</text>
|
||||
<view class="goods-price-row">
|
||||
<text class="goods-price">¥<text class="price-num">{{ item.price }}</text></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-state">
|
||||
<image :src="Service.GetpayImg('/static/userFunc/null.png')" class="empty-image"></image>
|
||||
<text class="empty-text">暂无相关商品</text>
|
||||
</view>
|
||||
|
||||
<view class="load-more" v-if="leftList.length > 0">
|
||||
<up-loadmore :status="status" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { Service } from "@/Service/Service"
|
||||
import { ref } from "vue";
|
||||
import { GoodsService } from '@/Service/api/GoodsService';
|
||||
|
||||
let page = ref(1)
|
||||
let status = ref('nomore')
|
||||
let leftList = ref<Array<any>>([])
|
||||
let rightList = ref<Array<any>>([])
|
||||
let search = ref('')
|
||||
|
||||
onLoad((data: any) => {
|
||||
search.value = data.name
|
||||
|
||||
uni.setNavigationBarTitle({
|
||||
title: search.value
|
||||
})
|
||||
getData()
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
leftList.value = []
|
||||
rightList.value = []
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
getList()
|
||||
}
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
GoodsService.GetSerchGoodsList(search.value, page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
res.data.list.map((item: any, index: any) => {
|
||||
if (index % 2 == 1) {
|
||||
rightList.value.push(item)
|
||||
} else {
|
||||
leftList.value.push(item)
|
||||
}
|
||||
})
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
.search-page {
|
||||
min-height: 100vh;
|
||||
background: #F8F9FA;
|
||||
}
|
||||
|
||||
/* ===== 搜索栏 ===== */
|
||||
.search-bar {
|
||||
background: #fff;
|
||||
padding: 16rpx 20rpx;
|
||||
padding-top: calc(16rpx + env(safe-area-inset-top));
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.search-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.search-input-wrap {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ===== 搜索结果 ===== */
|
||||
.search-content {
|
||||
padding: 20rpx 20rpx 0;
|
||||
}
|
||||
|
||||
/* ===== 商品瀑布流 ===== */
|
||||
.goods-waterfall {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.waterfall-col {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.goods-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.goods-card:active {
|
||||
transform: scale(0.97);
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.goods-img-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
padding: 16rpx 16rpx 20rpx;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 26rpx;
|
||||
color: #1F2937;
|
||||
line-height: 1.5;
|
||||
font-weight: 500;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.goods-price-row {
|
||||
margin-top: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-weight: 700;
|
||||
font-size: 28rpx;
|
||||
color: #FF6A00;
|
||||
}
|
||||
|
||||
.price-num {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* ===== 空状态 ===== */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 160rpx 20rpx;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
margin-bottom: 24rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #C0C4CC;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ===== 加载更多 ===== */
|
||||
.load-more {
|
||||
margin-top: 20rpx;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
</style>
|
||||
253
src/pages/userFunc/set.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
|
||||
<!-- 用户信息卡片 -->
|
||||
<view @click="Service.GoPage('/pages/userFunc/setUserData')" class="user-card">
|
||||
<img v-if="userInfo.avatar" :src="Service.GetMateUrlByImg(userInfo.avatar)"
|
||||
style="width: 100rpx; height: 100rpx; border-radius: 50%; border: 2rpx solid #fff; " alt="" />
|
||||
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ userInfo.nick }}</text>
|
||||
<view style="color: #fff; font-size: 24rpx; " >{{ phone(userInfo.phone) }}</view>
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="34rpx" color="#fff"></u-icon>
|
||||
</view>
|
||||
|
||||
<!-- 设置列表 -->
|
||||
<view class="settings-list">
|
||||
<!-- 账户安全 -->
|
||||
<view class="settings-section">
|
||||
<view class="section-title">账户安全</view>
|
||||
<view v-for="(accountItem,accountIndex) in account" :key="accountIndex"
|
||||
@click="Service.GoPage(accountItem.path)" class="section-item">
|
||||
<view class="" style="display: flex; align-items: center;">
|
||||
<view class="icon" style=" ">
|
||||
<img class="icon-img" :src="Service.GetpayImg(accountItem.icon)" alt="" />
|
||||
</view>
|
||||
<text style="margin-left: 14rpx; font-weight: 500; ">{{accountItem.name}}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
<view class=""
|
||||
style=" width: 100%; position: fixed; bottom: 0; left: 0; background-color: #f5f5f5; padding: 20rpx 30rpx; ">
|
||||
<view class="" @click="handleLogout()"
|
||||
style="width: 100%; color: #fff; background-color: #FF6A00; padding: 20rpx ; text-align: center; border-radius: 20rpx; ">
|
||||
退出登录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { UserService } from "@/Service/api/UserService"
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
|
||||
const userInfo = ref({
|
||||
avatar: '',
|
||||
nick: '张小明',
|
||||
phone: '',
|
||||
province: '',
|
||||
city: '',
|
||||
county: ''
|
||||
});
|
||||
|
||||
|
||||
|
||||
onLoad(()=>{
|
||||
getUserInfo();
|
||||
})
|
||||
|
||||
|
||||
const getUserInfo = () => {
|
||||
UserService.GetUserInfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
userInfo.value = res.data.info;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const account = ref([
|
||||
{
|
||||
name: '修改密码',
|
||||
icon: '/static/userFunc/set/change.png',
|
||||
path: '/pages/login/changgePassword'
|
||||
},
|
||||
{
|
||||
name: '修改手机号',
|
||||
icon: '/static/userFunc/set/phone.png',
|
||||
path: '/pages/login/changePhone'
|
||||
},
|
||||
{
|
||||
name: '修改支付密码',
|
||||
icon: '/static/userFunc/set/password.png',
|
||||
path: '/pages/login/changePay'
|
||||
}
|
||||
])
|
||||
const phone=(data:any)=>{
|
||||
return String(data).slice(0, 3) + '****' + String(data).slice(-4)
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
uni.showModal({
|
||||
title: '退出登录',
|
||||
content: '确定要退出当前账号吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
Service.OffUserToken()
|
||||
Service.GoPageDelse('/pages/login/login')
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
|
||||
// 图标
|
||||
.icon {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #FFEDD5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon-img {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
/* 用户信息卡片 */
|
||||
.user-card {
|
||||
margin: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(to right, #F97316, #FB923C);
|
||||
padding: 40rpx 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
background-color: #FFFFFF;
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-left: 30rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.user-level {
|
||||
width: fit-content;
|
||||
font-size: 18rpx;
|
||||
color: #fff;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
padding: 4rpx 20rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
|
||||
/* 设置列表 */
|
||||
.settings-list {
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
overflow: hidden;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.section-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #e2e2e2;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.u-cell {
|
||||
height: 100rpx;
|
||||
font-size: 32rpx;
|
||||
--u-cell-value-color: #999999;
|
||||
--u-cell-title-color: #333333;
|
||||
--u-cell-arrow-size: 40rpx;
|
||||
}
|
||||
|
||||
/* 版本信息 */
|
||||
.version-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 60rpx;
|
||||
|
||||
}
|
||||
|
||||
.version-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.version-number {
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.check-update {
|
||||
font-size: 24rpx;
|
||||
color: #FF6600;
|
||||
background-color: #FFF7ED;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 15rpx;
|
||||
}
|
||||
|
||||
/* 退出登录按钮 */
|
||||
.logout-btn {
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
</style>
|
||||
269
src/pages/userFunc/setuserData.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<view class="set-user-page">
|
||||
<!-- 顶部装饰背景 -->
|
||||
<view class="header-bg">
|
||||
<view class="header-bg-inner"></view>
|
||||
</view>
|
||||
|
||||
<!-- 头像区域 -->
|
||||
<view class="avatar-section" @click="uploadFImg">
|
||||
<view class="avatar-wrapper">
|
||||
<img class="avatar-img" :src="Service.GetMateUrlByImg(info.avatar)" />
|
||||
<!-- 相机图标 -->
|
||||
<view class="avatar-camera">
|
||||
<u-icon name="camera-fill" color="#fff" size="16"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<text class="avatar-tip">点击更换头像</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单卡片 -->
|
||||
<view class="form-card">
|
||||
<u-form labelWidth="90" labelPosition="left" :model="info" ref="form1" :borderBottom="true">
|
||||
<u-form-item label="昵称" prop="info.nick" borderBottom>
|
||||
<u-input inputAlign="right" v-model="info.nick" border="none" placeholder="请输入昵称" clearable
|
||||
:customStyle="{ color: '#333' }"></u-input>
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="手机号" prop="info.phone" borderBottom>
|
||||
<u-input inputAlign="right" disabled disabledColor="transparent" v-model="info.phone" border="none"
|
||||
placeholder="未绑定" :customStyle="{ color: '#999' }"></u-input>
|
||||
</u-form-item>
|
||||
</u-form>
|
||||
</view>
|
||||
|
||||
<!-- 底部保存按钮 -->
|
||||
<view class="footer-safe">
|
||||
<view class="save-btn-wrapper">
|
||||
<u-button type="primary" shape="circle" size="large" color="linear-gradient(90deg, #FF7F00, #FF6A00)"
|
||||
:customStyle="btnStyle" @click="save" text="保存信息"></u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { Service } from "@/Service/Service";
|
||||
import { ref } from "vue";
|
||||
import { UserService } from "@/Service/api/UserService";
|
||||
|
||||
let info = ref({
|
||||
avatar: "",
|
||||
nick: "",
|
||||
phone: "",
|
||||
});
|
||||
|
||||
const btnStyle = {
|
||||
fontWeight: 600,
|
||||
fontSize: "32rpx",
|
||||
boxShadow: "0 8rpx 24rpx rgba(255, 106, 0, 0.35)",
|
||||
height: "96rpx",
|
||||
lineHeight: "96rpx",
|
||||
};
|
||||
|
||||
onLoad(() => { });
|
||||
|
||||
onShow(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
UserService.GetUserInfo().then((res) => {
|
||||
if (res.code == 0) {
|
||||
info.value = res.data.info;
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const uploadFImg = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ["original", "compressed"],
|
||||
sourceType: ["album", "camera"],
|
||||
success: function (res) {
|
||||
let path = res.tempFiles[0].path;
|
||||
Service.uploadH5(path, "Avatar", (data) => {
|
||||
console.log(data);
|
||||
info.value.avatar = JSON.parse(data).data.path;
|
||||
});
|
||||
},
|
||||
fail: function (err) {
|
||||
console.error("选择失败:", err.errMsg);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (info.value.avatar == "") {
|
||||
Service.Msg("请上传头像!");
|
||||
return;
|
||||
}
|
||||
if (info.value.nick == "") {
|
||||
Service.Msg("请输入昵称!");
|
||||
return;
|
||||
}
|
||||
|
||||
UserService.UpdateUser(info.value.avatar, info.value.nick).then((res) => {
|
||||
if (res.code == 0) {
|
||||
Service.Msg("保存成功!");
|
||||
setTimeout(() => {
|
||||
Service.GoPageBack();
|
||||
}, 1000);
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #f6f7f9;
|
||||
}
|
||||
|
||||
.set-user-page {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 160rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 顶部装饰背景 */
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
|
||||
.header-bg-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, #fff5ec 0%, rgba(255, 245, 236, 0) 100%);
|
||||
border-radius: 0 0 50% 50%;
|
||||
transform: scaleX(1.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* 头像区域 */
|
||||
.avatar-section {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 40rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 4rpx solid #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.avatar-empty {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.avatar-placeholder-icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.avatar-camera {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
background: linear-gradient(135deg, #FF7F00, #FF6A00);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 106, 0, 0.3);
|
||||
}
|
||||
|
||||
.avatar-tip {
|
||||
margin-top: 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
/* 表单卡片 */
|
||||
.form-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0 30rpx;
|
||||
padding: 0 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
::v-deep .u-form-item__body__left__content__label {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
::v-deep .u-input__content__field-wrapper__field {
|
||||
font-size: 30rpx !important;
|
||||
}
|
||||
|
||||
::v-deep .u-form-item__body__right__content__slot {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部安全区 + 保存按钮 */
|
||||
.footer-safe {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding: 24rpx 40rpx calc(env(safe-area-inset-bottom) + 24rpx) 40rpx;
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(12rpx);
|
||||
z-index: 10;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.save-btn-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
470
src/pages/userFunc/transfer.vue
Normal file
@@ -0,0 +1,470 @@
|
||||
<template>
|
||||
<view class="">
|
||||
|
||||
|
||||
<view class="transfer-section">
|
||||
<view class="transfer-card">
|
||||
<view class="form-item">
|
||||
<text class="form-label">转账到</text>
|
||||
<view class="type-options">
|
||||
<view class="type-item" :class="{ active: transferType === 1 }" @click="transferType = 1">
|
||||
<view class="type-radio">
|
||||
<view v-if="transferType === 1" class="type-radio-dot"></view>
|
||||
</view>
|
||||
<view class="type-info">
|
||||
<text class="type-name">报单积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="type-item" :class="{ active: transferType === 2 }" @click="transferType = 2">
|
||||
<view class="type-radio">
|
||||
<view v-if="transferType === 2" class="type-radio-dot"></view>
|
||||
</view>
|
||||
<view class="type-info">
|
||||
<text class="type-name">普通积分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="type-item" :class="{ active: transferType === 3 }" @click="transferType = 3">
|
||||
<view class="type-radio">
|
||||
<view v-if="transferType === 3" class="type-radio-dot"></view>
|
||||
</view>
|
||||
<view class="type-info">
|
||||
<text class="type-name">自营积分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item" style="margin-bottom: 10rpx;">
|
||||
<text class="form-label">转账金额</text>
|
||||
<view class="input-wrapper amount-wrapper">
|
||||
<text class="currency-symbol">¥</text>
|
||||
<input v-model="transferForm.amount" class="input-field amount-input" type="text"
|
||||
placeholder="0.00" @input="handleAmountInput" />
|
||||
</view>
|
||||
<view class="amount-hint" style="margin-top: 20rpx;">
|
||||
<text class="hint-text">当前余额:</text>
|
||||
<text class="available-amount">¥{{ accInfo.account }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<view class="bottom-action">
|
||||
<button class="transfer-btn" @click="handleTransfer" :disabled="!canTransfer">
|
||||
立即转账
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<up-popup :show="showPasswordPopup" mode="center" :round="20" :closeOnClickOverlay="false">
|
||||
<view class="password-popup">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">请输入支付密码</text>
|
||||
<view class="popup-close" @click="closePasswordPopup">
|
||||
<up-icon name="close" size="24" color="#999"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="popup-content">
|
||||
<view class="password-desc">转账金额:¥{{ transferForm.amount || '0.00' }}</view>
|
||||
<view class="password-input-wrapper">
|
||||
<view v-for="(item, index) in 6" :key="index" class="password-box"
|
||||
:class="{ active: passwordInput.length === index }">
|
||||
<text v-if="passwordInput[index]" class="password-dot"></text>
|
||||
</view>
|
||||
</view>
|
||||
<input ref="passwordInputRef" v-model="passwordInput" class="hidden-password-input" type="number"
|
||||
maxlength="6" :focus="showPasswordPopup" @input="onPasswordInput" />
|
||||
<view class="forget-password" @click="goToSetPassword">
|
||||
<text class="forget-text">忘记密码?</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { UserService } from '@/Service/api/UserService';
|
||||
|
||||
const showPasswordPopup = ref(false);
|
||||
const passwordInput = ref('');
|
||||
const passwordInputRef = ref<any>(null);
|
||||
|
||||
const transferType = ref(1)
|
||||
const transferForm = ref({
|
||||
amount: ''
|
||||
});
|
||||
|
||||
let accInfo=ref<any>({})
|
||||
const canTransfer = computed(() => {
|
||||
return transferForm.value.amount &&
|
||||
parseFloat(transferForm.value.amount) > 0;
|
||||
});
|
||||
|
||||
onLoad(() => {
|
||||
getAccInfo()
|
||||
});
|
||||
|
||||
|
||||
const getAccInfo = () => {
|
||||
UserService.GetUserAccInfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
accInfo.value = res.data.accInfo;
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleAmountInput = (e : any) => {
|
||||
let value = e.detail.value;
|
||||
|
||||
value = value.replace(/[^\d.]/g, '');
|
||||
|
||||
const parts = value.split('.');
|
||||
if (parts.length > 2) {
|
||||
value = parts[0] + '.' + parts.slice(1).join('');
|
||||
}
|
||||
|
||||
if (parts.length === 2 && parts[1].length > 2) {
|
||||
value = parts[0] + '.' + parts[1].substring(0, 2);
|
||||
}
|
||||
|
||||
transferForm.value.amount = value;
|
||||
};
|
||||
|
||||
const handleTransfer = () => {
|
||||
if (!transferForm.value.amount) {
|
||||
Service.Msg('请输入转账金额');
|
||||
return;
|
||||
}
|
||||
|
||||
if (parseFloat(transferForm.value.amount) <= 0) {
|
||||
Service.Msg('转账金额必须大于0');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
passwordInput.value = '';
|
||||
showPasswordPopup.value = true;
|
||||
|
||||
nextTick(() => {
|
||||
if (passwordInputRef.value) {
|
||||
passwordInputRef.value.focus();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onPasswordInput = () => {
|
||||
if (passwordInput.value.length === 6) {
|
||||
submitTransfer();
|
||||
}
|
||||
};
|
||||
|
||||
const submitTransfer = () => {
|
||||
showPasswordPopup.value = false;
|
||||
Service.LoadIng('转账中...');
|
||||
UserService.TranAccount(Number(transferForm.value.amount), transferType.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.LoadClose();
|
||||
Service.Msg('转账成功!');
|
||||
setTimeout(() => {
|
||||
Service.GoPageBack();
|
||||
}, 1000);
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const closePasswordPopup = () => {
|
||||
showPasswordPopup.value = false;
|
||||
passwordInput.value = '';
|
||||
};
|
||||
|
||||
const goToSetPassword = () => {
|
||||
closePasswordPopup();
|
||||
Service.GoPage('/pages/userFunc/setPaypwd');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background: linear-gradient(180deg, #FFF7ED 0%, #FFF5EB 50%, #F5F5F5 100%);
|
||||
}
|
||||
|
||||
|
||||
.transfer-section {
|
||||
padding: 30rpx 30rpx 0;
|
||||
}
|
||||
|
||||
.transfer-card {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 40rpx 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 40rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #FF6A00;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
&.amount-wrapper {
|
||||
padding: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.currency-symbol {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #FF6A00;
|
||||
margin-right: 10rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
|
||||
&.amount-input {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.type-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.type-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
background: #F9FAFB;
|
||||
border: 2rpx solid transparent;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
border-color: #FF6A00;
|
||||
background: #FFF7ED;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.type-radio {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border: 2rpx solid #ccc;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
.type-item.active & {
|
||||
border-color: #FF6A00;
|
||||
}
|
||||
}
|
||||
|
||||
.type-radio-dot {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: #FF6A00;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.type-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.type-desc {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.amount-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.available-amount {
|
||||
font-size: 24rpx;
|
||||
color: #FF6A00;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bottom-action {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
padding: 20rpx 30rpx;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.transfer-btn {
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #FF6A00 0%, #FF7F00 100%);
|
||||
color: #fff;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 50rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(255, 106, 0, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 106, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.password-popup {
|
||||
width: 600rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.popup-close {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
padding: 40rpx 30rpx 30rpx;
|
||||
}
|
||||
|
||||
.password-desc {
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.password-input-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.password-box {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border: 2rpx solid #E5E7EB;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #F9FAFB;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
border-color: #FF6A00;
|
||||
background: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.password-dot {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: #333;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.hidden-password-input {
|
||||
position: absolute;
|
||||
top: -9999rpx;
|
||||
left: -9999rpx;
|
||||
width: 1rpx;
|
||||
height: 1rpx;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.forget-password {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.forget-text {
|
||||
font-size: 26rpx;
|
||||
color: #FF6A00;
|
||||
}
|
||||
</style>
|
||||
256
src/pages/userFunc/wallet.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<view class="wallet-page">
|
||||
|
||||
|
||||
<!-- 账户列表 -->
|
||||
<view class="accounts-section">
|
||||
<view class="accounts-list">
|
||||
<!-- 余额 -->
|
||||
<view @click="goToDetail('account')" class="account-item">
|
||||
<view class="account-left">
|
||||
<view class="account-icon balance-icon">
|
||||
<image :src="Service.GetIconImg('/static/icon/userFunc/wallet/01.png')" class="account-icon-img" />
|
||||
</view>
|
||||
<view class="account-text">
|
||||
<text class="account-name">余额</text>
|
||||
<text class="account-desc">账户余额</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-right">
|
||||
<text class="account-value">¥{{ accInfo.account || '0.00' }}</text>
|
||||
<up-icon name="arrow-right" color="#C4C4C4" size="16"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 报单商品积分 -->
|
||||
<view @click="goToDetail('customs')" class="account-item">
|
||||
<view class="account-left">
|
||||
<view class="account-icon customs-icon">
|
||||
<image :src="Service.GetIconImg('/static/icon/userFunc/wallet/02.png')" class="account-icon-img" />
|
||||
</view>
|
||||
<view class="account-text">
|
||||
<text class="account-name">报单积分</text>
|
||||
<text class="account-desc">报单商品奖励</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-right">
|
||||
<text class="account-value">{{ accInfo.customs || '0' }}</text>
|
||||
<up-icon name="arrow-right" color="#C4C4C4" size="16"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 普通商品积分 -->
|
||||
<view @click="goToDetail('integral')" class="account-item">
|
||||
<view class="account-left">
|
||||
<view class="account-icon integral-icon">
|
||||
<image :src="Service.GetIconImg('/static/icon/userFunc/wallet/03.png')" class="account-icon-img" />
|
||||
</view>
|
||||
<view class="account-text">
|
||||
<text class="account-name">普通积分</text>
|
||||
<text class="account-desc">普通商品奖励</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-right">
|
||||
<text class="account-value">{{ accInfo.integral || '0' }}</text>
|
||||
<up-icon name="arrow-right" color="#C4C4C4" size="16"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 自营商品积分 -->
|
||||
<view @click="goToDetail('selfIntegral')" class="account-item">
|
||||
<view class="account-left">
|
||||
<view class="account-icon self-icon">
|
||||
<image :src="Service.GetIconImg('/static/icon/userFunc/wallet/04.png')" class="account-icon-img" />
|
||||
</view>
|
||||
<view class="account-text">
|
||||
<text class="account-name">自营积分</text>
|
||||
<text class="account-desc">自营商品奖励</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="account-right">
|
||||
<text class="account-value">{{ accInfo.selfIntegral || '0' }}</text>
|
||||
<up-icon name="arrow-right" color="#C4C4C4" size="16"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { UserService } from '@/Service/api/UserService';
|
||||
|
||||
|
||||
// ==================== 自定义导航栏 ====================
|
||||
let statusBarHeight = ref(uni.getWindowInfo().statusBarHeight || 20)
|
||||
|
||||
const accInfo = ref<any>({});
|
||||
onShow(() => {
|
||||
|
||||
getAccInfo()
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const getAccInfo = () => {
|
||||
UserService.GetUserAccInfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
accInfo.value = res.data.accInfo;
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const goToDetail = (type : string) => {
|
||||
Service.GoPage('/pages/userFunc/walletRecord?type='+type)
|
||||
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wallet-page {
|
||||
background-color: #F8F9FA;
|
||||
}
|
||||
|
||||
/* 账户区域 */
|
||||
.accounts-section {
|
||||
background-color: #fff;
|
||||
margin: 30rpx 24rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.accounts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 0;
|
||||
}
|
||||
|
||||
.account-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.account-icon {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.account-icon-img {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
.balance-icon {
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
}
|
||||
|
||||
.customs-icon {
|
||||
background: linear-gradient(135deg, #10B981, #34D399);
|
||||
}
|
||||
|
||||
.integral-icon {
|
||||
background: linear-gradient(135deg, #8B5CF6, #A78BFA);
|
||||
}
|
||||
|
||||
.self-icon {
|
||||
background: linear-gradient(135deg, #F59E0B, #FBBF24);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.account-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.account-name {
|
||||
font-size: 30rpx;
|
||||
color: #1A1A1A;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.account-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.account-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.account-value {
|
||||
font-size: 32rpx;
|
||||
color: #1A1A1A;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
// ==================== 自定义导航栏 ====================
|
||||
.custom-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
width: 80rpx;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
570
src/pages/userFunc/walletRecord.vue
Normal file
@@ -0,0 +1,570 @@
|
||||
<template>
|
||||
<view class="balance-page">
|
||||
<!-- 顶部余额区域 -->
|
||||
<view class="balance-header">
|
||||
<view class="header-bg"></view>
|
||||
<view class="balance-info">
|
||||
<text class="balance-label">
|
||||
{{ getName(type) }} </text>
|
||||
<view class="balance-value-wrapper">
|
||||
<text v-if="type=='account'" class="currency">¥</text>
|
||||
<text class="balance-value">{{ getAmount(type) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="balance-actions" v-if="type=='account'">
|
||||
<view class="action-btn" @click="goWithdraw">
|
||||
<text class="action-text">提现</text>
|
||||
</view>
|
||||
<view class="action-btn" @click="goTransfer">
|
||||
<text class="action-text">转账</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 提现记录入口 -->
|
||||
<view v-if="type=='account'" class="entry-row" @click="goWithdrawList">
|
||||
<text class="entry-label">提现记录</text>
|
||||
<text class="entry-arrow">›</text>
|
||||
</view>
|
||||
|
||||
<!-- 余额明细 -->
|
||||
<view class="detail-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">明细</text>
|
||||
<view class="filter-tabs">
|
||||
<text class="filter-tab" v-for="(tab, index) in filterTabs" :key="index"
|
||||
:class="{ active: currentFilter === index }" @click="switchFilter(index)">{{ tab.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="detail-list">
|
||||
<view v-if="detailList.length>0" class="detail-item" v-for="(item, index) in detailList" :key="index">
|
||||
<view class="detail-icon">
|
||||
<image :src="Service.GetIconImg('/static/icon/userFunc/money.png')"
|
||||
style="width: 100% ; height: 100%;" />
|
||||
</view>
|
||||
<view class="detail-info">
|
||||
<text class="detail-title">{{ item.name }}</text>
|
||||
<text class="detail-desc">{{ item.remark }}</text>
|
||||
<text class="detail-time">{{ Service.formatDate(item.addTime,1) }}</text>
|
||||
</view>
|
||||
<view class="detail-amount" :class="item.code === '收入' ? 'income' : 'expense'">
|
||||
{{ item.code === '收入' ? '+' : '-' }} <text v-if="type=='account'">¥</text> {{ item.amount }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class=""
|
||||
style="text-align: center; font-size: 32rpx; font-weight: bold; padding: 20rpx; border-radius: 10rpx; background-color: #f6f6f6; ">
|
||||
暂无记录
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<up-loadmore v-if="detailList.length>0" :status="status" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { UserService } from "@/Service/api/UserService"
|
||||
import { onLoad, onReachBottom, onShow } from '@dcloudio/uni-app';
|
||||
|
||||
const selectedPackage = ref(1);
|
||||
const currentFilter = ref(0);
|
||||
|
||||
const filterTabs = ref([
|
||||
{ name: '全部' },
|
||||
{ name: '支出' },
|
||||
{ name: '收入' }
|
||||
|
||||
]);
|
||||
|
||||
|
||||
const detailList = ref<Array<any>>([
|
||||
]);
|
||||
|
||||
let type = ref('account')
|
||||
let accInfo = ref<any>({})
|
||||
let page = ref(1)
|
||||
let status = ref('nomore')
|
||||
let shareAcc=ref(0)
|
||||
onLoad((data : any) => {
|
||||
type.value = data.type
|
||||
|
||||
if (data.type) {
|
||||
uni.setNavigationBarTitle({
|
||||
title: getTitile(data.type)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
onShow(()=>{
|
||||
getData()
|
||||
getAccInfo()
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
|
||||
const getAmount = (name : any) => {
|
||||
|
||||
|
||||
if (name == 'account') {
|
||||
return accInfo.value.account
|
||||
} else if (name == 'customs') {
|
||||
return accInfo.value.customs
|
||||
}
|
||||
else if (name == 'integral') {
|
||||
return accInfo.value.integral
|
||||
} else if (name == 'selfIntegral') {
|
||||
return accInfo.value.selfIntegral
|
||||
}
|
||||
}
|
||||
|
||||
const getTitile = (name : any) => {
|
||||
if (name == 'account') {
|
||||
return '余额明细'
|
||||
} else if (name == 'customs') {
|
||||
return '报单积分明细'
|
||||
}else if (name == 'integral') {
|
||||
return '普通积分明细'
|
||||
}
|
||||
else if (name == 'selfIntegral') {
|
||||
return '自营积分明细'
|
||||
}
|
||||
}
|
||||
|
||||
const getName=(name:any)=>{
|
||||
if (name == 'account') {
|
||||
return '余额'
|
||||
} else if (name == 'customs') {
|
||||
return '报单积分'
|
||||
}else if (name == 'integral') {
|
||||
return '普通积分'
|
||||
}
|
||||
else if (name == 'selfIntegral') {
|
||||
return '自营积分'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getAccInfo = () => {
|
||||
UserService.GetUserAccInfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
accInfo.value = res.data.accInfo
|
||||
shareAcc.value=res.data.shareAcc
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getData = () => {
|
||||
detailList.value = []
|
||||
page.value = 1
|
||||
status.value = 'loadmore'
|
||||
getList()
|
||||
}
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return
|
||||
}
|
||||
status.value = 'loading'
|
||||
UserService.GetUserAccLog(type.value, currentFilter.value == 0 ? '' : String(currentFilter.value - 1), page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
detailList.value = [...detailList.value, ...res.data.list]
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore'
|
||||
page.value++
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const switchFilter = (index : number) => {
|
||||
currentFilter.value = index;
|
||||
getData()
|
||||
};
|
||||
|
||||
const goWithdraw = () => {
|
||||
Service.GoPage('/pages/userFunc/withDrow')
|
||||
}
|
||||
|
||||
const goTransfer = () => {
|
||||
Service.GoPage('/pages/userFunc/transfer?accType=' + type.value)
|
||||
}
|
||||
|
||||
const goWithdrawList = () => {
|
||||
Service.GoPage('/pages/userFunc/withdrowList')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.balance-page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 顶部余额区域 */
|
||||
.balance-header {
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
padding: 40rpx 30rpx 60rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
position: absolute;
|
||||
top: -100rpx;
|
||||
right: -100rpx;
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.balance-info {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.balance-value-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.currency {
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
font-size: 72rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.balance-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
min-width: 160rpx;
|
||||
padding: 16rpx 40rpx;
|
||||
border-radius: 40rpx;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.5);
|
||||
text-align: center;
|
||||
backdrop-filter: blur(10rpx);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
/* 提现记录入口 */
|
||||
.entry-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 20rpx 20rpx 0;
|
||||
padding: 24rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.entry-row:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.entry-label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.entry-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #ccc;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 通用区域样式 */
|
||||
.recharge-section,
|
||||
.detail-section,
|
||||
.stats-section {
|
||||
background-color: #fff;
|
||||
margin: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
display: flex;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
padding: 8rpx 0;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
color: #F97316;
|
||||
font-weight: 500;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 4rpx;
|
||||
background-color: #F97316;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 充值套餐 */
|
||||
.recharge-packages {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: -10rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.package-item {
|
||||
width: calc(50% - 20rpx);
|
||||
margin: 10rpx;
|
||||
padding: 30rpx 20rpx;
|
||||
border: 2rpx solid #e5e5e5;
|
||||
border-radius: 16rpx;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.active {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF7ED;
|
||||
}
|
||||
}
|
||||
|
||||
.package-tag {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
padding: 4rpx 16rpx;
|
||||
border-radius: 0 16rpx 0 16rpx;
|
||||
}
|
||||
|
||||
.package-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.package-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.package-price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 36rpx;
|
||||
color: #F97316;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.original-price {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.package-bonus {
|
||||
font-size: 22rpx;
|
||||
color: #F97316;
|
||||
background-color: #FFF7ED;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.package-check {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
left: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.recharge-btn {
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
/* 余额明细 */
|
||||
.detail-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.detail-icon {
|
||||
width: 70rpx;
|
||||
height: 70rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.icon-text {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.detail-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.detail-desc {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.detail-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.detail-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
|
||||
&.income {
|
||||
color: #10B981;
|
||||
}
|
||||
|
||||
&.expense {
|
||||
color: #F97316;
|
||||
}
|
||||
}
|
||||
|
||||
/* 收支统计 */
|
||||
.stats-cards {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
flex: 1;
|
||||
padding: 30rpx;
|
||||
border-radius: 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&.income {
|
||||
background: linear-gradient(135deg, #ECFDF5, #D1FAE5);
|
||||
}
|
||||
|
||||
&.expense {
|
||||
background: linear-gradient(135deg, #FFF7ED, #FFEDD5);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
54
src/pages/userFunc/webView.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<view>
|
||||
<web-view :src="h5url" @message="handleWebMessage"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from "@/Service/Service";
|
||||
|
||||
|
||||
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const vw = ref(null)
|
||||
|
||||
let h5url = ref()
|
||||
let url = ref('http://192.168.0.192:5174/#')
|
||||
let address = ref('/pages/userFunc/orderDetail')
|
||||
onLoad((data : any) => {
|
||||
h5url.value = url.value + address.value
|
||||
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
});
|
||||
|
||||
// 接收H5 postMessage消息
|
||||
const handleWebMessage = (e : any) => {
|
||||
const msg = e.detail.data[0]
|
||||
console.log('收到H5消息', msg)
|
||||
const { code, data } = msg
|
||||
|
||||
if (code == 'Back') {
|
||||
Service.GoPageBack()
|
||||
}
|
||||
if (code === 'Ready') {
|
||||
SendMsg()
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 向子元素传递的值
|
||||
function SendMsg(data : any) {
|
||||
vw.value = pages[pages.length - 1].$getAppWebview().children()[0]
|
||||
const jsonStr = JSON.stringify({ code: 'init', data: data })
|
||||
vw.value.evalJS(`GetTwEvent('${jsonStr}')`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
491
src/pages/userFunc/withDrow.vue
Normal file
@@ -0,0 +1,491 @@
|
||||
<template>
|
||||
<view class="withdraw-page">
|
||||
<view class="withdraw-container">
|
||||
<view class="withdraw-header">
|
||||
<text class="page-title">申请提现</text>
|
||||
<text class="page-subtitle">请填写账户信息</text>
|
||||
</view>
|
||||
|
||||
<view class="withdraw-form">
|
||||
<!-- 姓名 -->
|
||||
<view class="input-item">
|
||||
<up-icon name="account" size="20" color="#999"></up-icon>
|
||||
<input v-model="withdrawForm.name" class="input-field" type="text" placeholder="请输入姓名" />
|
||||
</view>
|
||||
<!-- 银行卡号 -->
|
||||
<view class="input-item">
|
||||
<image :src="Service.GetIconImg('/static/icon/userFunc/userBank.png')"
|
||||
style="width: 30rpx; height: 30rpx;" />
|
||||
<input v-model="withdrawForm.bankName" class="input-field" maxlength="19" placeholder="请输入所属银行" />
|
||||
</view>
|
||||
|
||||
<!-- 银行卡号 -->
|
||||
<view class="input-item">
|
||||
<image :src="Service.GetIconImg('/static/icon/userFunc/bank.png')"
|
||||
style="width: 30rpx; height: 30rpx;" />
|
||||
<input v-model="withdrawForm.bankCard" class="input-field" type="number" maxlength="19"
|
||||
placeholder="请输入银行卡号" />
|
||||
</view>
|
||||
|
||||
<!-- 金额 -->
|
||||
<view class="input-item">
|
||||
<up-icon name="rmb-circle" size="20" color="#999"></up-icon>
|
||||
<input v-model="withdrawForm.amount" class="input-field" type="text" placeholder="请输入提现金额" />
|
||||
</view>
|
||||
|
||||
<!-- 余额显示 -->
|
||||
<view class="balance-info">
|
||||
<view class="balance-row">
|
||||
<text class="balance-label">可提现余额:</text>
|
||||
<text class="balance-value">¥{{ currentBalance || '0.00' }}</text>
|
||||
</view>
|
||||
<!-- <view class="balance-row">
|
||||
<text class="quota-label">可提现额度:</text>
|
||||
<text class="balance-value">¥{{ withdrawQuota || '0.00' }}</text>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="button-group">
|
||||
<button class="withdraw-btn" @click="handleWithdraw">
|
||||
确认提现
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付密码弹窗 -->
|
||||
<up-popup :show="showPasswordPopup" mode="center" @close="closePasswordPopup" :closeable="true" :round="10"
|
||||
:closeOnClickOverlay="true">
|
||||
<view class="password-popup">
|
||||
<view class="password-popup-header">
|
||||
<text class="password-popup-title">输入支付密码</text>
|
||||
</view>
|
||||
<view class="password-popup-content">
|
||||
<view class="pay-amount">
|
||||
<text class="amount-label">提现金额</text>
|
||||
<text class="amount-value">¥{{ withdrawForm.amount || '0.00' }}</text>
|
||||
</view>
|
||||
<view class="password-input-wrapper">
|
||||
<view class="password-dots">
|
||||
<view v-for="(dot, index) in 6" :key="index" class="password-dot"
|
||||
:class="{ filled: index < payPassword.length }">
|
||||
</view>
|
||||
</view>
|
||||
<input v-model="payPassword" class="hidden-password-input" type="number" maxlength="6"
|
||||
:focus="showPasswordPopup" @input="handlePasswordInput" />
|
||||
</view>
|
||||
<view class="password-actions">
|
||||
<text class="forgot-password" @click="handleForgotPassword">忘记支付密码?</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="password-popup-footer">
|
||||
<button class="cancel-btn" @click="closePasswordPopup">取消</button>
|
||||
<button class="confirm-btn" @click="confirmWithdraw"
|
||||
:disabled="payPassword.length < 6">确认提现</button>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onLoad } from '@dcloudio/uni-app';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { UserService } from '@/Service/api/UserService';
|
||||
|
||||
const withdrawForm = ref({
|
||||
name: '',
|
||||
bankCard: '',
|
||||
amount: '',
|
||||
bankName: ''
|
||||
});
|
||||
|
||||
const accountType = ref('user');
|
||||
const currentBalance = ref('0.00');
|
||||
const withdrawQuota = ref('0.00');
|
||||
const accInfo = ref<any>({});
|
||||
|
||||
const showPasswordPopup = ref(false);
|
||||
const payPassword = ref('');
|
||||
|
||||
|
||||
let accType = ref('')
|
||||
onLoad((data : any) => {
|
||||
if (data.accType) {
|
||||
accType.value = data.accType;
|
||||
}
|
||||
getAccInfo();
|
||||
});
|
||||
|
||||
const getAccInfo = () => {
|
||||
UserService.GetUserAccInfo().then(res => {
|
||||
if (res.code == 0) {
|
||||
accInfo.value = res.data.accInfo;
|
||||
currentBalance.value = accInfo.value.account;
|
||||
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
const handleWithdraw = () => {
|
||||
if (!withdrawForm.value.name) {
|
||||
Service.Msg('请输入姓名');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!withdrawForm.value.bankName) {
|
||||
Service.Msg('请输入所属银行');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!withdrawForm.value.bankCard) {
|
||||
Service.Msg('请输入银行卡号');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!withdrawForm.value.amount) {
|
||||
Service.Msg('请输入提现金额');
|
||||
return;
|
||||
}
|
||||
|
||||
const amount = parseFloat(withdrawForm.value.amount);
|
||||
const balance = parseFloat(currentBalance.value);
|
||||
const quota = parseFloat(withdrawQuota.value);
|
||||
|
||||
if (amount <= 0) {
|
||||
Service.Msg('提现金额必须大于0');
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount > balance) {
|
||||
Service.Msg('提现金额不能超过可提现余额');
|
||||
return;
|
||||
}
|
||||
|
||||
showPasswordPopup.value = true;
|
||||
payPassword.value = '';
|
||||
};
|
||||
|
||||
const handleTransfer = () => {
|
||||
Service.Msg('转账功能开发中');
|
||||
};
|
||||
|
||||
const handlePasswordInput = () => {
|
||||
};
|
||||
|
||||
const closePasswordPopup = () => {
|
||||
showPasswordPopup.value = false;
|
||||
payPassword.value = '';
|
||||
};
|
||||
|
||||
const handleForgotPassword = () => {
|
||||
Service.GoPage('/pages/userFunc/setPaypwd');
|
||||
};
|
||||
|
||||
const confirmWithdraw = () => {
|
||||
if (payPassword.value.length < 6) {
|
||||
Service.Msg('请输入完整的支付密码');
|
||||
return;
|
||||
}
|
||||
|
||||
Service.LoadIng('提现申请中...');
|
||||
let data={
|
||||
amount:withdrawForm.value.amount,
|
||||
name:withdrawForm.value.name,
|
||||
account: withdrawForm.value.bankCard,
|
||||
pwd:payPassword.value,
|
||||
bankName:withdrawForm.value.bankName
|
||||
}
|
||||
|
||||
UserService.UserWithAcc(data).then(res => {
|
||||
if (res.code == 0) {
|
||||
Service.LoadClose();
|
||||
Service.Msg('提现申请已提交,请等待审核');
|
||||
closePasswordPopup();
|
||||
setTimeout(() => {
|
||||
Service.GoPageBack();
|
||||
}, 1500);
|
||||
} else {
|
||||
Service.Msg(res.msg)
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background: linear-gradient(135deg, #FFF7ED 0%, #FFEDD5 100%);
|
||||
}
|
||||
|
||||
.withdraw-page {
|
||||
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.withdraw-container {
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 32rpx;
|
||||
padding: 60rpx 40rpx;
|
||||
box-shadow: 0 8rpx 32rpx rgba(249, 115, 22, 0.1);
|
||||
}
|
||||
|
||||
.withdraw-header {
|
||||
margin-bottom: 50rpx;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
display: block;
|
||||
font-size: 44rpx;
|
||||
font-weight: bold;
|
||||
color: #1F2937;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.withdraw-form {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #F9FAFB;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx 30rpx;
|
||||
margin-bottom: 28rpx;
|
||||
border: 2rpx solid transparent;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #F97316;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.balance-info {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 16rpx;
|
||||
padding: 30rpx;
|
||||
background: linear-gradient(135deg, #FFF7ED, #FFEDD5);
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.balance-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #F97316;
|
||||
}
|
||||
|
||||
.quota-label {
|
||||
font-size: 28rpx;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.quota-value {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
.balance-all {
|
||||
font-size: 26rpx;
|
||||
color: #F97316;
|
||||
padding: 8rpx 20rpx;
|
||||
background-color: #FFF;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.transfer-btn {
|
||||
flex: 1;
|
||||
padding: 0 30rpx;
|
||||
background: #fff;
|
||||
color: #F97316;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: 2rpx solid #F97316;
|
||||
border-radius: 16rpx;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background-color: #FFF7ED;
|
||||
}
|
||||
}
|
||||
|
||||
.withdraw-btn {
|
||||
flex: 1;
|
||||
padding: 0 30rpx;
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #FFFFFF;
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(249, 115, 22, 0.3);
|
||||
transition: all 0.3s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* 支付密码弹窗样式 */
|
||||
.password-popup {
|
||||
width: 600rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.password-popup-header {
|
||||
padding: 40rpx 30rpx 30rpx;
|
||||
text-align: center;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.password-popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.password-popup-content {
|
||||
padding: 40rpx 30rpx;
|
||||
}
|
||||
|
||||
.pay-amount {
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.amount-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #F97316;
|
||||
}
|
||||
|
||||
.password-input-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.password-dots {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid #e5e7eb;
|
||||
}
|
||||
|
||||
.password-dot {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 50%;
|
||||
background-color: transparent;
|
||||
border: 2rpx solid #d1d5db;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.filled {
|
||||
background-color: #333;
|
||||
border-color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden-password-input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.password-actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
font-size: 26rpx;
|
||||
color: #F97316;
|
||||
}
|
||||
|
||||
.password-popup-footer {
|
||||
display: flex;
|
||||
padding: 0 30rpx 30rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.cancel-btn,
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 30rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||
color: #fff;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
232
src/pages/userFunc/withdrowList.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<view class="withdraw-list-page">
|
||||
<view class="withdraw-list">
|
||||
<view class="list-header">
|
||||
<text class="list-title">提现记录</text>
|
||||
</view>
|
||||
|
||||
<view v-if="withdrawList.length > 0" class="withdraw-item" v-for="(item, index) in withdrawList"
|
||||
:key="index">
|
||||
<view class="item-left">
|
||||
<view class="user-info">
|
||||
<text class="user-name">{{ item.name }}</text>
|
||||
</view>
|
||||
<view class="account-info">
|
||||
<text class="account-label">提现银行:</text>
|
||||
<text class="account-value">{{ item.bankName }}</text>
|
||||
</view>
|
||||
<view class="account-info">
|
||||
<text class="account-label">提现账户:</text>
|
||||
<text class="account-value">{{ item.account }}</text>
|
||||
</view>
|
||||
<view class="time-info">
|
||||
<text v-if="item.service" class="fee-info">手续费: ¥{{ item.service }}</text>
|
||||
<text v-if="item.refuse" class="fee-info" style="color: red;">拒绝原因:{{ item.refuse }}</text>
|
||||
<text class="withdraw-time">提现时间: {{ Service.formatDate(item.addTime, 1) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<text class="amount">¥{{ item.withAmount }}</text>
|
||||
<view class="status-tag" :class="getStatusClass(item.state)">
|
||||
{{ getStatusText(item.state) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="empty-state">
|
||||
<text class="empty-text">暂无提现记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<up-loadmore v-if="withdrawList.length > 0" :status="status" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
|
||||
import { Service } from '@/Service/Service';
|
||||
import { UserService } from '@/Service/api/UserService';
|
||||
|
||||
const withdrawList = ref<Array<any>>([]);
|
||||
let page = ref(1);
|
||||
let status = ref('nomore');
|
||||
let accType = ref('moneyAcc');
|
||||
|
||||
onLoad((data : any) => {
|
||||
getData()
|
||||
});
|
||||
|
||||
onReachBottom(() => {
|
||||
getList();
|
||||
});
|
||||
|
||||
const getData = () => {
|
||||
withdrawList.value = [];
|
||||
page.value = 1;
|
||||
status.value = 'loadmore';
|
||||
getList();
|
||||
};
|
||||
|
||||
const getList = () => {
|
||||
if (status.value == 'loading' || status.value == 'nomore') {
|
||||
return;
|
||||
}
|
||||
status.value = 'loading';
|
||||
UserService.GetUserWithList('', page.value).then(res => {
|
||||
if (res.code == 0) {
|
||||
withdrawList.value = [...withdrawList.value, ...res.data.list];
|
||||
status.value = res.data.list.length == 10 ? 'loadmore' : 'nomore';
|
||||
page.value++;
|
||||
} else {
|
||||
Service.Msg(res.msg);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getStatusText = (state : number) => {
|
||||
const stateMap : { [key : number] : string } = {
|
||||
0: '审核中',
|
||||
1: '已打款',
|
||||
2: '拒绝提现',
|
||||
};
|
||||
return stateMap[state] || '未知';
|
||||
};
|
||||
|
||||
const getStatusClass = (state : number) => {
|
||||
const classMap : { [key : number] : string } = {
|
||||
0: 'pending',
|
||||
1: 'processing',
|
||||
2: 'success',
|
||||
3: 'failed'
|
||||
};
|
||||
return classMap[state] || '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.withdraw-list-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.withdraw-item {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.item-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
margin-bottom: 8rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.account-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.account-value {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.time-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.withdraw-time {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.fee-info {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.item-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #F97316;
|
||||
}
|
||||
|
||||
.status-tag {
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&.pending {
|
||||
background-color: #FFF7ED;
|
||||
color: #F97316;
|
||||
}
|
||||
|
||||
&.processing {
|
||||
background-color: #EFF6FF;
|
||||
color: #3B82F6;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: #ECFDF5;
|
||||
color: #10B981;
|
||||
}
|
||||
|
||||
&.failed {
|
||||
background-color: #FEF2F2;
|
||||
color: #EF4444;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 80rpx 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
428
src/pages/webView/goodsDetail.vue
Normal file
@@ -0,0 +1,428 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 自定义导航栏 -->
|
||||
<view v-if="isLoading" class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="Service.GoPageBack()">
|
||||
<up-icon name="arrow-left" size="20" color="#000000"></up-icon>
|
||||
</view>
|
||||
<view class="nav-center">
|
||||
<text class="nav-title">商品详情</text>
|
||||
</view>
|
||||
<view class="nav-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isLoading" class="nav-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
|
||||
<!-- 骨架屏区域 - 数据加载时显示 -->
|
||||
<view v-if="isLoading" class="skeleton-detail-container">
|
||||
<!-- 轮播图骨架 -->
|
||||
<view class="skeleton-swiper">
|
||||
<view class="skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-indicator">
|
||||
<view class="skeleton-item"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品信息骨架 -->
|
||||
<view class="skeleton-goods-info">
|
||||
<view class="skeleton-price-row">
|
||||
<view class="skeleton-price skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-old-price skeleton-item skeleton-shimmer"></view>
|
||||
</view>
|
||||
<view class="skeleton-tag skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-name skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-name skeleton-item skeleton-shimmer short"></view>
|
||||
</view>
|
||||
|
||||
<!-- 标签栏骨架 -->
|
||||
<view class="skeleton-tabs">
|
||||
<view class="skeleton-tab skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-tab skeleton-item skeleton-shimmer"></view>
|
||||
</view>
|
||||
|
||||
<!-- 商品介绍骨架 -->
|
||||
<view class="skeleton-intro">
|
||||
<view class="skeleton-intro-title skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-intro-content">
|
||||
<view class="skeleton-intro-img skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-intro-img skeleton-item skeleton-shimmer"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 评价区域骨架 -->
|
||||
<view class="skeleton-comment">
|
||||
<view class="skeleton-comment-title">
|
||||
<view class="skeleton-item skeleton-shimmer"></view>
|
||||
</view>
|
||||
<view v-for="i in 2" :key="i" class="skeleton-comment-item">
|
||||
<view class="skeleton-comment-avatar skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-comment-content">
|
||||
<view class="skeleton-comment-header">
|
||||
<view class="skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-item skeleton-shimmer"></view>
|
||||
</view>
|
||||
<view class="skeleton-comment-text skeleton-item skeleton-shimmer"></view>
|
||||
<view class="skeleton-comment-text skeleton-item skeleton-shimmer short"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="skeleton-bottom-placeholder"></view>
|
||||
</view>
|
||||
<web-view v-show="!isLoading" :src="h5url" @message="handleWebMessage"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from "@/Service/Service";
|
||||
|
||||
let isLoading = ref(true)
|
||||
let statusBarHeight = ref(uni.getWindowInfo().statusBarHeight || 20)
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const vw = ref(null)
|
||||
|
||||
let h5url = ref()
|
||||
let url = ref('http://192.168.0.192:5173/#')
|
||||
let address = ref('/pages/userFunc/goodsDetail')
|
||||
let goodsId = ref('')
|
||||
let content = ref<any>({})
|
||||
onLoad((data : any) => {
|
||||
goodsId.value = data.goodsId
|
||||
h5url.value = url.value + address.value
|
||||
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
});
|
||||
|
||||
// 接收H5 postMessage消息
|
||||
const handleWebMessage = (e : any) => {
|
||||
const msg = e.detail.data[0]
|
||||
console.log('收到H5消息', msg)
|
||||
let token = Service.GetUserToken()
|
||||
content.value = {
|
||||
goodsId: goodsId.value,
|
||||
token: token
|
||||
}
|
||||
const { code, data } = msg
|
||||
|
||||
if (code == 'Back') {
|
||||
Service.GoPageBack()
|
||||
}
|
||||
if (code === 'Ready') {
|
||||
isLoading.value = false
|
||||
SendMsg(content.value)
|
||||
}
|
||||
if (code === 'orderDetail') {
|
||||
Service.GoPage('/pages/webView/orderDetail?orderId=' + data)
|
||||
}
|
||||
|
||||
|
||||
if (code === 'shop') {
|
||||
setTimeout(() => {
|
||||
Service.GoPageTab('/pages/index/shop')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 向子元素传递的值
|
||||
function SendMsg(data : any) {
|
||||
vw.value = pages[pages.length - 1].$getAppWebview().children()[0]
|
||||
const jsonStr = JSON.stringify({ code: 'init', data: data })
|
||||
vw.value.evalJS(`GetTwEvent('${jsonStr}')`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
// ==================== 自定义导航栏 ====================
|
||||
.custom-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom: 1rpx solid #E5E5E5; // 加上底部细线
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8rpx; // 原生左右边距较小
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx; // 接近原生 17px
|
||||
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.nav-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
width: 44px; // 与左侧对称,保证标题严格居中
|
||||
}
|
||||
|
||||
// ==================== 骨架屏样式 ====================
|
||||
|
||||
// 骨架屏容器
|
||||
.skeleton-detail-container {
|
||||
padding-bottom: 200rpx;
|
||||
background-color: #f6f6f6;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
// 骨架屏基础元素样式 - 渐变灰色占位块
|
||||
.skeleton-item {
|
||||
// 使用柔和的渐变背景,从浅灰到稍深的灰色
|
||||
background: linear-gradient(135deg,
|
||||
#f8f8f8 0%,
|
||||
#f0f0f0 50%,
|
||||
#e8e8e8 100%);
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
// ==================== 闪光动画效果 ====================
|
||||
// 使用CSS动画实现骨架屏的闪光效果
|
||||
.skeleton-shimmer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
// 创建闪光层
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%; // 从左侧开始
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
// 使用多层渐变创造更丰富的闪光效果
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.3) 20%, // 淡入
|
||||
rgba(255, 255, 255, 0.6) 40%, // 最亮
|
||||
rgba(255, 255, 255, 0.4) 60%, // 稍暗
|
||||
rgba(255, 255, 255, 0.2) 80%, // 淡出
|
||||
transparent 100%);
|
||||
animation: skeleton-shimmer 2s infinite; // 循环播放动画,稍慢一点更自然
|
||||
}
|
||||
|
||||
// 添加一个额外的渐变层,创造更深的视觉层次
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg,
|
||||
rgba(255, 255, 255, 0.1) 0%,
|
||||
transparent 50%,
|
||||
rgba(0, 0, 0, 0.03) 100%);
|
||||
pointer-events: none; // 不影响点击
|
||||
}
|
||||
}
|
||||
|
||||
// 闪光动画关键帧
|
||||
@keyframes skeleton-shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%); // 起始位置
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(100%); // 结束位置
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 轮播图骨架 ====================
|
||||
.skeleton-swiper {
|
||||
width: 100%;
|
||||
height: 600rpx;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
|
||||
.skeleton-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-indicator {
|
||||
position: absolute;
|
||||
bottom: 30rpx;
|
||||
right: 30rpx;
|
||||
|
||||
.skeleton-item {
|
||||
width: 80rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 商品信息骨架 ====================
|
||||
.skeleton-goods-info {
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-price {
|
||||
width: 160rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-old-price {
|
||||
width: 100rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
|
||||
.skeleton-tag {
|
||||
width: 300rpx;
|
||||
height: 36rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-name {
|
||||
width: 100%;
|
||||
height: 32rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&.short {
|
||||
width: 70%;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 标签栏骨架 ====================
|
||||
.skeleton-tabs {
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
display: flex;
|
||||
gap: 60rpx;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.skeleton-tab {
|
||||
width: 120rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
|
||||
// ==================== 商品介绍骨架 ====================
|
||||
.skeleton-intro {
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-intro-title {
|
||||
width: 160rpx;
|
||||
height: 36rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.skeleton-intro-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-intro-img {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
// ==================== 评价区域骨架 ====================
|
||||
.skeleton-comment {
|
||||
background-color: #fff;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-comment-title {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.skeleton-item {
|
||||
width: 200rpx;
|
||||
height: 36rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-comment-item {
|
||||
display: flex;
|
||||
padding: 20rpx 0;
|
||||
border-top: 1rpx solid #e2e2e2;
|
||||
}
|
||||
|
||||
.skeleton-comment-avatar {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
|
||||
.skeleton-comment-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.skeleton-comment-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
.skeleton-item:first-child {
|
||||
width: 120rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
|
||||
.skeleton-item:last-child {
|
||||
width: 140rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-comment-text {
|
||||
width: 100%;
|
||||
height: 28rpx;
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
&.short {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 底部占位 ====================
|
||||
.skeleton-bottom-placeholder {
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
}
|
||||
</style>
|
||||
363
src/pages/webView/orderDetail.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 自定义导航栏 -->
|
||||
<view v-if="isLoading" class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="Service.GoPageBack()">
|
||||
<up-icon name="arrow-left" size="20" color="#000000"></up-icon>
|
||||
</view>
|
||||
<view class="nav-center">
|
||||
<text class="nav-title">订单详情</text>
|
||||
</view>
|
||||
<view class="nav-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="nav-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
<template v-if="isLoading">
|
||||
<!-- 骨架屏 -->
|
||||
<view class="skeleton-wrapper">
|
||||
<!-- 订单状态 -->
|
||||
<view class="order-status skeleton-status">
|
||||
<view class="skeleton-text-lg"></view>
|
||||
<view class="skeleton-text-sm"></view>
|
||||
</view>
|
||||
|
||||
<!-- 收货信息 -->
|
||||
<view class="shipping-info skeleton-shipping">
|
||||
<view class="skeleton-icon"></view>
|
||||
<view class="skeleton-shipping-content">
|
||||
<view class="skeleton-name-phone">
|
||||
<view class="skeleton-text" style="width: 120rpx;"></view>
|
||||
<view class="skeleton-text" style="width: 200rpx;"></view>
|
||||
</view>
|
||||
<view class="skeleton-text" style="width: 80%;"></view>
|
||||
</view>
|
||||
<view class="skeleton-icon-sm"></view>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<view class="goods-list skeleton-goods-list">
|
||||
<view class="goods-item skeleton-goods-item" v-for="i in 2" :key="i">
|
||||
<view class="skeleton-img"></view>
|
||||
<view class="skeleton-goods-info">
|
||||
<view class="skeleton-text" style="width: 90%;"></view>
|
||||
<view class="skeleton-text" style="width: 50%;"></view>
|
||||
<view class="skeleton-price-quantity">
|
||||
<view class="skeleton-text" style="width: 120rpx;"></view>
|
||||
<view class="skeleton-text" style="width: 60rpx;"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="info-item skeleton-total">
|
||||
<view class="skeleton-text" style="width: 140rpx;"></view>
|
||||
<view class="skeleton-text" style="width: 160rpx;"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 订单信息 -->
|
||||
<view class="order-info skeleton-order-info">
|
||||
<view class="section-title skeleton-title"></view>
|
||||
<view class="info-item skeleton-info-item" v-for="i in 4" :key="i">
|
||||
<view class="skeleton-text" style="width: 140rpx;"></view>
|
||||
<view class="skeleton-text" style="width: 200rpx;"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="skeleton-bottom-space"></view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-buttons skeleton-bottom">
|
||||
<view class="skeleton-btn"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<web-view v-show="!isLoading" :src="h5url" @message="handleWebMessage"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from "@/Service/Service";
|
||||
|
||||
|
||||
|
||||
let isLoading = ref(true)
|
||||
let statusBarHeight = ref(uni.getWindowInfo().statusBarHeight || 20)
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const vw = ref(null)
|
||||
|
||||
let h5url = ref()
|
||||
let url = ref('http://192.168.0.192:5173/#')
|
||||
let address = ref('/pages/userFunc/orderDetail')
|
||||
let orderId = ref(0)
|
||||
let content = ref<any>({})
|
||||
onLoad((data : any) => {
|
||||
orderId.value=data.orderId
|
||||
h5url.value = url.value + address.value
|
||||
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
});
|
||||
|
||||
// 接收H5 postMessage消息
|
||||
const handleWebMessage = (e : any) => {
|
||||
const msg = e.detail.data[0]
|
||||
console.log('收到H5消息', msg)
|
||||
let token = Service.GetUserToken()
|
||||
content.value = {
|
||||
orderId: orderId.value,
|
||||
token: token
|
||||
}
|
||||
const { code, data } = msg
|
||||
|
||||
if (code == 'Back') {
|
||||
Service.GoPageBack()
|
||||
}
|
||||
if (code === 'Ready') {
|
||||
SendMsg(content.value)
|
||||
isLoading.value=false
|
||||
}
|
||||
if (code === 'addAddress') {
|
||||
Service.GoPage('/pages/userFunc/addAddress');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
// 向子元素传递的值
|
||||
function SendMsg(data : any) {
|
||||
vw.value = pages[pages.length - 1].$getAppWebview().children()[0]
|
||||
const jsonStr = JSON.stringify({ code: 'init', data: data })
|
||||
vw.value.evalJS(`GetTwEvent('${jsonStr}')`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
// ==================== 自定义导航栏 ====================
|
||||
.custom-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom: 1rpx solid #E5E5E5; // 加上底部细线
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8rpx; // 原生左右边距较小
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx; // 接近原生 17px
|
||||
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.nav-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
width: 44px; // 与左侧对称,保证标题严格居中
|
||||
}
|
||||
|
||||
// ==================== 骨架屏 ====================
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 100% 50%; }
|
||||
100% { background-position: 0 50%; }
|
||||
}
|
||||
|
||||
.skeleton-wrapper {
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
.skeleton,
|
||||
.skeleton-text,
|
||||
.skeleton-text-lg,
|
||||
.skeleton-text-sm,
|
||||
.skeleton-icon,
|
||||
.skeleton-icon-sm,
|
||||
.skeleton-img,
|
||||
.skeleton-btn,
|
||||
.skeleton-title {
|
||||
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 50%, #f2f2f2 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
height: 28rpx;
|
||||
}
|
||||
|
||||
.skeleton-text-lg {
|
||||
height: 44rpx;
|
||||
width: 160rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.skeleton-text-sm {
|
||||
height: 24rpx;
|
||||
width: 240rpx;
|
||||
}
|
||||
|
||||
.skeleton-icon {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.skeleton-icon-sm {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.skeleton-img {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.skeleton-btn {
|
||||
width: 100%;
|
||||
height: 70rpx;
|
||||
border-radius: 35rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.skeleton-shipping {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
margin-top: 20rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.skeleton-shipping-content {
|
||||
flex: 1;
|
||||
margin-right: 20rpx;
|
||||
|
||||
.skeleton-name-phone {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-goods-list {
|
||||
background-color: #fff;
|
||||
margin-top: 20rpx;
|
||||
padding: 0 30rpx;
|
||||
|
||||
.skeleton-goods-item {
|
||||
display: flex;
|
||||
padding: 30rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.skeleton-goods-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.skeleton-text {
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-price-quantity {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-order-info {
|
||||
background-color: #fff;
|
||||
margin-top: 20rpx;
|
||||
padding: 30rpx;
|
||||
|
||||
.skeleton-title {
|
||||
width: 140rpx;
|
||||
height: 32rpx;
|
||||
margin-bottom: 25rpx;
|
||||
}
|
||||
|
||||
.skeleton-info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 18rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-bottom-space {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.skeleton-bottom {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 110rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #fff;
|
||||
border-top: 1rpx solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
266
src/pages/webView/orderList.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 自定义导航栏 -->
|
||||
<view v-if="isLoading" class="custom-nav" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||
<view class="nav-content">
|
||||
<view class="nav-left" @click="Service.GoPageBack()">
|
||||
<up-icon name="arrow-left" size="20" color="#000000"></up-icon>
|
||||
</view>
|
||||
<view class="nav-center">
|
||||
<text class="nav-title">我的订单</text>
|
||||
</view>
|
||||
<view class="nav-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isLoading" class="nav-placeholder" :style="{ height: (statusBarHeight + 44) + 'px' }"></view>
|
||||
<!-- 订单列表骨架屏 -->
|
||||
<view class="skeleton-wrapper" v-if="isLoading">
|
||||
<view class="skeleton-order-item" v-for="i in 3" :key="i">
|
||||
<view class="skeleton-header">
|
||||
<view class="skeleton-text" style="width: 260rpx;"></view>
|
||||
<view class="skeleton-text" style="width: 140rpx;"></view>
|
||||
</view>
|
||||
<view class="skeleton-product">
|
||||
<view class="skeleton-img"></view>
|
||||
<view class="skeleton-product-details">
|
||||
<view class="skeleton-text" style="width: 90%;"></view>
|
||||
<view class="skeleton-text" style="width: 60%;"></view>
|
||||
<view class="skeleton-text" style="width: 120rpx;"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="skeleton-total">
|
||||
<view class="skeleton-text" style="width: 160rpx;"></view>
|
||||
</view>
|
||||
<view class="skeleton-footer">
|
||||
<view class="skeleton-status"></view>
|
||||
<view class="skeleton-actions">
|
||||
<view class="skeleton-btn secondary"></view>
|
||||
<view class="skeleton-btn primary"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<web-view v-show="!isLoading" :src="h5url" @message="handleWebMessage"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onShow, onLoad } from "@dcloudio/uni-app";
|
||||
import { ref } from "vue";
|
||||
import { Service } from "@/Service/Service";
|
||||
|
||||
let isLoading = ref(true)
|
||||
let statusBarHeight = ref(uni.getWindowInfo().statusBarHeight || 20)
|
||||
|
||||
|
||||
|
||||
const pages = getCurrentPages()
|
||||
const vw = ref(null)
|
||||
|
||||
let h5url = ref()
|
||||
let url = ref('http://192.168.0.192:5173/#')
|
||||
let address = ref('/pages/userFunc/orderList')
|
||||
let type = ref(0)
|
||||
let content = ref<any>({})
|
||||
onLoad((data : any) => {
|
||||
console.log(data);
|
||||
type.value = data.type
|
||||
h5url.value = url.value + address.value
|
||||
|
||||
});
|
||||
|
||||
onShow(() => {
|
||||
|
||||
});
|
||||
|
||||
// 接收H5 postMessage消息
|
||||
const handleWebMessage = (e : any) => {
|
||||
const msg = e.detail.data[0]
|
||||
console.log('收到H5消息', msg)
|
||||
let token = Service.GetUserToken()
|
||||
content.value = {
|
||||
type: type.value,
|
||||
token: token
|
||||
}
|
||||
const { code, data } = msg
|
||||
|
||||
if (code == 'Back') {
|
||||
Service.GoPageBack()
|
||||
}
|
||||
if (code === 'Ready') {
|
||||
SendMsg(content.value)
|
||||
isLoading.value=false
|
||||
}
|
||||
if (code === 'orderDetail') {
|
||||
Service.GoPage('/pages/webView/orderDetail?orderId=' + data)
|
||||
}
|
||||
if (code === 'comment') {
|
||||
Service.GoPage('/pages/userFunc/goComment?orderId=' + data)
|
||||
}
|
||||
};
|
||||
|
||||
// 向子元素传递的值
|
||||
function SendMsg(data : any) {
|
||||
vw.value = pages[pages.length - 1].$getAppWebview().children()[0]
|
||||
const jsonStr = JSON.stringify({ code: 'init', data: data })
|
||||
vw.value.evalJS(`GetTwEvent('${jsonStr}')`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
// ==================== 骨架屏 ====================
|
||||
@keyframes skeleton-loading {
|
||||
0% { background-position: 100% 50%; }
|
||||
100% { background-position: 0 50%; }
|
||||
}
|
||||
|
||||
.skeleton-wrapper {
|
||||
padding: 20rpx;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
.skeleton,
|
||||
.skeleton-text,
|
||||
.skeleton-img,
|
||||
.skeleton-status,
|
||||
.skeleton-btn {
|
||||
background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 50%, #f2f2f2 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
height: 28rpx;
|
||||
}
|
||||
|
||||
.skeleton-img {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-right: 20rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.skeleton-status {
|
||||
width: 100rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.skeleton-btn {
|
||||
height: 56rpx;
|
||||
border-radius: 40rpx;
|
||||
|
||||
&.secondary {
|
||||
width: 140rpx;
|
||||
}
|
||||
|
||||
&.primary {
|
||||
width: 140rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-order-item {
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
|
||||
.skeleton-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.skeleton-product {
|
||||
display: flex;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #e2e2e2;
|
||||
|
||||
.skeleton-product-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.skeleton-text {
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton-total {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.skeleton-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20rpx;
|
||||
|
||||
.skeleton-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 自定义导航栏 ====================
|
||||
.custom-nav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
background-color: #FFFFFF;
|
||||
border-bottom: 1rpx solid #E5E5E5; // 加上底部细线
|
||||
}
|
||||
|
||||
.nav-content {
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 8rpx; // 原生左右边距较小
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 34rpx; // 接近原生 17px
|
||||
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.nav-center {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
width: 44px; // 与左侧对称,保证标题严格居中
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
6
src/shime-uni.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export {}
|
||||
|
||||
declare module "vue" {
|
||||
type Hooks = App.AppInstance & Page.PageInstance;
|
||||
interface ComponentCustomOptions extends Hooks {}
|
||||
}
|
||||
BIN
src/static/goods/comment/fire.png
Normal file
|
After Width: | Height: | Size: 377 B |
BIN
src/static/goods/goodsIcon/cart.png
Normal file
|
After Width: | Height: | Size: 583 B |
BIN
src/static/goods/goodsIcon/class.png
Normal file
|
After Width: | Height: | Size: 570 B |
BIN
src/static/goods/goodsIcon/like.png
Normal file
|
After Width: | Height: | Size: 693 B |
BIN
src/static/goods/goodsIcon/liked.png
Normal file
|
After Width: | Height: | Size: 418 B |
BIN
src/static/goods/goodsIcon/message.png
Normal file
|
After Width: | Height: | Size: 790 B |
BIN
src/static/goods/group/group.png
Normal file
|
After Width: | Height: | Size: 437 B |
BIN
src/static/goods/group/people.png
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
src/static/goods/left.png
Normal file
|
After Width: | Height: | Size: 314 B |
BIN
src/static/goods/pingtuan.png
Normal file
|
After Width: | Height: | Size: 776 KiB |
BIN
src/static/icon/class/search.png
Normal file
|
After Width: | Height: | Size: 449 B |
BIN
src/static/icon/home/bottom.png
Normal file
|
After Width: | Height: | Size: 184 B |
BIN
src/static/icon/home/flame.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
src/static/icon/home/goods.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
src/static/icon/home/grid/class.png
Normal file
|
After Width: | Height: | Size: 516 B |
BIN
src/static/icon/home/grid/many.png
Normal file
|
After Width: | Height: | Size: 275 B |
BIN
src/static/icon/home/grid/new.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
src/static/icon/home/grid/time.png
Normal file
|
After Width: | Height: | Size: 523 B |
BIN
src/static/icon/home/grid/user.png
Normal file
|
After Width: | Height: | Size: 489 B |
BIN
src/static/icon/home/location.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
src/static/icon/home/pintuan.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/static/icon/home/right.png
Normal file
|
After Width: | Height: | Size: 262 B |
BIN
src/static/icon/home/time.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
src/static/icon/my/addService/check.png
Normal file
|
After Width: | Height: | Size: 735 B |
BIN
src/static/icon/my/addService/intergrate.png
Normal file
|
After Width: | Height: | Size: 655 B |
BIN
src/static/icon/my/addService/share.png
Normal file
|
After Width: | Height: | Size: 841 B |
BIN
src/static/icon/my/addService/vip.png
Normal file
|
After Width: | Height: | Size: 734 B |
BIN
src/static/icon/my/get.png
Normal file
|
After Width: | Height: | Size: 515 B |