迁移版本
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
.project
|
||||||
|
unpackage/
|
||||||
|
.DS_Store
|
||||||
|
.hbuilderx
|
||||||
|
.idea
|
||||||
74
App.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script>
|
||||||
|
import { loginApi } from "@/api/page/auth";
|
||||||
|
import { getCache, setCache } from "@/utils/cache";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
onLaunch: function () {
|
||||||
|
console.log("App Launch");
|
||||||
|
|
||||||
|
// 检查更新
|
||||||
|
if (uni.canIUse('getUpdateManager')) {
|
||||||
|
const updateManager = uni.getUpdateManager();
|
||||||
|
updateManager.onCheckForUpdate(function (res) {
|
||||||
|
// 请求完新版本信息的回调
|
||||||
|
if (res.hasUpdate) {
|
||||||
|
console.log("有新版本");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateManager.onUpdateReady(function () {
|
||||||
|
uni.showModal({
|
||||||
|
title: "更新提示",
|
||||||
|
content: "新版本已经准备好,是否重启应用?",
|
||||||
|
success: function (res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
||||||
|
updateManager.applyUpdate();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
updateManager.onUpdateFailed(function () {
|
||||||
|
// 新版本下载失败
|
||||||
|
uni.showModal({
|
||||||
|
title: "更新提示",
|
||||||
|
content: "新版本下载失败,请检查网络后重试",
|
||||||
|
showCancel: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShow: function () {
|
||||||
|
console.log("App Show");
|
||||||
|
const token = getCache("token");
|
||||||
|
if (!token || token === "" || token === null) {
|
||||||
|
uni.login({
|
||||||
|
provider: "weixin",
|
||||||
|
success: (loginRes) => {
|
||||||
|
if (loginRes.code) {
|
||||||
|
loginApi(
|
||||||
|
loginRes.code,
|
||||||
|
this.phoneCode,
|
||||||
|
"",
|
||||||
|
"微信用户" + Math.random().toString(36).substring(2)
|
||||||
|
).then((result) => {
|
||||||
|
setCache("token", result.token);
|
||||||
|
setCache("user_info", result.user_info);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("登录失败!" + loginRes.errMsg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHide: function () {
|
||||||
|
console.log("App Hide");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "uview-plus/index.scss";
|
||||||
|
</style>
|
||||||
114
api/interceptor.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
// 定义一个全局请求配置(可被多个拦截器修改)
|
||||||
|
import {setCache} from "@/utils/cache";
|
||||||
|
|
||||||
|
export let globalConfig = {
|
||||||
|
baseURL: 'https://wx.api.borman.top/api/',
|
||||||
|
// baseURL: 'http://brm.wx.api.borman.top/api/',
|
||||||
|
// baseURL: 'https://wx.boerman.top/api/',
|
||||||
|
// baseURL: 'http://127.0.0.1:18004/api/', // 本地测试地址
|
||||||
|
// baseURL: 'http://127.0.0.1:18006/api/', // 本地测试地址
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 请求拦截器队列
|
||||||
|
const requestInterceptors = [];
|
||||||
|
|
||||||
|
// 添加请求拦截器
|
||||||
|
export function addRequestInterceptor(interceptor) {
|
||||||
|
requestInterceptors.push(interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行请求拦截器
|
||||||
|
export async function runRequestInterceptors(config) {
|
||||||
|
let newConfig = { ...config };
|
||||||
|
for (const interceptor of requestInterceptors) {
|
||||||
|
newConfig = await interceptor(newConfig);
|
||||||
|
}
|
||||||
|
return newConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应拦截器队列
|
||||||
|
const responseInterceptors = [];
|
||||||
|
|
||||||
|
// 添加响应拦截器
|
||||||
|
export function addResponseInterceptor(interceptor) {
|
||||||
|
responseInterceptors.push(interceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行响应拦截器
|
||||||
|
export async function runResponseInterceptors(response) {
|
||||||
|
let newResponse = { ...response };
|
||||||
|
for (const interceptor of responseInterceptors) {
|
||||||
|
newResponse = await interceptor(newResponse);
|
||||||
|
}
|
||||||
|
return newResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化默认拦截器(可选)
|
||||||
|
addRequestInterceptor(config => {
|
||||||
|
// 自动携带token示例
|
||||||
|
const token = uni.getStorageSync('token');
|
||||||
|
// 临时token(测试用)
|
||||||
|
// const token = 'JG7hKhTQoANh/hyL67BzIg==';
|
||||||
|
if (token) {
|
||||||
|
config.header.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
addResponseInterceptor(response => {
|
||||||
|
// 统一处理错误状态码
|
||||||
|
if (response.statusCode !== 200) {
|
||||||
|
uni.showToast({ title: `请求错误: ${response.data.message}`, icon: 'none' });
|
||||||
|
return Promise.reject(response.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.code === 500) {
|
||||||
|
uni.showToast({ title: response.data.message, icon: 'none' });
|
||||||
|
return Promise.reject(response.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data.code === 401) {
|
||||||
|
uni.showToast({ title: '登录已过期,请重新登录', icon: 'none' });
|
||||||
|
setCache('token', null);
|
||||||
|
setTimeout(() => {
|
||||||
|
// 储存当前访问的路由
|
||||||
|
// 获取当前页面栈
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
if (pages.length === 0) {
|
||||||
|
console.error('未找到当前页面栈');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取当前页面实例
|
||||||
|
const currentPage = pages[pages.length - 1];
|
||||||
|
if (!currentPage) {
|
||||||
|
console.error('未找到当前页面实例');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取当前页面的路由地址
|
||||||
|
const currentRoute = currentPage.route;
|
||||||
|
if (!currentRoute) {
|
||||||
|
console.error('未找到当前页面的路由地址');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取传参
|
||||||
|
const options = currentPage.options;
|
||||||
|
// 构建包含参数的查询字符串
|
||||||
|
let queryString = '';
|
||||||
|
if (Object.keys(options).length > 0) {
|
||||||
|
queryString = '?' + Object.entries(options)
|
||||||
|
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
||||||
|
.join('&');
|
||||||
|
}
|
||||||
|
// 拼接完整的路由地址
|
||||||
|
const fullRoute = `/${currentRoute}${queryString}`;
|
||||||
|
// 设置缓存
|
||||||
|
setCache('currentPage', fullRoute);
|
||||||
|
uni.reLaunch({ url: '/pages/login/index' });
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.data.result; // 根据实际接口返回结构调整
|
||||||
|
});
|
||||||
11
api/page/auth.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import {get, post} from '../request';
|
||||||
|
|
||||||
|
const prefix = 'auth/'
|
||||||
|
export const loginApi = async (code, phoneCode, avatarUrl, nickName) => {
|
||||||
|
return await post(`${prefix}login`, {
|
||||||
|
code,
|
||||||
|
phone_code: phoneCode,
|
||||||
|
avatar: avatarUrl,
|
||||||
|
nick_name: nickName
|
||||||
|
})
|
||||||
|
}
|
||||||
30
api/page/cart.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import {get, post} from '../request';
|
||||||
|
|
||||||
|
const prefix = 'list/'
|
||||||
|
|
||||||
|
export const getCartListApi = async () => {
|
||||||
|
return await get(`${prefix}list`)
|
||||||
|
}
|
||||||
|
export const getCartItemApi = async (id) => {
|
||||||
|
return await get(`${prefix}detail`, {id})
|
||||||
|
}
|
||||||
|
export const createCartApi = async (params) => {
|
||||||
|
return await post(`${prefix}create`, params)
|
||||||
|
}
|
||||||
|
export const deleteCartApi = async (id) => {
|
||||||
|
return await post(`${prefix}delete`, {
|
||||||
|
ids: [id]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const deleteCartItemApi = async (id) => {
|
||||||
|
return await post(`${prefix}delete-item`, {
|
||||||
|
ids: [id]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const toCartApi = async (listId, productId) => {
|
||||||
|
return await post(`${prefix}to-cart`, {
|
||||||
|
list_id: listId,
|
||||||
|
product_id: productId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
10
api/page/home.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {get} from '../request';
|
||||||
|
|
||||||
|
const prefix = 'home/'
|
||||||
|
|
||||||
|
export const getCarouselApi = async () => {
|
||||||
|
return await get(`${prefix}carousel`)
|
||||||
|
}
|
||||||
|
export const getCategoryListApi = async () => {
|
||||||
|
return await get(`${prefix}category-list`)
|
||||||
|
}
|
||||||
15
api/page/image.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {get, post} from '../request';
|
||||||
|
|
||||||
|
const prefix = 'w-oss/'
|
||||||
|
|
||||||
|
export const getImageApi = async (hash = '') => {
|
||||||
|
return await get(`${prefix}getImage`, {
|
||||||
|
hash
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const uploadImageWatermarkApi = async (imageUrl = '', watermark = 'BRM') => {
|
||||||
|
return await post(`${prefix}watermark`, {
|
||||||
|
image_url: imageUrl,
|
||||||
|
watermark
|
||||||
|
})
|
||||||
|
}
|
||||||
14
api/page/product.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
import {get} from '../request';
|
||||||
|
|
||||||
|
const prefix = 'product/'
|
||||||
|
|
||||||
|
export const getProductListApi = async (param) => {
|
||||||
|
return await get(`${prefix}list`, param)
|
||||||
|
}
|
||||||
|
export const getCategoryListByPidApi = async (pid = 0) => {
|
||||||
|
return await get(`${prefix}category-list-by-pid`, {pid})
|
||||||
|
}
|
||||||
|
export const getProductDetailApi = async (id = 0, p_user_id = 0) => {
|
||||||
|
return await get(`${prefix}detail`, {id, p_user_id})
|
||||||
|
}
|
||||||
17
api/page/user.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {get, post} from '../request';
|
||||||
|
|
||||||
|
const prefix = 'user/'
|
||||||
|
|
||||||
|
export const getUserInfoApi = async () => {
|
||||||
|
return await get(`${prefix}my-info`)
|
||||||
|
}
|
||||||
|
export const bandPhoneApi = async (phone) => {
|
||||||
|
return await post(`${prefix}band-phone`, {
|
||||||
|
phone: phone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const updateNickNameApi = async (nick_name) => {
|
||||||
|
return await post(`${prefix}update-nick-name`, {
|
||||||
|
nick_name
|
||||||
|
})
|
||||||
|
}
|
||||||
70
api/request.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
runRequestInterceptors,
|
||||||
|
runResponseInterceptors,
|
||||||
|
globalConfig
|
||||||
|
} from './interceptor.js';
|
||||||
|
// import * as uni from "@/api/request"; // 原错误导入保留供参考
|
||||||
|
|
||||||
|
// 基础请求方法
|
||||||
|
export function request(options) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
// 合并全局配置
|
||||||
|
const mergedConfig = {
|
||||||
|
...globalConfig,
|
||||||
|
...options,
|
||||||
|
header: { ...globalConfig.header, ...(options.header || {}) }
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行请求拦截器
|
||||||
|
let finalConfig;
|
||||||
|
try {
|
||||||
|
finalConfig = await runRequestInterceptors(mergedConfig);
|
||||||
|
} catch (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!finalConfig.url) {
|
||||||
|
return reject(new Error('URL is required'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起请求(修复点:移除多余的await)
|
||||||
|
uni.request({
|
||||||
|
...finalConfig,
|
||||||
|
url: finalConfig.baseURL + finalConfig.url,
|
||||||
|
success: async (res) => {
|
||||||
|
try {
|
||||||
|
const handledRes = await runResponseInterceptors(res);
|
||||||
|
// console.log(handledRes, 'ssssssssssssssss'); // 保留调试日志
|
||||||
|
resolve(handledRes);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => reject(err)
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 封装GET方法(保持async/await结构)
|
||||||
|
export async function get(url, params = {}, options = {}) {
|
||||||
|
return await request({
|
||||||
|
url,
|
||||||
|
method: 'GET',
|
||||||
|
data: params,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 封装POST方法(保持async/await结构)
|
||||||
|
export async function post(url, data = {}, options = {}) {
|
||||||
|
return await request({
|
||||||
|
url,
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
3
common/api.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const { http } = uni.$u
|
||||||
|
// 获取菜单
|
||||||
|
export const fetchMenu = (params, config = {}) => http.post('/ebapi/public_api/index', params, config)
|
||||||
1
common/area.js
Normal file
1
common/city.js
Normal file
1087
common/classify.data.js
Normal file
3
common/config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default {
|
||||||
|
baseUrl: 'https://uview-plus.lingyun.net'
|
||||||
|
}
|
||||||
55
common/demo.scss
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
.u-block {
|
||||||
|
padding: 14px;
|
||||||
|
&__section {
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
&__title {
|
||||||
|
margin-top:10px;
|
||||||
|
font-size: 15px;
|
||||||
|
color: $u-content-color;
|
||||||
|
margin-bottom:10px;
|
||||||
|
}
|
||||||
|
&__flex {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
display: flex;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用了cell组件的icon图片样式
|
||||||
|
.u-cell-icon {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-page {
|
||||||
|
padding: 15px 15px 40px 15px;
|
||||||
|
&__item {
|
||||||
|
flex: 1;
|
||||||
|
// margin-bottom: 23px;
|
||||||
|
&__title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(143, 156, 162);
|
||||||
|
// margin-bottom: 8px;
|
||||||
|
@include flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-demo-block {
|
||||||
|
flex: 1;
|
||||||
|
margin-bottom: 23px;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
@include flex(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgb(143, 156, 162);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
@include flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
21
common/locales/en.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
// 可以以页面为单位来写,比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
|
||||||
|
components: {
|
||||||
|
desc: 'Numerous components cover the various requirements of the development process, and the components are rich in functions and compatible with multiple terminals. Let you integrate quickly, out of the box'
|
||||||
|
},
|
||||||
|
js: {
|
||||||
|
desc: 'Numerous intimate gadgets are a weapon that you can call upon during the development process, allowing you to dart in your hand and pierce the Yang with a hundred steps'
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
desc: 'Collection of many commonly used pages and layouts, reducing the repetitive work of developers, allowing you to focus on logic and get twice the result with half the effort'
|
||||||
|
},
|
||||||
|
nav: {
|
||||||
|
components: 'Components',
|
||||||
|
js: 'JS',
|
||||||
|
template: 'Template'
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
intro: 'UI framework for rapid development of multiple platforms',
|
||||||
|
title: 'uview-plus',
|
||||||
|
},
|
||||||
|
}
|
||||||
21
common/locales/zh.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
// 可以以页面为单位来写,比如首页的内容,写在index字段,个人中心写在center,共同部分写在common部分
|
||||||
|
components: {
|
||||||
|
desc: '众多组件覆盖开发过程的各个需求,组件功能丰富,多端兼容。让你快速集成,开箱即用'
|
||||||
|
},
|
||||||
|
js: {
|
||||||
|
desc: '众多的贴心小工具,是你开发过程中召之即来的利器,让你飞镖在手,百步穿杨'
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
desc: '收集众多的常用页面和布局,减少开发者的重复工作,让你专注逻辑,事半功倍'
|
||||||
|
},
|
||||||
|
nav: {
|
||||||
|
components: '组件',
|
||||||
|
js: '工具',
|
||||||
|
template: '模板'
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
intro: '多平台快速开发的UI框架',
|
||||||
|
title: 'uview-plus',
|
||||||
|
},
|
||||||
|
}
|
||||||
7
common/mixin.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isWeixin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
common/props.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
uni.$u.props.gap.bgColor = '#f3f4f6'
|
||||||
|
uni.$u.props.gap.height = '10'
|
||||||
1
common/province.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
var provinceData=[{"label":"北京市","value":"11"},{"label":"天津市","value":"12"},{"label":"河北省","value":"13"},{"label":"山西省","value":"14"},{"label":"内蒙古自治区","value":"15"},{"label":"辽宁省","value":"21"},{"label":"吉林省","value":"22"},{"label":"黑龙江省","value":"23"},{"label":"上海市","value":"31"},{"label":"江苏省","value":"32"},{"label":"浙江省","value":"33"},{"label":"安徽省","value":"34"},{"label":"福建省","value":"35"},{"label":"江西省","value":"36"},{"label":"山东省","value":"37"},{"label":"河南省","value":"41"},{"label":"湖北省","value":"42"},{"label":"湖南省","value":"43"},{"label":"广东省","value":"44"},{"label":"广西壮族自治区","value":"45"},{"label":"海南省","value":"46"},{"label":"重庆市","value":"50"},{"label":"四川省","value":"51"},{"label":"贵州省","value":"52"},{"label":"云南省","value":"53"},{"label":"西藏自治区","value":"54"},{"label":"陕西省","value":"61"},{"label":"甘肃省","value":"62"},{"label":"青海省","value":"63"},{"label":"宁夏回族自治区","value":"64"},{"label":"新疆维吾尔自治区","value":"65"},{"label":"台湾","value":"66"},{"label":"香港","value":"67"},{"label":"澳门","value":"68"}];export default provinceData;
|
||||||
22
components/common/Footer.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Footer"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="footer">
|
||||||
|
<view>各位经销商:</view>
|
||||||
|
<view>合作商请绑定公司名称与电话</view>
|
||||||
|
<view>并联系业务经理</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.footer {
|
||||||
|
color: #999;
|
||||||
|
margin: 50rpx auto;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 50rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
57
components/common/HeaderSearch.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "HeaderSearch",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keyword: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goSearch() {
|
||||||
|
console.log('跳转')
|
||||||
|
this.$u.route({
|
||||||
|
url: '/pages/search/search',
|
||||||
|
params: {
|
||||||
|
keyword: this.keyword
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onClickIcon() {
|
||||||
|
console.log('点击了扫码图标')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="header-search">
|
||||||
|
<view class="left-search">
|
||||||
|
<up-search searchIcon="scan"
|
||||||
|
class="search"
|
||||||
|
placeholder="输入你想查询的服务"
|
||||||
|
v-model="keyword"
|
||||||
|
:showAction="false"
|
||||||
|
@focus="goSearch"
|
||||||
|
@clickIcon="onClickIcon"
|
||||||
|
></up-search>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header-search {
|
||||||
|
width: 100%;
|
||||||
|
padding: 29rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-search {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
//width: 400rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
43
components/common/SearchMain.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "SearchMain",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keyword: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
searchList(value) {
|
||||||
|
console.log('搜索关键字22:' + value)
|
||||||
|
},
|
||||||
|
onClickIcon() {
|
||||||
|
console.log('点击了扫码图标')
|
||||||
|
},
|
||||||
|
rightClick() {
|
||||||
|
console.log('rightClick');
|
||||||
|
},
|
||||||
|
leftClick() {
|
||||||
|
console.log('leftClick');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<up-navbar
|
||||||
|
title="个人中心"
|
||||||
|
@rightClick="rightClick"
|
||||||
|
:autoBack="true"
|
||||||
|
>
|
||||||
|
</up-navbar>
|
||||||
|
<up-search class="search" searchIcon="scan" placeholder="输入你想查询的服务"
|
||||||
|
@clickIcon="onClickIcon" v-model="keyword" :showAction="true" :animation="false" :actionStyle="{}" @custom="searchList"></up-search>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
315
components/index/Index.vue
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
<script>
|
||||||
|
import Tabs from "./Tabs.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Index",
|
||||||
|
components: {Tabs},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
swiper: [
|
||||||
|
{ image: 'https://cdn.uviewui.com/uview/swiper/swiper2.png' },
|
||||||
|
{ image: 'https://cdn.uviewui.com/uview/swiper/swiper1.png' },
|
||||||
|
{ image: 'https://cdn.uviewui.com/uview/swiper/swiper3.png' },
|
||||||
|
{ image: 'https://cdn.uviewui.com/uview/swiper/swiper1.png' },
|
||||||
|
{ image: 'https://cdn.uviewui.com/uview/swiper/swiper3.png' }
|
||||||
|
],
|
||||||
|
swiperHeight: 190, // 默认高度
|
||||||
|
quickAccess: [
|
||||||
|
{ name: '余额查询', icon: 'red-packet-fill' },
|
||||||
|
{ name: '积分商城', icon: 'integral' },
|
||||||
|
// { name: '客服中心', icon: 'kefup-ermai' },
|
||||||
|
// { name: '优惠券', icon: 'coupon-fill' },
|
||||||
|
// { name: '礼券中心', icon: 'gift-fill' },
|
||||||
|
{ name: '预约列表', icon: 'calendar-fill' },
|
||||||
|
{ name: '我的订单', icon: 'order' },
|
||||||
|
// { name: '我的住址', icon: 'map-fill' },
|
||||||
|
{ name: '我的收藏', icon: 'star-fill' },
|
||||||
|
// { name: '常见问题', icon: 'question-circle' },
|
||||||
|
// { name: '问题反馈', icon: 'error-circle-fill' },
|
||||||
|
],
|
||||||
|
scrollList: [
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '全屋清洁',
|
||||||
|
price: '99.00',
|
||||||
|
tag: '热门',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '全屋清洁',
|
||||||
|
price: '99.00',
|
||||||
|
tag: '热门',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '全屋清洁',
|
||||||
|
price: '99.00',
|
||||||
|
tag: '热门',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '全屋清洁',
|
||||||
|
price: '99.00',
|
||||||
|
tag: '热门',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '全屋清洁',
|
||||||
|
price: '99.00',
|
||||||
|
tag: '热门',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '全屋清洁',
|
||||||
|
price: '99.00',
|
||||||
|
tag: '热门',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '全屋清洁',
|
||||||
|
price: '99.00',
|
||||||
|
tag: '热门',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '深度清洁',
|
||||||
|
price: '129.00',
|
||||||
|
tag: '新品',
|
||||||
|
purchased: false,
|
||||||
|
purchaseCount: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '除尘服务',
|
||||||
|
price: '89.00',
|
||||||
|
tag: '优惠',
|
||||||
|
purchased: false,
|
||||||
|
purchaseCount: 200
|
||||||
|
},
|
||||||
|
{
|
||||||
|
img: 'https://images.pexels.com/photos/9594679/pexels-photo-9594679.jpeg?auto=compress&cs=tinysrgb&w=600',
|
||||||
|
name: '家庭保洁',
|
||||||
|
price: '119.00',
|
||||||
|
tag: '推荐',
|
||||||
|
purchased: true,
|
||||||
|
purchaseCount: 150
|
||||||
|
},
|
||||||
|
],
|
||||||
|
itemsPerPage: 10 // 每页显示的项数
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
paginatedQuickAccess() {
|
||||||
|
const pages = [];
|
||||||
|
for (let i = 0; i < this.quickAccess.length; i += this.itemsPerPage) {
|
||||||
|
pages.push(this.quickAccess.slice(i, i + this.itemsPerPage));
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
console.log('onLoad');
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateSwiperHeight();
|
||||||
|
window.addEventListener('resize', this.updateSwiperHeight);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
window.removeEventListener('resize', this.updateSwiperHeight);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToDetail(item) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/info/index?name=${item.name}&price=${item.price}&img=${item.img}&tag=${item.tag}&purchaseCount=${item.purchaseCount}&purchased=${item.purchased}`
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updateSwiperHeight() {
|
||||||
|
const width = window.innerWidth;
|
||||||
|
if (width >= 1920) {
|
||||||
|
this.swiperHeight = 600;
|
||||||
|
} else if (width >= 1024) {
|
||||||
|
this.swiperHeight = 500;
|
||||||
|
} else if (width >= 768) {
|
||||||
|
this.swiperHeight = 300;
|
||||||
|
} else {
|
||||||
|
this.swiperHeight = 190;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 轮播图 -->
|
||||||
|
<view class="swiper-images">
|
||||||
|
<up-swiper
|
||||||
|
:list="swiper"
|
||||||
|
keyName="image"
|
||||||
|
circular
|
||||||
|
:height="swiperHeight"
|
||||||
|
indicatorActiveColor="#ff8800"
|
||||||
|
indicatorInactiveColor="rgba(255, 255, 255, 0.35)"
|
||||||
|
:indicatorStyle="{ right: '20rpx' }"
|
||||||
|
:circular="true"
|
||||||
|
:indicator="true"
|
||||||
|
></up-swiper>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 快捷入口 -->
|
||||||
|
<view class="quick-access">
|
||||||
|
<view class="title">全部功能</view>
|
||||||
|
<swiper :indicator-dots="true" indicator-active-color="#ff8800" class="swiper">
|
||||||
|
<swiper-item v-for="(page, pageIndex) in paginatedQuickAccess" :key="pageIndex">
|
||||||
|
<up-grid :border="true" col="5">
|
||||||
|
<up-grid-item
|
||||||
|
v-for="(item, index) in page"
|
||||||
|
:key="index"
|
||||||
|
style="border: none !important;"
|
||||||
|
>
|
||||||
|
<up-icon
|
||||||
|
:customStyle="{paddingTop: 20 + 'rpx', color: '#ff8800'}"
|
||||||
|
:name="item.icon"
|
||||||
|
:size="22"
|
||||||
|
></up-icon>
|
||||||
|
<text class="grid-text">{{ item.name }}</text>
|
||||||
|
</up-grid-item>
|
||||||
|
</up-grid>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="title">推荐服务</view>
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<view class="box">
|
||||||
|
|
||||||
|
<view class="lists">
|
||||||
|
<view class="h-item" v-for="(item, index) in scrollList" :key="index" @click="goToDetail(item)">
|
||||||
|
<view class="left">
|
||||||
|
<image :src="item.img" mode="aspectFill"></image>
|
||||||
|
</view>
|
||||||
|
<view class="info">
|
||||||
|
<view class="tit">
|
||||||
|
{{ item.name }}
|
||||||
|
<text v-if="item.purchased" class="purchased-tag">购买过</text>
|
||||||
|
</view>
|
||||||
|
<view class="price">价格: ¥{{ item.price }}</view>
|
||||||
|
<view class="purchase-count">购买人数: {{ item.purchaseCount }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="tag">{{ item.tag }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.swiper-images {
|
||||||
|
margin: 20rpx auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper {
|
||||||
|
border: none;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
box-shadow: 0 0 10rpx rgba(153, 153, 153, 0.3);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
padding: 10rpx 0 20rpx 0;
|
||||||
|
/* #ifndef APP-PLUS */
|
||||||
|
box-sizing: border-box;
|
||||||
|
/* #endif */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 服务列表 */
|
||||||
|
.box {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 30rpx 50rpx 30rpx 10rpx;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 0 10rpx rgba(153, 153, 153, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
margin-top: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-item {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
padding: 20rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
box-shadow: 0 0 10rpx rgba(153, 153, 153, 0.1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-item .left image {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-item .info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-item .info .tit {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-item .info .price {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-item .info .purchase-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
position: absolute;
|
||||||
|
right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h-item .tag {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ff8800;
|
||||||
|
background-color: #ffe4d2;
|
||||||
|
padding: 5rpx 10rpx;
|
||||||
|
border-radius: 5rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.purchased-tag {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #ff8800;
|
||||||
|
padding: 5rpx 10rpx;
|
||||||
|
border-radius: 5rpx;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
components/index/Tabs.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Tabs",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
name: "推荐",
|
||||||
|
icon: "home-o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "套餐",
|
||||||
|
icon: "apps-o"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "全屋清洁",
|
||||||
|
icon: "video-o"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click(index) {
|
||||||
|
console.log(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<up-tabs :list="tabs"
|
||||||
|
@click="click"
|
||||||
|
:activeStyle="{
|
||||||
|
color: '#303133',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
transform: 'scale(1.05)'
|
||||||
|
}"
|
||||||
|
lineColor="#f56c6c"
|
||||||
|
></up-tabs>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
227
components/tabbar/customTabbar.vue
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
<view class="tab-bar-container">
|
||||||
|
<view class="tab-bar">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in list"
|
||||||
|
:key="index"
|
||||||
|
:class="['tab-item', { active: currentTab === item.pagePath, activity: item.pagePath === '/pages/activity/activity' }]"
|
||||||
|
@click="switchTab(item.pagePath)"
|
||||||
|
>
|
||||||
|
<view class="icon-wrapper" :class="{ active: currentTab === item.pagePath }">
|
||||||
|
<image :src="currentTab === item.pagePath ? item.selectedIconPath : item.iconPath" />
|
||||||
|
<view class="text">{{ item.text }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="tab-bar-placeholder"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
selectedTab: {
|
||||||
|
type: String,
|
||||||
|
default: '/pages/home/home'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentTab: this.selectedTab,
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
text: '首页',
|
||||||
|
pagePath: '/pages/home/home',
|
||||||
|
iconPath: '/static/tabbar/home.png',
|
||||||
|
selectedIconPath: '/static/tabbar/home-fill.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '分类',
|
||||||
|
pagePath: '/pages/classify/classify',
|
||||||
|
iconPath: '/static/tabbar/classify.png',
|
||||||
|
selectedIconPath: '/static/tabbar/classify-fill.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '活动',
|
||||||
|
pagePath: '/pages/activity/activity',
|
||||||
|
iconPath: '/static/tabbar/activity.png',
|
||||||
|
selectedIconPath: '/static/tabbar/activity-fill.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '购物车',
|
||||||
|
pagePath: '/pages/shoppingCart/shoppingCart',
|
||||||
|
iconPath: '/static/tabbar/shopping-cart.png',
|
||||||
|
selectedIconPath: '/static/tabbar/shopping-cart-fill.png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '我的',
|
||||||
|
pagePath: '/pages/mine/index',
|
||||||
|
iconPath: '/static/tabbar/mine.png',
|
||||||
|
selectedIconPath: '/static/tabbar/mine-fill.png'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.updateCurrentTab();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
uni.hideTabBar();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
switchTab(url) {
|
||||||
|
if (this.currentTab !== url) {
|
||||||
|
this.currentTab = url;
|
||||||
|
uni.switchTab({ url });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCurrentTab() {
|
||||||
|
const currentPage = getCurrentPages().pop();
|
||||||
|
if (currentPage && currentPage.route) {
|
||||||
|
this.currentTab = `/${currentPage.route}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedTab(newVal) {
|
||||||
|
this.currentTab = newVal;
|
||||||
|
},
|
||||||
|
'$route'() {
|
||||||
|
this.updateCurrentTab();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.updateCurrentTab();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tab-bar-container {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
height: 70px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
color: #888;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wrapper.active {
|
||||||
|
transform: scale(1.2);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item image {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item .text {
|
||||||
|
margin-top: 3px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active .text {
|
||||||
|
color: #ff8800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.3s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity .icon-wrapper {
|
||||||
|
position: relative;
|
||||||
|
top: -20px;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity .icon-wrapper image {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-bar-placeholder {
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background: #ff8800;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-enter-active, .scale-leave-active {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-enter, .scale-leave-to /* .scale-leave-active in <2.1.8 */
|
||||||
|
{
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-leave-active {
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-enter-active, .slide-left-leave-active {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-left-enter, .slide-left-leave-to {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-enter-active, .slide-right-leave-active {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-right-enter, .slide-right-leave-to {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
index.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<script>
|
||||||
|
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
|
||||||
|
CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||||
|
</script>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
25
main.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import App from './App'
|
||||||
|
|
||||||
|
// #ifndef VUE3
|
||||||
|
import Vue from 'vue'
|
||||||
|
import './uni.promisify.adaptor'
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
App.mpType = 'app'
|
||||||
|
const app = new Vue({
|
||||||
|
...App
|
||||||
|
})
|
||||||
|
app.$mount()
|
||||||
|
// #endif
|
||||||
|
// main.js
|
||||||
|
import uviewPlus from 'uview-plus'
|
||||||
|
|
||||||
|
// #ifdef VUE3
|
||||||
|
import { createSSRApp } from 'vue'
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App)
|
||||||
|
app.use(uviewPlus)
|
||||||
|
return {
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
78
manifest.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"name" : "uview-shop",
|
||||||
|
"appid" : "__UNI__FC74E08",
|
||||||
|
"description" : "",
|
||||||
|
"versionName" : "1.0.0",
|
||||||
|
"versionCode" : "100",
|
||||||
|
"transformPx" : false,
|
||||||
|
/* 5+App特有相关 */
|
||||||
|
"app-plus" : {
|
||||||
|
"usingComponents" : true,
|
||||||
|
"nvueStyleCompiler" : "uni-app",
|
||||||
|
"compilerVersion" : 3,
|
||||||
|
"splashscreen" : {
|
||||||
|
"alwaysShowBeforeRender" : true,
|
||||||
|
"waiting" : true,
|
||||||
|
"autoclose" : true,
|
||||||
|
"delay" : 0
|
||||||
|
},
|
||||||
|
/* 模块配置 */
|
||||||
|
"modules" : {},
|
||||||
|
/* 应用发布信息 */
|
||||||
|
"distribute" : {
|
||||||
|
/* android打包配置 */
|
||||||
|
"android" : {
|
||||||
|
"permissions" : [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
/* ios打包配置 */
|
||||||
|
"ios" : {},
|
||||||
|
/* SDK配置 */
|
||||||
|
"sdkConfigs" : {},
|
||||||
|
"splashscreen" : {
|
||||||
|
"androidStyle" : "default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* 快应用特有相关 */
|
||||||
|
"quickapp" : {},
|
||||||
|
/* 小程序特有相关 */
|
||||||
|
"mp-weixin" : {
|
||||||
|
// "appid" : "wx679d36842570cea7", // 旧的铂尔曼
|
||||||
|
"appid" : "wx57b54060a579c31a", // 新的小程序
|
||||||
|
"setting" : {
|
||||||
|
"urlCheck" : false,
|
||||||
|
"minified" : true
|
||||||
|
},
|
||||||
|
// "resizable" : true,
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-alipay" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-baidu" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"mp-toutiao" : {
|
||||||
|
"usingComponents" : true
|
||||||
|
},
|
||||||
|
"uniStatistics" : {
|
||||||
|
"enable" : false
|
||||||
|
},
|
||||||
|
"vueVersion" : "3"
|
||||||
|
}
|
||||||
71
package-lock.json
generated
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "lgp-wx",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"clipboard": "^2.0.11",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"js-base64": "^3.7.7",
|
||||||
|
"uview-plus": "^3.3.36"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/clipboard": {
|
||||||
|
"version": "2.0.11",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"good-listener": "^1.2.2",
|
||||||
|
"select": "^1.1.2",
|
||||||
|
"tiny-emitter": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dayjs": {
|
||||||
|
"version": "1.11.13",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/delegate": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/good-listener": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delegate": "^3.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-base64": {
|
||||||
|
"version": "3.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz",
|
||||||
|
"integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/select": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/tiny-emitter": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/uview-plus": {
|
||||||
|
"version": "3.3.36",
|
||||||
|
"dependencies": {
|
||||||
|
"clipboard": "^2.0.11",
|
||||||
|
"dayjs": "^1.11.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"HBuilderX": "^3.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
package.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"clipboard": "^2.0.11",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"js-base64": "^3.7.7",
|
||||||
|
"uview-plus": "^3.3.36"
|
||||||
|
}
|
||||||
|
}
|
||||||
112
pages.json
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"easycom": {
|
||||||
|
"autoscan": true,
|
||||||
|
"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": [
|
||||||
|
{
|
||||||
|
"path": "pages/goods/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "家具优选"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "登录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/mine",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/my/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "个人信息"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/product/product",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "商品列表",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"backgroundTextStyle": "dark",
|
||||||
|
"backgroundColor": "#f8f8f8",
|
||||||
|
"onReachBottomDistance": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/product/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "商品详情",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/cart/cart",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/cart/detail",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "清单详情",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/search/search",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "搜索",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "uni-app",
|
||||||
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
},
|
||||||
|
"uniIdRouter": {},
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#7A7E83",
|
||||||
|
"selectedColor": "#3cc51f",
|
||||||
|
"backgroundColor": "#ffffff",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/goods/index",
|
||||||
|
"text": "首页",
|
||||||
|
"iconPath": "static/tabbar/v2/home.png",
|
||||||
|
"selectedIconPath": "static/tabbar/v2/home-fill.png"
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// "pagePath": "pages/goods/index",
|
||||||
|
// "text": "首页",
|
||||||
|
// "iconPath": "static/tabbar/classify.png",
|
||||||
|
// "selectedIconPath": "static/tabbar/classify-fill.png"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "pagePath": "pages/order/order",
|
||||||
|
// "text": "我的订单",
|
||||||
|
// "iconPath": "static/icon/order.png",
|
||||||
|
// "selectedIconPath": "static/icon/order.png"
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
"pagePath": "pages/mine/mine",
|
||||||
|
"text": "我",
|
||||||
|
"iconPath": "static/tabbar/v2/mine.png",
|
||||||
|
"selectedIconPath": "static/tabbar/v2/mine-fill.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
507
pages/cart/cart.vue
Normal file
@@ -0,0 +1,507 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- Header: 极简大气,与详情页保持一致 -->
|
||||||
|
<view class="page-header">
|
||||||
|
<view class="header-content">
|
||||||
|
<view class="title-group">
|
||||||
|
<text class="header-title">我的清单</text>
|
||||||
|
<text class="header-subtitle">My COLLECTIONS</text>
|
||||||
|
</view>
|
||||||
|
<view class="header-badges">
|
||||||
|
<text class="count-badge">共计: {{ lists.length }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- List Content -->
|
||||||
|
<scroll-view class="list-content" scroll-y @scrolltolower="loadMore">
|
||||||
|
<view v-if="lists.length > 0" class="list-wrapper">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in lists"
|
||||||
|
:key="index"
|
||||||
|
class="list-card animate-slide-in"
|
||||||
|
:style="{ animationDelay: index * 0.05 + 's' }"
|
||||||
|
@tap="goToDetail(item)"
|
||||||
|
>
|
||||||
|
<!-- 1. 卡片顶部工具栏:日期 + 删除 -->
|
||||||
|
<!-- 彻底移除了 u-swipe-action,解决编译报错 -->
|
||||||
|
<view class="card-top-bar">
|
||||||
|
<view class="top-left">
|
||||||
|
<u-icon name="calendar" color="#999" size="14"></u-icon>
|
||||||
|
<text class="date-text">{{ item.created_at }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 显性删除按钮:更直观,防误触 -->
|
||||||
|
<view class="delete-btn" @tap.stop="handleDelete(index, item.id)">
|
||||||
|
<u-icon name="trash" color="#ccc" size="18"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 2. 卡片主体内容 -->
|
||||||
|
<view class="card-body">
|
||||||
|
<view class="body-left">
|
||||||
|
<text class="item-name">{{ item.name }}</text>
|
||||||
|
<view class="item-remark" v-if="item.remark">
|
||||||
|
<text class="remark-line"></text>
|
||||||
|
<text class="remark-text">{{ item.remark }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-remark placeholder" v-else>
|
||||||
|
<text class="remark-text">暂无备注信息</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 右侧数量展示:更有设计感 -->
|
||||||
|
<view class="body-right">
|
||||||
|
<text class="count-label">商品数</text>
|
||||||
|
<text class="count-num">{{ item.count < 10 ? '0' + item.count : item.count }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 3. 底部装饰条 / 进入指引 -->
|
||||||
|
<view class="card-footer">
|
||||||
|
<text class="footer-status">查看详情</text>
|
||||||
|
<u-icon name="arrow-right" color="#B4854D" size="12"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="empty-state" v-else>
|
||||||
|
<image src="/static/images/empty.png" mode="widthFix" class="empty-img"></image>
|
||||||
|
<text class="empty-text">您还没有创建任何清单</text>
|
||||||
|
<text class="empty-sub">点击下方按钮开始创建</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 底部垫高 -->
|
||||||
|
<view style="height: 180rpx;"></view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- Create Button (Floating) -->
|
||||||
|
<view class="fab-container">
|
||||||
|
<view class="create-btn" @tap="showCreateModal" hover-class="btn-hover">
|
||||||
|
<u-icon name="plus" color="#fff" size="18"></u-icon>
|
||||||
|
<text>新建清单</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Create List Modal -->
|
||||||
|
<u-modal
|
||||||
|
:show="showModal"
|
||||||
|
:title="false"
|
||||||
|
:showCancelButton="true"
|
||||||
|
cancelText="取消"
|
||||||
|
confirmText="创建"
|
||||||
|
confirmColor="#B4854D"
|
||||||
|
@confirm="createList"
|
||||||
|
@cancel="cancelCreate"
|
||||||
|
class="custom-modal"
|
||||||
|
>
|
||||||
|
<view class="modal-content">
|
||||||
|
<view class="modal-header-deco">
|
||||||
|
<text class="modal-title">新建清单</text>
|
||||||
|
<text class="modal-sub">创建新的选品组合</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<u-form :model="formData" ref="uForm" :errorType="['message']">
|
||||||
|
<u-form-item prop="name" class="modal-input-item">
|
||||||
|
<u-input
|
||||||
|
v-model="formData.name"
|
||||||
|
placeholder="清单名称"
|
||||||
|
border="bottom"
|
||||||
|
class="custom-input"
|
||||||
|
/>
|
||||||
|
</u-form-item>
|
||||||
|
<u-form-item prop="remark" class="modal-input-item">
|
||||||
|
<u-input
|
||||||
|
v-model="formData.remark"
|
||||||
|
placeholder="备注信息 (选填)"
|
||||||
|
border="bottom"
|
||||||
|
class="custom-input"
|
||||||
|
/>
|
||||||
|
</u-form-item>
|
||||||
|
</u-form>
|
||||||
|
</view>
|
||||||
|
</u-modal>
|
||||||
|
|
||||||
|
<!-- Loading -->
|
||||||
|
<u-loading-page :loading="isLoading" bgColor="rgba(255,255,255,0.9)" color="#B4854D" loadingText="加载中..."></u-loading-page>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {createCartApi, deleteCartApi, getCartListApi} from "@/api/page/cart";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
showModal: false,
|
||||||
|
formData: {
|
||||||
|
name: '',
|
||||||
|
remark: ''
|
||||||
|
},
|
||||||
|
// swipeOptions 已移除,不再需要
|
||||||
|
lists: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigateBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
},
|
||||||
|
showCreateModal() {
|
||||||
|
this.showModal = true
|
||||||
|
},
|
||||||
|
cancelCreate() {
|
||||||
|
this.showModal = false
|
||||||
|
this.formData = { name: '', remark: '' }
|
||||||
|
},
|
||||||
|
getList() {
|
||||||
|
getCartListApi().then((res) => {
|
||||||
|
this.lists = res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
createList() {
|
||||||
|
if (!this.formData.name) {
|
||||||
|
uni.showToast({
|
||||||
|
title: '请输入清单名称',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createCartApi(this.formData).then((res) => {
|
||||||
|
this.showModal = false
|
||||||
|
this.formData = { name: '', remark: '' }
|
||||||
|
uni.showToast({
|
||||||
|
title: '已创建',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
this.getList();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
goToDetail(item) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/cart/detail?id=${item.id}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadMore() {
|
||||||
|
// 暂无更多逻辑
|
||||||
|
},
|
||||||
|
handleDelete(index, id) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '删除确认',
|
||||||
|
content: '确定要删除这个清单吗?此操作不可恢复。',
|
||||||
|
confirmText: '删除',
|
||||||
|
confirmColor: '#B4854D',
|
||||||
|
cancelColor: '#999',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
deleteCartApi(id).then(() => {
|
||||||
|
this.lists.splice(index, 1)
|
||||||
|
uni.showToast({
|
||||||
|
title: '已删除',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* 核心:Border-box 全局应用,防止布局错位 */
|
||||||
|
view, scroll-view, text, image {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #F7F8FA; /* 高级灰背景 */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 */
|
||||||
|
@keyframes slideInUp {
|
||||||
|
from { opacity: 0; transform: translateY(30rpx); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in {
|
||||||
|
animation: slideInUp 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.page-header {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
padding: 50rpx 40rpx 30rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
border-bottom: 1rpx solid rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #1A1A1A;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
display: block;
|
||||||
|
font-family: serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-subtitle {
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: #B4854D;
|
||||||
|
letter-spacing: 6rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-badge {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List Content */
|
||||||
|
.list-content {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 核心卡片样式 --- */
|
||||||
|
.list-card {
|
||||||
|
background: #fff;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.04);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1rpx solid rgba(0,0,0,0.02);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top Bar */
|
||||||
|
.card-top-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 30rpx;
|
||||||
|
border-bottom: 1rpx solid #F9F9F9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
padding: 10rpx;
|
||||||
|
margin-right: -10rpx; /* 增加点击热区修正视觉位置 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Body */
|
||||||
|
.card-body {
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-left {
|
||||||
|
flex: 1;
|
||||||
|
padding-right: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #222;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
font-family: serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-remark {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-line {
|
||||||
|
width: 20rpx;
|
||||||
|
height: 2rpx;
|
||||||
|
background-color: #B4854D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #888;
|
||||||
|
font-style: italic;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 300rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder .remark-text {
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧数量区 */
|
||||||
|
.body-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 30rpx;
|
||||||
|
border-left: 1rpx dashed #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-label {
|
||||||
|
font-size: 18rpx; /* 微调字体大小 */
|
||||||
|
color: #ccc;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-num {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 400; /* 数字细一点更高级 */
|
||||||
|
color: #B4854D;
|
||||||
|
font-family: 'Times New Roman', serif;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card Footer */
|
||||||
|
.card-footer {
|
||||||
|
background: #FDFDFD;
|
||||||
|
padding: 16rpx 30rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 1rpx solid #F9F9F9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-status {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #B4854D;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FAB */
|
||||||
|
.fab-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 60rpx;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-btn {
|
||||||
|
pointer-events: auto;
|
||||||
|
background: #222; /* 纯黑背景更显奢华 */
|
||||||
|
color: #B4854D; /* 金色文字 */
|
||||||
|
padding: 28rpx 70rpx;
|
||||||
|
border-radius: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
box-shadow: 0 20rpx 40rpx rgba(0,0,0,0.2);
|
||||||
|
transition: all 0.2s;
|
||||||
|
border: 1rpx solid #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hover {
|
||||||
|
transform: translateY(2rpx);
|
||||||
|
box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 200rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-img {
|
||||||
|
width: 200rpx;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
filter: grayscale(100%); /* 图片黑白化 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-sub {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #ccc;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Styling - 极简风 */
|
||||||
|
.modal-content {
|
||||||
|
padding: 50rpx 30rpx 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header-deco {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 300;
|
||||||
|
color: #333;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-sub {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-input {
|
||||||
|
padding: 20rpx 0 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
428
pages/cart/detail.vue
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 顶部 Header: 极简大气 -->
|
||||||
|
<view class="detail-header">
|
||||||
|
<view class="header-content">
|
||||||
|
<view class="title-group">
|
||||||
|
<text class="page-title">{{ listName }}</text>
|
||||||
|
<text class="page-subtitle">高端臻选系列</text>
|
||||||
|
</view>
|
||||||
|
<view class="header-badges">
|
||||||
|
<text class="count-badge">共计 {{ info.items ? info.items.length : 0 }} 件</text>
|
||||||
|
<text class="id-badge">清单号: {{ listId }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<scroll-view class="list-container" scroll-y>
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in info.items"
|
||||||
|
:key="index"
|
||||||
|
class="product-card animate-up"
|
||||||
|
:style="{ animationDelay: index * 0.05 + 's' }"
|
||||||
|
>
|
||||||
|
<!-- 1. 卡片顶部工具栏:此处替代了 u-swipe-action -->
|
||||||
|
<!-- 彻底移除了滑动组件,改为显性的删除按钮 -->
|
||||||
|
<view class="card-top-bar" @tap="navigateToDetail(item.product.id)">
|
||||||
|
<view class="top-left">
|
||||||
|
<text class="index-num">NO.{{ index + 1 < 10 ? '0' + (index + 1) : index + 1 }}</text>
|
||||||
|
<text class="divider-v">|</text>
|
||||||
|
<text class="status-text">已选配置</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="top-right">
|
||||||
|
<!-- 删除按钮 -->
|
||||||
|
<view class="action-btn delete" @tap.stop="handleDelete(index, item.id)">
|
||||||
|
<u-icon name="trash" color="#999" size="18"></u-icon>
|
||||||
|
</view>
|
||||||
|
<!-- 详情跳转图标 -->
|
||||||
|
<view class="action-btn arrow">
|
||||||
|
<u-icon name="arrow-right" color="#ccc" size="14"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 2. 产品主体内容:左图右文,极简排版 -->
|
||||||
|
<view class="card-body" @tap="navigateToDetail(item.product.id)">
|
||||||
|
<view class="image-wrapper">
|
||||||
|
<image
|
||||||
|
:src="item.product.cover"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="product-thumb"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="product-info">
|
||||||
|
<text class="p-title">{{ item.product.title }}</text>
|
||||||
|
<view class="p-tags">
|
||||||
|
<text class="luxury-tag">甄选家具</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 3. 规格清单:轻奢菜单风格 -->
|
||||||
|
<view class="specs-panel">
|
||||||
|
<view class="specs-inner">
|
||||||
|
<view
|
||||||
|
v-for="(spec, sIndex) in item.product.price_sheet"
|
||||||
|
:key="sIndex"
|
||||||
|
class="spec-item"
|
||||||
|
>
|
||||||
|
<view class="spec-header">
|
||||||
|
<text class="spec-label">{{ spec.specification }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="spec-detail-row">
|
||||||
|
<!-- 移除 nowrap 防止长文本撑开 -->
|
||||||
|
<text class="dim-text" v-if="spec.dimension">{{ spec.dimension }}</text>
|
||||||
|
<view class="dashed-line"></view>
|
||||||
|
<view class="price-group">
|
||||||
|
<view v-for="(pItem, pIndex) in spec.routine" :key="pIndex" class="price-unit">
|
||||||
|
<text class="currency">¥</text>{{ pItem }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="safe-bottom"></view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<u-toast ref="uToast"></u-toast>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {deleteCartItemApi, getCartItemApi} from "@/api/page/cart";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
listId: '',
|
||||||
|
listName: '清单详情',
|
||||||
|
info: { items: [] },
|
||||||
|
// swipeOptions 已彻底移除
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
this.listId = options.id
|
||||||
|
this.getInfo();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleDelete(index, id) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '移除确认',
|
||||||
|
content: '将从您的甄选清单中移除此商品',
|
||||||
|
confirmText: '移除',
|
||||||
|
confirmColor: '#B4854D', // 金色确认按钮
|
||||||
|
cancelColor: '#999999',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
deleteCartItemApi(id).then(() => {
|
||||||
|
this.info.items.splice(index, 1)
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
type: 'success',
|
||||||
|
message: '已移除'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getInfo() {
|
||||||
|
uni.showLoading({ title: '加载中...' }) // 中文Loading
|
||||||
|
getCartItemApi(this.listId).then((res) => {
|
||||||
|
this.info = res || { items: [] };
|
||||||
|
uni.hideLoading()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
navigateToDetail(id) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/product/detail?id=${id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
/* 核心修复:强制使用 border-box 盒模型,防止 padding 撑开宽度 */
|
||||||
|
view, scroll-view, text, image {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全局容器:高级灰背景 */
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%; /* 确保容器宽度 */
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-x: hidden; /* 防止水平溢出 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from { opacity: 0; transform: translateY(30rpx); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.animate-up { animation: slideUp 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards; }
|
||||||
|
|
||||||
|
/* 1. Header 区域:极简,大量留白 */
|
||||||
|
.detail-header {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
padding: 50rpx 40rpx 30rpx;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 300; /* 细字体显得更高级 */
|
||||||
|
color: #1A1A1A;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
font-family: serif; /* 尝试使用衬线风格(取决于系统支持) */
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-subtitle {
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: #B4854D; /* 金色副标题 */
|
||||||
|
letter-spacing: 6rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-badges {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-badge, .id-badge {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 列表容器 */
|
||||||
|
.list-container {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%; /* 明确宽度 */
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 商品卡片:核心设计 */
|
||||||
|
.product-card {
|
||||||
|
width: 100%; /* 确保卡片不超出父容器 */
|
||||||
|
background: #fff;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
border-radius: 12rpx; /* 不需要太大的圆角 */
|
||||||
|
box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.04); /* 大而柔和的阴影 */
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
border: 1rpx solid rgba(0,0,0,0.02); /* 极细的边框 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- 卡片顶部工具栏 -- */
|
||||||
|
.card-top-bar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 30rpx;
|
||||||
|
border-bottom: 1rpx solid #F5F5F5;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.index-num {
|
||||||
|
font-family: 'Times New Roman', serif;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-v {
|
||||||
|
margin: 0 16rpx;
|
||||||
|
color: #eee;
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 30rpx; /* 按钮之间拉开距离 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- 卡片主体 (图片+标题) -- */
|
||||||
|
.card-body {
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* 垂直居中 */
|
||||||
|
gap: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #F9F9F9;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
|
||||||
|
flex-shrink: 0; /* 防止图片被压缩 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-thumb {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden; /* 防止文字过长溢出 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #222;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
/* 增加截断,防止超长标题撑开 */
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-tags {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.luxury-tag {
|
||||||
|
font-size: 18rpx;
|
||||||
|
color: #B4854D;
|
||||||
|
background: rgba(180, 133, 77, 0.08); /* 极淡的金色背景 */
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -- 规格清单面板 (菜单式) -- */
|
||||||
|
.specs-panel {
|
||||||
|
padding: 0 30rpx 40rpx; /* 底部留白 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs-inner {
|
||||||
|
background: #FAFAFA; /* 浅灰底 */
|
||||||
|
border: 1rpx solid #EFEFEF;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-item {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-item:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-header {
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-detail-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dim-text {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #888;
|
||||||
|
/* 移除 nowrap,允许换行,防止长尺寸描述撑开容器 */
|
||||||
|
/* white-space: nowrap; */
|
||||||
|
padding-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 虚线连接符 */
|
||||||
|
.dashed-line {
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
border-bottom: 2rpx dashed #E0E0E0;
|
||||||
|
margin: 0 16rpx;
|
||||||
|
position: relative;
|
||||||
|
top: -6rpx; /* 微调对齐 */
|
||||||
|
min-width: 40rpx; /* 保证至少有一点虚线 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
flex-shrink: 0; /* 防止价格被挤压 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-unit {
|
||||||
|
font-family: 'Times New Roman', serif; /* 衬线体价格,显贵 */
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #B4854D;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency {
|
||||||
|
font-size: 20rpx;
|
||||||
|
margin-right: 4rpx;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-bottom {
|
||||||
|
height: 60rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
264
pages/goods/index.vue
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
|
||||||
|
<!-- 轮播图优化 -->
|
||||||
|
<view class="hero-banner">
|
||||||
|
<swiper class="product-swiper" circular autoplay interval="5000" duration="800">
|
||||||
|
<swiper-item v-for="(item, index) in carousel" :key="index" class="swiper-item-box">
|
||||||
|
<view class="image-wrapper">
|
||||||
|
<image :src="item.url" mode="aspectFill" class="swiper-image" />
|
||||||
|
<view class="swiper-overlay"></view>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Search Bar (悬浮毛玻璃风格) -->
|
||||||
|
<view class="search-wrapper">
|
||||||
|
<view class="search-box">
|
||||||
|
<up-search
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索心仪的产品型号"
|
||||||
|
placeholder-class="search-placeholder"
|
||||||
|
v-model="title"
|
||||||
|
:showAction="true"
|
||||||
|
:animation="true"
|
||||||
|
actionText="搜索"
|
||||||
|
:disabled="true"
|
||||||
|
bgColor="#f5f5f5"
|
||||||
|
@search="handleSearch"
|
||||||
|
@click="handleSearch"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 分类展示优化 (回归三列布局) -->
|
||||||
|
<view class="category-wrapper">
|
||||||
|
<view class="section-header animate-fade-in-up">
|
||||||
|
<text class="title-en">CATEGORIES</text>
|
||||||
|
<text class="title-cn">产品分类</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="category-grid">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in categories"
|
||||||
|
:key="index"
|
||||||
|
class="category-card animate-fade-in-up"
|
||||||
|
:style="{ animationDelay: index * 0.05 + 's' }"
|
||||||
|
@tap="navigateToList(item.id)"
|
||||||
|
>
|
||||||
|
<view class="category-image-box">
|
||||||
|
<image :src="item.url" mode="aspectFill" class="cat-img"></image>
|
||||||
|
<view class="cat-mask"></view>
|
||||||
|
</view>
|
||||||
|
<view class="category-info">
|
||||||
|
<text class="category-name">{{ item.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {getCarouselApi, getCategoryListApi} from "@/api/page/home";
|
||||||
|
import {getCache} from "@/utils/cache";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
categoriesList: [],
|
||||||
|
categories: [],
|
||||||
|
carousel: [],
|
||||||
|
title: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
const userInfo = getCache('user_info')
|
||||||
|
if (userInfo) {
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: userInfo?.enterprise.name || '家具优选'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShareAppMessage() {
|
||||||
|
const userInfo = getCache('user_info')
|
||||||
|
return {
|
||||||
|
title: userInfo?.enterprise.name || '家具优选',
|
||||||
|
path: `pages/goods/index`,
|
||||||
|
imageUrl: 'http://qiniu.boerman.top/b_a2d78f027b3d26c0b0621655726c0c54.jpg'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getCarousel();
|
||||||
|
this.getCategoryList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigateToList(pid) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/product/product?pid=${pid}`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getCarousel() {
|
||||||
|
getCarouselApi().then((res) => {
|
||||||
|
this.carousel = res;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getCategoryList() {
|
||||||
|
getCategoryListApi().then((res) => {
|
||||||
|
this.categories = res;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/search/search?title=${this.title}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from { opacity: 0; transform: translate3d(0, 40rpx, 0); }
|
||||||
|
to { opacity: 1; transform: translate3d(0, 0, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-up {
|
||||||
|
animation: fadeInUp 0.8s cubic-bezier(0.2, 0.8, 0.2, 1) forwards;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-wrapper {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
border-radius: 40rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-banner {
|
||||||
|
position: relative;
|
||||||
|
height: 400rpx;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-swiper {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(to bottom, rgba(0,0,0,0) 80%, rgba(0,0,0,0.3) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-wrapper {
|
||||||
|
padding: 40rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
padding-left: 10rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-en {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #B4854D;
|
||||||
|
letter-spacing: 4rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-cn {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3列布局 */
|
||||||
|
.category-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
|
||||||
|
position: relative;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-card:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-image-box {
|
||||||
|
height: 180rpx;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-mask {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-info {
|
||||||
|
padding: 16rpx 10rpx;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
183
pages/login/index.vue
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
<template>
|
||||||
|
<view class="login-container">
|
||||||
|
<view class="logo-area">
|
||||||
|
<image
|
||||||
|
src="/static/images/logo.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="logo-image"
|
||||||
|
/>
|
||||||
|
<text class="logo-text">佛山家具工厂</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="login-form">
|
||||||
|
<view class="form-title">微信一键登录</view>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="wechat-login-btn"
|
||||||
|
@click="handleAuth"
|
||||||
|
>
|
||||||
|
<u-icon name="weixin-fill" color="#ffffff" size="28"></u-icon>
|
||||||
|
<text>一键登录</text>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<u-toast ref="uToast"></u-toast>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {loginApi} from "@/api/page/auth";
|
||||||
|
import {getCache, setCache} from "@/utils/cache";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
phoneCode: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleAuth() {
|
||||||
|
// loading
|
||||||
|
this.$refs.uToast.show({
|
||||||
|
type: 'loading',
|
||||||
|
message: '正在登录...'
|
||||||
|
})
|
||||||
|
// 获取用户信息
|
||||||
|
|
||||||
|
uni.login({
|
||||||
|
provider: 'weixin',
|
||||||
|
success: loginRes => {
|
||||||
|
if (loginRes.code) {
|
||||||
|
loginApi(
|
||||||
|
loginRes.code,
|
||||||
|
this.phoneCode,
|
||||||
|
'',
|
||||||
|
'微信用户' + Math.random().toString(36).substring(2)
|
||||||
|
).then((result) => {
|
||||||
|
console.log('登录成功', result);
|
||||||
|
setCache('token', result.token)
|
||||||
|
setCache('user_info', result.user_info)
|
||||||
|
uni.showToast({
|
||||||
|
title: '登录成功',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
uni.hideLoading()
|
||||||
|
setTimeout(() => {
|
||||||
|
let url = getCache('currentPage')
|
||||||
|
if (url) {
|
||||||
|
setCache('currentPage', '')
|
||||||
|
} else {
|
||||||
|
url = '/pages/goods/index'
|
||||||
|
}
|
||||||
|
uni.reLaunch({
|
||||||
|
url: url
|
||||||
|
})
|
||||||
|
}, 1000)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error('登录失败!' + loginRes.errMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.login-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 60rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 80rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-image {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 180rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #333333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
padding: 0 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-title {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #333333;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechat-login-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #B4854D 0%, #8C6239 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
margin: 60rpx 0;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(180, 133, 77, 0.2);
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
margin-left: 12rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agreement-text {
|
||||||
|
margin-left: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #B4854D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-content {
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333333;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666666;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
360
pages/mine/mine.vue
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<!-- 顶部背景 -->
|
||||||
|
<view class="header-bg"></view>
|
||||||
|
|
||||||
|
<view class="content">
|
||||||
|
<!-- 用户信息卡片 -->
|
||||||
|
<view class="user-card animate-slide-down">
|
||||||
|
<view class="user-info-row" @click="handleUserClick">
|
||||||
|
<view class="avatar-wrapper">
|
||||||
|
<image
|
||||||
|
:src="userInfo.avatar || 'https://img2.baidu.com/it/u=2953585264,744730101&fm=253&fmt=auto&app=138&f=JPEG?w=360&h=360'"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="avatar"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="info-text">
|
||||||
|
<view class="nickname">{{ userInfo.nick_name || '登录 / 注册' }}</view>
|
||||||
|
<view class="subtitle" v-if="userInfo.id">ID: {{ userInfo.id }}</view>
|
||||||
|
<view class="subtitle" v-else>点击登录开启家具选购之旅</view>
|
||||||
|
</view>
|
||||||
|
<view class="arrow-icon">
|
||||||
|
<u-icon name="arrow-right" color="#ccc" size="16"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 统计数据 -->
|
||||||
|
<view class="stats-row">
|
||||||
|
<view class="stat-item">
|
||||||
|
<!-- 确保 count 字段正确绑定 -->
|
||||||
|
<view class="num">{{ count }}</view>
|
||||||
|
<view class="label">当前清单数量</view>
|
||||||
|
</view>
|
||||||
|
<view class="vertical-line"></view>
|
||||||
|
<view class="stat-item" @click="goCart">
|
||||||
|
<view class="action-text">去查看</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 企业卡片 -->
|
||||||
|
<view v-if="userInfo && userInfo.enterprise_id" class="enterprise-card animate-fade-in" @click="goEnterpriseDetail">
|
||||||
|
<image class="ent-logo" :src="userInfo.enterprise?.logo" mode="aspectFill"></image>
|
||||||
|
<view class="ent-info">
|
||||||
|
<text class="ent-name">{{ userInfo.enterprise?.name }}</text>
|
||||||
|
<text class="ent-tag">已绑定企业账户</text>
|
||||||
|
</view>
|
||||||
|
<u-icon name="arrow-right" color="rgba(255,255,255,0.6)" size="14"></u-icon>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 菜单列表 -->
|
||||||
|
<view class="menu-group animate-fade-in">
|
||||||
|
<view class="menu-item" @click="goCart">
|
||||||
|
<view class="item-left">
|
||||||
|
<image src="/static/icon/await.png" class="menu-icon" mode="aspectFit"></image>
|
||||||
|
<text>我的清单</text>
|
||||||
|
</view>
|
||||||
|
<u-icon name="arrow-right" color="#ccc" size="14"></u-icon>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="menu-item" @click="goMy">
|
||||||
|
<view class="item-left">
|
||||||
|
<u-icon name="setting-fill" color="#B4854D" size="20" class="u-icon-fix"></u-icon>
|
||||||
|
<text style="margin-left: 20rpx;">个人设置</text>
|
||||||
|
</view>
|
||||||
|
<u-icon name="arrow-right" color="#ccc" size="14"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CustomTabBar from "../../components/tabbar/customTabbar.vue";
|
||||||
|
import Footer from "../../components/common/Footer.vue";
|
||||||
|
import {getCache} from "@/utils/cache";
|
||||||
|
import {getUserInfoApi} from "@/api/page/user";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { Footer, CustomTabBar },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
userInfo: {},
|
||||||
|
count: 0,
|
||||||
|
indexList: [
|
||||||
|
// {
|
||||||
|
// name: '用户反馈',
|
||||||
|
// icon: {
|
||||||
|
// color: '#ff8800',
|
||||||
|
// size: '26',
|
||||||
|
// type: 'info-circle'
|
||||||
|
// },
|
||||||
|
// page: '/pages/feedback/feedback'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: '我的邮件',
|
||||||
|
// icon: {
|
||||||
|
// color: '#ff8800',
|
||||||
|
// size: '26',
|
||||||
|
// type: 'email'
|
||||||
|
// },
|
||||||
|
// page: '/pages/email/email'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: '分享有礼',
|
||||||
|
// icon: {
|
||||||
|
// color: '#ff8800',
|
||||||
|
// size: '26',
|
||||||
|
// type: 'gift'
|
||||||
|
// },
|
||||||
|
// page: '/pages/share/share'
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.checkLoginAndLoad();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkLoginAndLoad() {
|
||||||
|
// 优先从缓存读取以快速渲染
|
||||||
|
// const cachedUser = getCache('user_info');
|
||||||
|
// if (cachedUser) {
|
||||||
|
// this.userInfo = cachedUser;
|
||||||
|
// }
|
||||||
|
// 调用接口获取最新数据(包括count)
|
||||||
|
this.getMyInfo();
|
||||||
|
},
|
||||||
|
getMyInfo() {
|
||||||
|
getUserInfoApi().then((res) => {
|
||||||
|
console.log("UserInfo API Response:", res);
|
||||||
|
// 强制确保 count 赋值
|
||||||
|
this.count = res.count;
|
||||||
|
this.userInfo = res.user;
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Get user info failed:", err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleUserClick() {
|
||||||
|
if (!this.userInfo.id) {
|
||||||
|
this.loginFun();
|
||||||
|
} else {
|
||||||
|
this.goMy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loginFun() {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index' });
|
||||||
|
},
|
||||||
|
goMy() {
|
||||||
|
uni.navigateTo({ url: '/pages/my/index' });
|
||||||
|
},
|
||||||
|
goCart() {
|
||||||
|
uni.navigateTo({ url: '/pages/cart/cart' });
|
||||||
|
},
|
||||||
|
goEnterpriseDetail() {
|
||||||
|
// 企业详情跳转逻辑
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F5F7FA;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 350rpx;
|
||||||
|
background: linear-gradient(135deg, #E6D5C1 0%, #F5F7FA 100%);
|
||||||
|
z-index: 0;
|
||||||
|
border-radius: 0 0 40rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 140rpx 30rpx 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* User Card */
|
||||||
|
.user-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(180, 133, 77, 0.08);
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
margin-right: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4rpx solid #fff;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-text {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nickname {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 30rpx;
|
||||||
|
border-top: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.num {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #B4854D;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-line {
|
||||||
|
width: 2rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-text {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12rpx 32rpx;
|
||||||
|
background: #333;
|
||||||
|
color: #D4AF37;
|
||||||
|
font-size: 24rpx;
|
||||||
|
border-radius: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enterprise Card */
|
||||||
|
.enterprise-card {
|
||||||
|
background: linear-gradient(135deg, #2c2c2c 0%, #4a4a4a 100%);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ent-logo {
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #fff;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ent-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ent-name {
|
||||||
|
color: #ECD1A8;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ent-tag {
|
||||||
|
color: rgba(236, 209, 168, 0.6);
|
||||||
|
font-size: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu Group */
|
||||||
|
.menu-group {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 10rpx 0;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item:active {
|
||||||
|
background: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-icon-fix {
|
||||||
|
width: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes slideDown {
|
||||||
|
from { opacity: 0; transform: translateY(-20rpx); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-down { animation: slideDown 0.6s cubic-bezier(0.2, 0.8, 0.2, 1); }
|
||||||
|
.animate-fade-in { animation: fadeIn 0.8s ease-out; }
|
||||||
|
</style>
|
||||||
225
pages/my/index.vue
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page-container">
|
||||||
|
<view class="content-wrapper animate-enter">
|
||||||
|
<view class="header">
|
||||||
|
<text class="title">个人信息</text>
|
||||||
|
<text class="subtitle">维护您的基本资料</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 信息展示列表 -->
|
||||||
|
<view class="info-list">
|
||||||
|
<!-- 昵称 -->
|
||||||
|
<view class="info-item" @tap="openNicknameModal">
|
||||||
|
<view class="item-left">
|
||||||
|
<text class="label">用户昵称</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-right">
|
||||||
|
<text class="value">{{ nickname || '未设置' }}</text>
|
||||||
|
<u-icon name="arrow-right" color="#ccc" size="14"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="divider"></view>
|
||||||
|
|
||||||
|
<!-- 手机号 -->
|
||||||
|
<view class="info-item" @tap="openPhoneModal">
|
||||||
|
<view class="item-left">
|
||||||
|
<text class="label">手机号码</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-right">
|
||||||
|
<text class="value">{{ phone || '未绑定' }}</text>
|
||||||
|
<u-icon name="arrow-right" color="#ccc" size="14"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="footer-action">
|
||||||
|
<button class="logout-btn" @click="logout" hover-class="btn-hover">
|
||||||
|
退出登录
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Modal: 修改昵称 -->
|
||||||
|
<u-modal
|
||||||
|
:show="showNicknameModal"
|
||||||
|
title="修改昵称"
|
||||||
|
:showCancelButton="true"
|
||||||
|
cancelText="取消"
|
||||||
|
confirmText="保存"
|
||||||
|
confirmColor="#B4854D"
|
||||||
|
@cancel="showNicknameModal = false"
|
||||||
|
@confirm="saveNickname"
|
||||||
|
>
|
||||||
|
<view class="modal-input-wrapper">
|
||||||
|
<u-input
|
||||||
|
v-model="tempNickname"
|
||||||
|
placeholder="请输入新昵称"
|
||||||
|
border="surround"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</u-modal>
|
||||||
|
|
||||||
|
<!-- Modal: 绑定手机 (手动输入) -->
|
||||||
|
<u-modal
|
||||||
|
:show="showPhoneModal"
|
||||||
|
title="绑定手机号"
|
||||||
|
:showCancelButton="true"
|
||||||
|
cancelText="取消"
|
||||||
|
confirmText="保存"
|
||||||
|
confirmColor="#B4854D"
|
||||||
|
@cancel="showPhoneModal = false"
|
||||||
|
@confirm="savePhone"
|
||||||
|
>
|
||||||
|
<view class="modal-input-wrapper">
|
||||||
|
<u-input
|
||||||
|
v-model="tempPhone"
|
||||||
|
placeholder="请输入手机号码"
|
||||||
|
type="number"
|
||||||
|
border="surround"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
<view class="tip-text">请确保输入真实的手机号码</view>
|
||||||
|
</view>
|
||||||
|
</u-modal>
|
||||||
|
|
||||||
|
<u-toast ref="uToast"></u-toast>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { bandPhoneApi, updateNickNameApi, getUserInfoApi } from "@/api/page/user";
|
||||||
|
import {getCache, setCache} from "@/utils/cache";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
nickname: '',
|
||||||
|
phone: '',
|
||||||
|
|
||||||
|
// Modal States
|
||||||
|
showNicknameModal: false,
|
||||||
|
tempNickname: '',
|
||||||
|
|
||||||
|
showPhoneModal: false,
|
||||||
|
tempPhone: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.initUserInfo()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async initUserInfo() {
|
||||||
|
try {
|
||||||
|
const res = await getUserInfoApi();
|
||||||
|
if(res.user) {
|
||||||
|
this.nickname = res.user.nick_name;
|
||||||
|
this.phone = res.user.phone;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openNicknameModal() {
|
||||||
|
this.tempNickname = this.nickname;
|
||||||
|
this.showNicknameModal = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
openPhoneModal() {
|
||||||
|
this.tempPhone = this.phone;
|
||||||
|
this.showPhoneModal = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveNickname() {
|
||||||
|
if(!this.tempNickname.trim()) {
|
||||||
|
this.showNicknameModal = false;
|
||||||
|
return this.$refs.uToast.show({ type: 'error', message: '昵称不能为空' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateNickNameApi(this.tempNickname).then(() => {
|
||||||
|
this.nickname = this.tempNickname;
|
||||||
|
this.updateCache('nick_name', this.nickname);
|
||||||
|
this.showNicknameModal = false;
|
||||||
|
this.$refs.uToast.show({ type: 'success', message: '保存成功' });
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.showNicknameModal = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 绑定手动输入的手机号
|
||||||
|
async savePhone() {
|
||||||
|
if(!this.tempPhone || this.tempPhone.length < 11) {
|
||||||
|
this.showPhoneModal = false;
|
||||||
|
return this.$refs.uToast.show({ type: 'error', message: '请输入有效的手机号' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
bandPhoneApi(this.tempPhone).then(() => {
|
||||||
|
this.phone = this.tempPhone;
|
||||||
|
this.updateCache('phone', this.phone);
|
||||||
|
this.showPhoneModal = false;
|
||||||
|
this.$refs.uToast.show({ type: 'success', message: '绑定成功' });
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.showPhoneModal = false;
|
||||||
|
this.$refs.uToast.show({ type: 'error', message: '绑定失败' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCache(key, value) {
|
||||||
|
let userInfo = getCache('user_info') || {};
|
||||||
|
userInfo[key] = value;
|
||||||
|
setCache('user_info', userInfo);
|
||||||
|
},
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '确定要退出登录吗?',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.removeStorageSync('token');
|
||||||
|
uni.removeStorageSync('user_info');
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/index'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.page-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-enter { animation: slideUp 0.6s ease-out; }
|
||||||
|
@keyframes slideUp { from { opacity: 0; transform: translateY(30rpx); } to { opacity: 1; transform: translateY(0); } }
|
||||||
|
|
||||||
|
.header { margin-bottom: 40rpx; padding-left: 20rpx; }
|
||||||
|
.title { font-size: 40rpx; font-weight: 700; color: #333; margin-bottom: 8rpx; display: block; }
|
||||||
|
.subtitle { font-size: 26rpx; color: #999; }
|
||||||
|
|
||||||
|
/* Info List */
|
||||||
|
.info-list { background: #fff; border-radius: 24rpx; padding: 0 30rpx; box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.03); }
|
||||||
|
.info-item { display: flex; justify-content: space-between; align-items: center; height: 120rpx; }
|
||||||
|
.divider { height: 1rpx; background: #f5f5f5; }
|
||||||
|
.item-left .label { font-size: 30rpx; color: #333; font-weight: 500; }
|
||||||
|
.item-right { display: flex; align-items: center; gap: 16rpx; }
|
||||||
|
.item-right .value { font-size: 28rpx; color: #666; }
|
||||||
|
|
||||||
|
.modal-input-wrapper { padding: 30rpx 10rpx; }
|
||||||
|
.tip-text { font-size: 24rpx; color: #999; margin-top: 20rpx; text-align: center; }
|
||||||
|
|
||||||
|
.footer-action { margin-top: 60rpx; }
|
||||||
|
.logout-btn { background: #fff; color: #ff4444; font-size: 30rpx; height: 88rpx; border-radius: 44rpx; box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05); }
|
||||||
|
</style>
|
||||||
471
pages/product/detail.vue
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- 产品轮播图 -->
|
||||||
|
<view class="swiper-container">
|
||||||
|
<swiper class="product-swiper" indicator-dots indicator-active-color="#B4854D" circular>
|
||||||
|
<swiper-item v-for="(image, index) in product.carousel" :key="index">
|
||||||
|
<image :src="image" mode="aspectFill" @click="previewImages(image)" class="product-image" />
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 悬浮分享按钮 (Fixed Positioning) -->
|
||||||
|
<button class="share-fixed-btn animate-scale" open-type="share" hover-class="none">
|
||||||
|
<u-icon name="share-fill" color="#333" size="20"></u-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 产品信息卡片 (移除负边距,防止遮挡) -->
|
||||||
|
<view class="info-card animate-slide-up">
|
||||||
|
<view class="title-section">
|
||||||
|
<text class="product-code">{{ product.title }}</text>
|
||||||
|
<view class="product-alias animate-fade-in" v-if="product.alias">
|
||||||
|
<rich-text :nodes="product.alias"></rich-text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 规格表格 -->
|
||||||
|
<view class="specs-section">
|
||||||
|
<view class="section-title">规格参数</view>
|
||||||
|
<view class="specs-table">
|
||||||
|
<view :class="['specs-header', product.is_show_price ? 'cols-2' : 'cols-1']">
|
||||||
|
<text>规格 / 尺寸</text>
|
||||||
|
<text v-if="product.is_show_price">报价信息</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="(spec, index) in product.price_sheet"
|
||||||
|
:key="index"
|
||||||
|
:class="['specs-row', product.is_show_price ? 'cols-2' : 'cols-1']"
|
||||||
|
>
|
||||||
|
<view class="cell-spec">
|
||||||
|
<view class="spec-name">{{ spec.specification }}</view>
|
||||||
|
<view class="spec-dim">{{ spec.dimension }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="cell-price" v-if="product.is_show_price">
|
||||||
|
<view v-for="(item, idx) in spec.routine" :key="idx" class="price-tag">
|
||||||
|
{{ item }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 详情图片 -->
|
||||||
|
<view class="detail-section">
|
||||||
|
<view class="section-title">产品细节</view>
|
||||||
|
<view class="detail-images">
|
||||||
|
<image
|
||||||
|
v-for="(image, index) in product.carousel"
|
||||||
|
:key="index"
|
||||||
|
:src="image"
|
||||||
|
@click="previewImages(image)"
|
||||||
|
mode="widthFix"
|
||||||
|
class="detail-image"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 添加清单弹窗 -->
|
||||||
|
<up-popup
|
||||||
|
:show="listModal"
|
||||||
|
title="添加到清单"
|
||||||
|
mode="bottom"
|
||||||
|
:round="24"
|
||||||
|
@close="listModal = false"
|
||||||
|
:safeAreaInsetTop="true"
|
||||||
|
:closeable="true"
|
||||||
|
bgColor="#F7F8FA"
|
||||||
|
>
|
||||||
|
<view class="popup-content">
|
||||||
|
<scroll-view scroll-y style="max-height: 600rpx;">
|
||||||
|
<view class="list-options">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in listOption"
|
||||||
|
:key="index"
|
||||||
|
class="option-item"
|
||||||
|
@click="addToCart(item.id)"
|
||||||
|
>
|
||||||
|
<view class="option-info">
|
||||||
|
<text class="option-name">{{ item.name }}</text>
|
||||||
|
<text class="option-id">ID: {{ item.id }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="add-icon-btn">
|
||||||
|
<u-icon name="plus" color="#fff" size="14"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</up-popup>
|
||||||
|
|
||||||
|
<!-- 底部垫高 -->
|
||||||
|
<view class="safe-padding-bottom"></view>
|
||||||
|
|
||||||
|
<!-- 底部操作栏 -->
|
||||||
|
<view class="action-bar-wrapper safe-area-bottom">
|
||||||
|
<view class="action-bar">
|
||||||
|
<button class="action-btn share-btn" open-type="share">
|
||||||
|
<u-icon name="weixin-fill" color="#ccc" size="20"></u-icon>
|
||||||
|
<text>分享</text>
|
||||||
|
</button>
|
||||||
|
<view class="btn-divider"></view>
|
||||||
|
<button class="action-btn add-btn" @tap="showModal">
|
||||||
|
<text>加入清单</text>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getProductDetailApi } from "@/api/page/product";
|
||||||
|
import { getCartListApi, toCartApi } from "@/api/page/cart";
|
||||||
|
import {getCache} from "@/utils/cache";
|
||||||
|
import { urlSafeBase64Encode } from "@/utils/utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: 0,
|
||||||
|
p_user_id: 0,
|
||||||
|
listModal: false,
|
||||||
|
listOption: [],
|
||||||
|
product: {
|
||||||
|
title: '',
|
||||||
|
cover: '',
|
||||||
|
price_sheet: [],
|
||||||
|
alias: '',
|
||||||
|
carousel: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
this.id = options.id;
|
||||||
|
this.p_user_id = options.p_user_id;
|
||||||
|
this.initAlias();
|
||||||
|
this.getProductDetail();
|
||||||
|
},
|
||||||
|
onShareAppMessage() {
|
||||||
|
const userInfo = getCache('user_info')
|
||||||
|
return {
|
||||||
|
title: this.product.title,
|
||||||
|
path: `/pages/product/detail?id=${this.id}&p_user_id=${userInfo.id}`,
|
||||||
|
imageUrl: this.product.cover || this.product.carousel[0] || ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
previewImages(url) {
|
||||||
|
let w = this.product.title;
|
||||||
|
w = urlSafeBase64Encode(w);
|
||||||
|
console.log(`${url}?watermark/2/text/${w}/fontsize/800`);
|
||||||
|
uni.previewImage({
|
||||||
|
current: 0,
|
||||||
|
urls: [`${url}?watermark/2/text/${w}/fontsize/800`],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
navigateBack() {
|
||||||
|
uni.navigateBack();
|
||||||
|
},
|
||||||
|
showModal() {
|
||||||
|
this.getListOption();
|
||||||
|
this.listModal = true;
|
||||||
|
},
|
||||||
|
addToCart(id) {
|
||||||
|
toCartApi(id, this.id).then(() => {
|
||||||
|
this.listModal = false;
|
||||||
|
uni.showToast({
|
||||||
|
title: '已添加到清单',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getProductDetail() {
|
||||||
|
getProductDetailApi(this.id, this.p_user_id).then((res) => {
|
||||||
|
this.product = {
|
||||||
|
...res,
|
||||||
|
cover: res.cover || res.carousel[0] || ''
|
||||||
|
};
|
||||||
|
this.initAlias();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initAlias() {
|
||||||
|
if(this.product.alias) {
|
||||||
|
this.product.alias = this.product.alias.replace(/\n/g, '<br/>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getListOption() {
|
||||||
|
getCartListApi().then((res) => {
|
||||||
|
this.listOption = res;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from { transform: translateY(60rpx); opacity: 0; }
|
||||||
|
to { transform: translateY(0); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from { transform: scale(0); }
|
||||||
|
to { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-up { animation: slideUp 0.6s cubic-bezier(0.2, 0.8, 0.2, 1); }
|
||||||
|
.animate-scale { animation: scaleIn 0.4s ease-out backwards; }
|
||||||
|
|
||||||
|
/* Swiper */
|
||||||
|
.swiper-container {
|
||||||
|
position: relative;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-swiper {
|
||||||
|
height: 750rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 始终悬浮的分享按钮 */
|
||||||
|
.share-fixed-btn {
|
||||||
|
position: fixed;
|
||||||
|
top: 40rpx;
|
||||||
|
right: 30rpx;
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.1);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info Card */
|
||||||
|
.info-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 32rpx 32rpx 0 0;
|
||||||
|
/* 关键修改:移除负边距,改为0,让内容自然连接 */
|
||||||
|
margin-top: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
min-height: 500rpx;
|
||||||
|
/* 增加一个小阴影让层级更明显 */
|
||||||
|
box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-code {
|
||||||
|
font-size: 40rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-alias {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specs */
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
position: relative;
|
||||||
|
padding-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 6rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
background: #B4854D;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs-table {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 2rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs-header, .specs-row {
|
||||||
|
display: grid;
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cols-1 { grid-template-columns: 1fr; }
|
||||||
|
.cols-2 { grid-template-columns: 1fr 1fr; }
|
||||||
|
|
||||||
|
.specs-header {
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.specs-row {
|
||||||
|
border-top: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-spec {
|
||||||
|
padding-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-dim {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-tag {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #B4854D;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Details */
|
||||||
|
.detail-section {
|
||||||
|
margin-top: 50rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-image {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 10rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Safe Padding Bottom */
|
||||||
|
.safe-padding-bottom {
|
||||||
|
height: 160rpx;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Bar */
|
||||||
|
.action-bar-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.05);
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 90rpx;
|
||||||
|
background: #333;
|
||||||
|
border-radius: 45rpx;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
flex: 1;
|
||||||
|
background: transparent;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 28rpx;
|
||||||
|
gap: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn::after { border: none; }
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
background: #B4854D;
|
||||||
|
color: #fff;
|
||||||
|
height: 80%;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-btn {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popup */
|
||||||
|
.popup-content {
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
background: #fff;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-id {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon-btn {
|
||||||
|
width: 50rpx;
|
||||||
|
height: 50rpx;
|
||||||
|
background: #333;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
559
pages/product/product.vue
Normal file
@@ -0,0 +1,559 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- Search Bar (独立按钮版) -->
|
||||||
|
<view class="header-section">
|
||||||
|
<view class="search-bar-row">
|
||||||
|
<view class="search-input-box">
|
||||||
|
<up-search
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索产品型号"
|
||||||
|
placeholder-class="search-placeholder"
|
||||||
|
v-model="title"
|
||||||
|
:showAction="false"
|
||||||
|
:animation="false"
|
||||||
|
bgColor="#f5f5f5"
|
||||||
|
shape="round"
|
||||||
|
@search="handleSearch"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="custom-search-btn" @tap="handleSearch" hover-class="btn-hover">
|
||||||
|
搜索
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Category Tabs (Sticky) -->
|
||||||
|
<view class="tabs-wrapper">
|
||||||
|
<scroll-view
|
||||||
|
class="category-tabs"
|
||||||
|
scroll-x
|
||||||
|
show-scrollbar="false"
|
||||||
|
:scroll-into-view="'tab-' + currentTab"
|
||||||
|
scroll-with-animation
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(tab, index) in tabs"
|
||||||
|
:key="index"
|
||||||
|
:id="'tab-' + index"
|
||||||
|
:class="['tab-item', { active: currentTab === index }]"
|
||||||
|
@tap="switchTab(index, tab.id)"
|
||||||
|
>
|
||||||
|
<text class="tab-text">{{ tab.name }}</text>
|
||||||
|
<view class="active-dot" v-if="currentTab === index"></view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Content Area -->
|
||||||
|
<view class="content-area">
|
||||||
|
<!-- Loading State (Skeleton Screen) -->
|
||||||
|
<view class="loading-container" v-if="loading && products.length === 0">
|
||||||
|
<!-- CSS 骨架屏结构 -->
|
||||||
|
<view class="waterfall-container">
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view class="skeleton-card" v-for="i in 3" :key="'l'+i">
|
||||||
|
<view class="skeleton-img pulse"></view>
|
||||||
|
<view class="skeleton-text pulse"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view class="skeleton-card" v-for="i in 3" :key="'r'+i">
|
||||||
|
<view class="skeleton-img pulse" style="height: 300rpx"></view>
|
||||||
|
<view class="skeleton-text pulse"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<view class="empty-container" v-else-if="products.length === 0">
|
||||||
|
<image src="/static/images/empty.png" mode="aspectFit" class="empty-image animate-float" />
|
||||||
|
<text class="empty-text">暂无相关产品</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Waterfall Layout -->
|
||||||
|
<view class="waterfall-container" v-else>
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view
|
||||||
|
v-for="(product, index) in leftProducts"
|
||||||
|
:key="product.id"
|
||||||
|
class="product-item animate-card-enter"
|
||||||
|
:style="{ animationDelay: index * 0.1 + 's' }"
|
||||||
|
@tap="navigateToDetail(product.id)"
|
||||||
|
>
|
||||||
|
<view class="img-box">
|
||||||
|
<image
|
||||||
|
:src="product.cover"
|
||||||
|
mode="widthFix"
|
||||||
|
class="product-image"
|
||||||
|
@load="imageLoaded(index * 2)"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="info-box">
|
||||||
|
<text class="product-code">{{ product.title }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view
|
||||||
|
v-for="(product, index) in rightProducts"
|
||||||
|
:key="product.id"
|
||||||
|
class="product-item animate-card-enter"
|
||||||
|
:style="{ animationDelay: (index * 0.1 + 0.05) + 's' }"
|
||||||
|
@tap="navigateToDetail(product.id)"
|
||||||
|
>
|
||||||
|
<view class="img-box">
|
||||||
|
<image
|
||||||
|
:src="product.cover"
|
||||||
|
mode="widthFix"
|
||||||
|
class="product-image"
|
||||||
|
@load="imageLoaded(index * 2 + 1)"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="info-box">
|
||||||
|
<text class="product-code">{{ product.title }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Footer State -->
|
||||||
|
<view class="footer-state">
|
||||||
|
<view class="loading-more" v-if="loadingMore">
|
||||||
|
<view class="loading-spinner"></view>
|
||||||
|
</view>
|
||||||
|
<view class="no-more-data" v-if="noMoreData && products.length > 0">
|
||||||
|
<text class="divider-line"></text>
|
||||||
|
<text>THE END</text>
|
||||||
|
<text class="divider-line"></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getCategoryListByPidApi, getProductListApi } from "@/api/page/product"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
pid: 0,
|
||||||
|
currentTab: 0,
|
||||||
|
categoryId: 0,
|
||||||
|
title: "",
|
||||||
|
tabs: [],
|
||||||
|
products: [],
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
noMoreData: false,
|
||||||
|
totalPages: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 将产品分为左右两列,实现瀑布流布局
|
||||||
|
leftProducts() {
|
||||||
|
return this.products.filter((_, index) => index % 2 === 0)
|
||||||
|
},
|
||||||
|
rightProducts() {
|
||||||
|
return this.products.filter((_, index) => index % 2 === 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
if (options && options.pid) {
|
||||||
|
this.pid = options.pid
|
||||||
|
this.categoryId = options.pid
|
||||||
|
this.getCategoryList()
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: "参数错误",
|
||||||
|
icon: "none",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.refreshProductList()
|
||||||
|
},
|
||||||
|
onReachBottom() {
|
||||||
|
this.loadMoreProducts()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getProductList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigateBack() {
|
||||||
|
uni.navigateBack()
|
||||||
|
},
|
||||||
|
switchTab(index, id) {
|
||||||
|
this.currentTab = index
|
||||||
|
this.categoryId = id
|
||||||
|
this.page = 1
|
||||||
|
this.products = []
|
||||||
|
this.noMoreData = false
|
||||||
|
this.getProductList()
|
||||||
|
},
|
||||||
|
navigateToDetail(id) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/product/detail?id=${id}`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getCategoryList() {
|
||||||
|
getCategoryListByPidApi(this.pid).then((res) => {
|
||||||
|
res.unshift({
|
||||||
|
id: 0,
|
||||||
|
name: "全部",
|
||||||
|
})
|
||||||
|
this.tabs = res
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
// 这里的搜索逻辑是正确的:重置分页和数据,重新请求
|
||||||
|
this.page = 1
|
||||||
|
this.products = []
|
||||||
|
this.noMoreData = false
|
||||||
|
this.getProductList()
|
||||||
|
},
|
||||||
|
refreshProductList() {
|
||||||
|
this.page = 1
|
||||||
|
this.products = []
|
||||||
|
this.noMoreData = false
|
||||||
|
this.getProductList()
|
||||||
|
.then(() => {
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
uni.showToast({
|
||||||
|
title: "刷新成功",
|
||||||
|
icon: "success",
|
||||||
|
duration: 1500,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
uni.showToast({
|
||||||
|
title: "刷新失败",
|
||||||
|
icon: "none",
|
||||||
|
duration: 1500,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadMoreProducts() {
|
||||||
|
if (this.loadingMore || this.noMoreData) return
|
||||||
|
|
||||||
|
this.page++
|
||||||
|
this.loadingMore = true
|
||||||
|
|
||||||
|
getProductListApi({
|
||||||
|
page: this.page,
|
||||||
|
size: this.pageSize,
|
||||||
|
title: this.title,
|
||||||
|
category_id: this.categoryId,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.data && res.data.length > 0) {
|
||||||
|
this.products = [...this.products, ...res.data]
|
||||||
|
if (res.data.length < this.pageSize || this.page >= this.totalPages) {
|
||||||
|
this.noMoreData = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.noMoreData = true
|
||||||
|
}
|
||||||
|
this.loadingMore = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.page--
|
||||||
|
this.loadingMore = false
|
||||||
|
uni.showToast({
|
||||||
|
title: "加载失败",
|
||||||
|
icon: "none",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getProductList() {
|
||||||
|
this.loading = true
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getProductListApi({
|
||||||
|
page: this.page,
|
||||||
|
size: this.pageSize,
|
||||||
|
title: this.title,
|
||||||
|
category_id: this.categoryId,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (this.page === 1) {
|
||||||
|
this.products = res.data || []
|
||||||
|
} else {
|
||||||
|
this.products = [...this.products, ...(res.data || [])]
|
||||||
|
}
|
||||||
|
if (!res.data || res.data.length < this.pageSize) {
|
||||||
|
this.noMoreData = true
|
||||||
|
}
|
||||||
|
if (res.last_page) {
|
||||||
|
this.totalPages = res.last_page
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.loading = false
|
||||||
|
uni.showToast({
|
||||||
|
title: "加载失败",
|
||||||
|
icon: "none",
|
||||||
|
})
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
imageLoaded(index) {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-section {
|
||||||
|
background: #fff;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 独立的搜索行样式 */
|
||||||
|
.search-bar-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-box {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-btn {
|
||||||
|
padding: 12rpx 30rpx;
|
||||||
|
background: #B4854D;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 4rpx 10rpx rgba(180, 133, 77, 0.3);
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-wrapper {
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tabs {
|
||||||
|
white-space: nowrap;
|
||||||
|
padding: 0 10rpx;
|
||||||
|
height: 90rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
height: 90rpx;
|
||||||
|
line-height: 90rpx;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 400;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active .tab-text {
|
||||||
|
color: #B4854D;
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-dot {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16rpx;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 8rpx;
|
||||||
|
height: 8rpx;
|
||||||
|
background-color: #B4854D;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 2rpx 4rpx rgba(180, 133, 77, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 瀑布流布局优化 */
|
||||||
|
.waterfall-container {
|
||||||
|
display: flex;
|
||||||
|
padding: 24rpx;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waterfall-column {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waterfall-column:first-child {
|
||||||
|
padding-right: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waterfall-column:last-child {
|
||||||
|
padding-left: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-item {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.03);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-item:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
padding: 24rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-code {
|
||||||
|
display: block;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-weight: 500;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画 Keyframes */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from { opacity: 0; transform: translateY(40rpx); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { opacity: 0.6; background: #eee; }
|
||||||
|
50% { opacity: 1; background: #ddd; }
|
||||||
|
100% { opacity: 0.6; background: #eee; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-10rpx); }
|
||||||
|
100% { transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-card-enter {
|
||||||
|
animation: fadeInUp 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-float {
|
||||||
|
animation: float 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏样式 */
|
||||||
|
.skeleton-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 240rpx;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
height: 30rpx;
|
||||||
|
margin: 20rpx;
|
||||||
|
width: 60%;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 1.5s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部状态 */
|
||||||
|
.loading-more {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 30rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
border: 4rpx solid #ddd;
|
||||||
|
border-top-color: #B4854D;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-more-data {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx 0;
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 22rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-line {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 2rpx;
|
||||||
|
background: #eee;
|
||||||
|
margin: 0 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态 */
|
||||||
|
.empty-container {
|
||||||
|
padding-top: 200rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-image {
|
||||||
|
width: 240rpx;
|
||||||
|
height: 240rpx;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
399
pages/search/search.vue
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
<template>
|
||||||
|
<view class="container">
|
||||||
|
<!-- Search Bar (独立按钮版) -->
|
||||||
|
<view class="search-section">
|
||||||
|
<view class="search-bar-row">
|
||||||
|
<view class="search-input-box">
|
||||||
|
<up-search
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索产品型号"
|
||||||
|
placeholder-class="search-placeholder"
|
||||||
|
v-model="title"
|
||||||
|
:showAction="false"
|
||||||
|
:animation="false"
|
||||||
|
bgColor="#f5f5f5"
|
||||||
|
shape="round"
|
||||||
|
@search="handleSearch"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="custom-search-btn" @tap="handleSearch" hover-class="btn-hover">
|
||||||
|
搜索
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="content-body">
|
||||||
|
<!-- Loading State (Skeleton) -->
|
||||||
|
<view class="loading-container" v-if="loading && products.length === 0">
|
||||||
|
<view class="waterfall-container">
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view class="skeleton-card animate-pulse" v-for="i in 3" :key="'l'+i">
|
||||||
|
<view class="skeleton-img"></view>
|
||||||
|
<view class="skeleton-txt"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view class="skeleton-card animate-pulse" v-for="i in 3" :key="'r'+i">
|
||||||
|
<view class="skeleton-img" style="height: 300rpx"></view>
|
||||||
|
<view class="skeleton-txt"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<view class="empty-container animate-fade-in" v-else-if="products.length === 0">
|
||||||
|
<image src="/static/images/empty.png" mode="aspectFit" class="empty-image" />
|
||||||
|
<text class="empty-text">未找到相关产品</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Waterfall Layout -->
|
||||||
|
<view class="waterfall-container" v-else>
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view
|
||||||
|
v-for="(product, index) in leftProducts"
|
||||||
|
:key="product.id"
|
||||||
|
class="product-item animate-card-enter"
|
||||||
|
:style="{ animationDelay: index * 0.05 + 's' }"
|
||||||
|
@tap="navigateToDetail(product.id)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:src="product.cover"
|
||||||
|
mode="widthFix"
|
||||||
|
class="product-image"
|
||||||
|
@load="imageLoaded(index * 2)"
|
||||||
|
/>
|
||||||
|
<view class="product-info">
|
||||||
|
<text class="product-code">{{ product.title }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="waterfall-column">
|
||||||
|
<view
|
||||||
|
v-for="(product, index) in rightProducts"
|
||||||
|
:key="product.id"
|
||||||
|
class="product-item animate-card-enter"
|
||||||
|
:style="{ animationDelay: (index * 0.05 + 0.05) + 's' }"
|
||||||
|
@tap="navigateToDetail(product.id)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:src="product.cover"
|
||||||
|
mode="widthFix"
|
||||||
|
class="product-image"
|
||||||
|
@load="imageLoaded(index * 2 + 1)"
|
||||||
|
/>
|
||||||
|
<view class="product-info">
|
||||||
|
<text class="product-code">{{ product.title }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Loading Footer -->
|
||||||
|
<view class="loading-more" v-if="loadingMore">
|
||||||
|
<view class="spinner"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="no-more-data" v-if="noMoreData && products.length > 0">
|
||||||
|
<text class="line"></text>
|
||||||
|
<text>THE END</text>
|
||||||
|
<text class="line"></text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { getProductListApi } from "@/api/page/product"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
title: "",
|
||||||
|
products: [],
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
noMoreData: false,
|
||||||
|
totalPages: 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
leftProducts() {
|
||||||
|
return this.products.filter((_, index) => index % 2 === 0)
|
||||||
|
},
|
||||||
|
rightProducts() {
|
||||||
|
return this.products.filter((_, index) => index % 2 === 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
if (options && options.title) {
|
||||||
|
this.title = options.title
|
||||||
|
}
|
||||||
|
this.getProductList()
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.refreshProductList()
|
||||||
|
},
|
||||||
|
onReachBottom() {
|
||||||
|
this.loadMoreProducts()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
navigateToDetail(id) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/product/detail?id=${id}`,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleSearch() {
|
||||||
|
this.page = 1
|
||||||
|
this.products = []
|
||||||
|
this.noMoreData = false
|
||||||
|
this.getProductList()
|
||||||
|
},
|
||||||
|
refreshProductList() {
|
||||||
|
this.page = 1
|
||||||
|
this.products = []
|
||||||
|
this.noMoreData = false
|
||||||
|
this.getProductList()
|
||||||
|
.then(() => {
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
uni.showToast({
|
||||||
|
title: "刷新成功",
|
||||||
|
icon: "success",
|
||||||
|
duration: 1500,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
uni.showToast({
|
||||||
|
title: "刷新失败",
|
||||||
|
icon: "none",
|
||||||
|
duration: 1500,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadMoreProducts() {
|
||||||
|
if (this.loadingMore || this.noMoreData) return
|
||||||
|
|
||||||
|
this.page++
|
||||||
|
this.loadingMore = true
|
||||||
|
|
||||||
|
getProductListApi({
|
||||||
|
page: this.page,
|
||||||
|
size: this.pageSize,
|
||||||
|
title: this.title,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res.data && res.data.length > 0) {
|
||||||
|
this.products = [...this.products, ...res.data]
|
||||||
|
if (res.data.length < this.pageSize || this.page >= this.totalPages) {
|
||||||
|
this.noMoreData = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.noMoreData = true
|
||||||
|
}
|
||||||
|
this.loadingMore = false
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.page--
|
||||||
|
this.loadingMore = false
|
||||||
|
uni.showToast({
|
||||||
|
title: "加载失败",
|
||||||
|
icon: "none",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getProductList() {
|
||||||
|
this.loading = true
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getProductListApi({
|
||||||
|
page: this.page,
|
||||||
|
size: this.pageSize,
|
||||||
|
title: this.title,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (this.page === 1) {
|
||||||
|
this.products = res.data || []
|
||||||
|
} else {
|
||||||
|
this.products = [...this.products, ...(res.data || [])]
|
||||||
|
}
|
||||||
|
if (!res.data || res.data.length < this.pageSize) {
|
||||||
|
this.noMoreData = true
|
||||||
|
}
|
||||||
|
if (res.last_page) {
|
||||||
|
this.totalPages = res.last_page
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
uni.hideLoading()
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.loading = false
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({
|
||||||
|
title: "加载失败",
|
||||||
|
icon: "none",
|
||||||
|
})
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
imageLoaded(index) {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.container {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #F7F8FA;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { opacity: 0.6; background: #e0e0e0; }
|
||||||
|
50% { opacity: 1; background: #d0d0d0; }
|
||||||
|
100% { opacity: 0.6; background: #e0e0e0; }
|
||||||
|
}
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from { opacity: 0; transform: translateY(40rpx); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
@keyframes spin { to { transform: rotate(360deg); } }
|
||||||
|
|
||||||
|
.animate-pulse { animation: pulse 1.5s infinite ease-in-out; }
|
||||||
|
.animate-card-enter { animation: fadeInUp 0.6s ease-out backwards; }
|
||||||
|
.animate-fade-in { animation: fadeInUp 0.5s ease-out; }
|
||||||
|
|
||||||
|
.search-section {
|
||||||
|
background: #fff;
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 4rpx 10rpx rgba(0,0,0,0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-box {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-search-btn {
|
||||||
|
padding: 12rpx 30rpx;
|
||||||
|
background: #B4854D;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 4rpx 10rpx rgba(180, 133, 77, 0.3);
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 瀑布流 */
|
||||||
|
.waterfall-container {
|
||||||
|
display: flex;
|
||||||
|
padding: 24rpx;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waterfall-column {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waterfall-column:first-child { padding-right: 12rpx; }
|
||||||
|
.waterfall-column:last-child { padding-left: 12rpx; }
|
||||||
|
|
||||||
|
.product-item {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-code {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-weight: 500;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skeleton */
|
||||||
|
.skeleton-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
.skeleton-img { width: 100%; height: 240rpx; background: #eee; }
|
||||||
|
.skeleton-txt { height: 30rpx; width: 70%; background: #eee; margin: 20rpx; border-radius: 8rpx; }
|
||||||
|
|
||||||
|
/* Empty */
|
||||||
|
.empty-container {
|
||||||
|
padding-top: 150rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.empty-image { width: 240rpx; height: 240rpx; opacity: 0.8; margin-bottom: 20rpx; }
|
||||||
|
.empty-text { color: #999; font-size: 28rpx; }
|
||||||
|
|
||||||
|
/* Loading Footer */
|
||||||
|
.loading-more {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
}
|
||||||
|
.spinner {
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border: 4rpx solid #e0e0e0;
|
||||||
|
border-top-color: #B4854D;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-more-data {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40rpx 0;
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 22rpx;
|
||||||
|
}
|
||||||
|
.line { width: 40rpx; height: 1px; background: #eee; margin: 0 10rpx; }
|
||||||
|
</style>
|
||||||
76
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
clipboard:
|
||||||
|
specifier: ^2.0.11
|
||||||
|
version: 2.0.11
|
||||||
|
dayjs:
|
||||||
|
specifier: ^1.11.13
|
||||||
|
version: 1.11.13
|
||||||
|
js-base64:
|
||||||
|
specifier: ^3.7.7
|
||||||
|
version: 3.7.8
|
||||||
|
uview-plus:
|
||||||
|
specifier: ^3.3.36
|
||||||
|
version: 3.3.36
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
clipboard@2.0.11:
|
||||||
|
resolution: {integrity: sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==, tarball: https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz}
|
||||||
|
|
||||||
|
dayjs@1.11.13:
|
||||||
|
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==, tarball: https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz}
|
||||||
|
|
||||||
|
delegate@3.2.0:
|
||||||
|
resolution: {integrity: sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==, tarball: https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz}
|
||||||
|
|
||||||
|
good-listener@1.2.2:
|
||||||
|
resolution: {integrity: sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==, tarball: https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz}
|
||||||
|
|
||||||
|
js-base64@3.7.8:
|
||||||
|
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
|
||||||
|
|
||||||
|
select@1.1.2:
|
||||||
|
resolution: {integrity: sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==, tarball: https://registry.npmmirror.com/select/-/select-1.1.2.tgz}
|
||||||
|
|
||||||
|
tiny-emitter@2.1.0:
|
||||||
|
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==, tarball: https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz}
|
||||||
|
|
||||||
|
uview-plus@3.3.36:
|
||||||
|
resolution: {integrity: sha512-yiIIt3OkCDkBzflrr6by8qvYibvCzsFZ/Mn+Fdx9TDaTQAltWFBj7UNVN/wOeb3lou2T5S1QFy2rxw3OcAYi5g==, tarball: https://registry.npmmirror.com/uview-plus/-/uview-plus-3.3.36.tgz}
|
||||||
|
engines: {HBuilderX: ^3.1.0}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
clipboard@2.0.11:
|
||||||
|
dependencies:
|
||||||
|
good-listener: 1.2.2
|
||||||
|
select: 1.1.2
|
||||||
|
tiny-emitter: 2.1.0
|
||||||
|
|
||||||
|
dayjs@1.11.13: {}
|
||||||
|
|
||||||
|
delegate@3.2.0: {}
|
||||||
|
|
||||||
|
good-listener@1.2.2:
|
||||||
|
dependencies:
|
||||||
|
delegate: 3.2.0
|
||||||
|
|
||||||
|
js-base64@3.7.8: {}
|
||||||
|
|
||||||
|
select@1.1.2: {}
|
||||||
|
|
||||||
|
tiny-emitter@2.1.0: {}
|
||||||
|
|
||||||
|
uview-plus@3.3.36:
|
||||||
|
dependencies:
|
||||||
|
clipboard: 2.0.11
|
||||||
|
dayjs: 1.11.13
|
||||||
BIN
static/icon/appointment.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
static/icon/await.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
static/icon/doing.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/icon/order.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
static/icon/served.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
static/icon/wallet.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/images/empty.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
static/images/logo.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
1
static/loading.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="150" style="display:block;margin:0 auto"><defs><radialGradient id="a" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"><stop offset="0%" style="stop-color:#fff;stop-opacity:1"/><stop offset="100%" style="stop-color:#ffb65c;stop-opacity:1"/></radialGradient><radialGradient id="c" cx="50%" cy="50%" r="50%" fx="50%" fy="50%"><stop offset="0%" style="stop-color:#fff;stop-opacity:1"/><stop offset="30%" style="stop-color:#fff;stop-opacity:1"/><stop offset="40%" style="stop-color:#ffffc8;stop-opacity:.8"/><stop offset="50%" style="stop-color:#ffff96;stop-opacity:.6"/><stop offset="60%" style="stop-color:#ffff64;stop-opacity:.4"/><stop offset="70%" style="stop-color:#ffff32;stop-opacity:.2"/><stop offset="80%" style="stop-color:#ffe600;stop-opacity:.1"/><stop offset="90%" style="stop-color:#ffb65c;stop-opacity:.05"/><stop offset="100%" style="stop-color:#ffb65c;stop-opacity:0"/></radialGradient><filter id="b"><feTurbulence baseFrequency=".1" numOctaves="2" result="turbulence" type="fractalNoise"/><feDisplacementMap in="SourceGraphic" in2="turbulence" scale="10"/></filter></defs><rect width="160" height="110" x="20" y="20" fill="url(#a)" rx="20" ry="20"/><circle cx="100" cy="60" r="10" fill="url(#a)"/><rect width="160" height="110" x="20" y="20" fill="url(#a)" filter="url(#b)" rx="20" ry="20"/><circle cx="30" cy="40" r="10" fill="url(#c)"/><circle cx="40" cy="90" r="15" fill="url(#c)"/><circle cx="110" cy="40" r="12" fill="url(#c)"/><circle cx="100" cy="60" r="10" fill="url(#c)"/><circle cx="160" cy="80" r="15" fill="url(#c)"/><g opacity=".2" transform="matrix(1.2 0 0 1.2 60 20)"><path fill="#f80" d="M4.5 2.2c-3.5 2.2-3.4 25.5.8 33l26.1-16c-4-7.1-23.6-19-26.9-17"/><path fill="#f7a4a4" d="M9.5 11.3c-1.5.9-2.2 16.2.4 21l16.7-10.2c-2.4-4.6-15.7-11.6-17.1-10.8"/><path fill="#f80" d="M59.5 2.2c3.5 2.2 3.4 25.5-.7 33l-26.1-16c3.9-7.1 23.5-19 26.8-17"/><path fill="#f7a4a4" d="M54.5 11.3c1.5.9 2.2 16.2-.4 21L37.3 22.1c2.5-4.6 15.8-11.6 17.2-10.8"/><path fill="#f80" d="M31.8 13.1C4.7 13.1 2.2 32.5 2.2 43.5 2.2 48 15.4 62 31.8 62c16.4 0 29.6-14 29.6-18.5 0-11-2.5-30.4-29.6-30.4"/><path fill="#fff" d="M24.2 38.7s-3.1 4.8-8.8 3.3c-5.7-1.5-6-7.2-6-7.2s3.1-4.8 8.8-3.3c5.8 1.5 6 7.2 6 7.2"/><path fill="#fff" d="M23.6 36.2s-2.7 3-6.5 3c-4.1 0-6.5-5.9-6.5-5.9s2.7-3.1 7.6-1.9c4.5 1.2 5.4 4.8 5.4 4.8"/><path fill="#f80" d="M19.4 36.1c0 6.6-3.2 6.6-3.2 0 .1-6.6 3.2-6.6 3.2 0"/><path fill="#fff" d="M39.4 38.7s3.1 4.8 8.8 3.3c5.7-1.5 6-7.2 6-7.2s-3.1-4.8-8.8-3.3c-5.8 1.5-6 7.2-6 7.2"/><path fill="#fff" d="M39.9 36.2s2.7 3 6.5 3c4.1 0 6.5-5.9 6.5-5.9s-2.7-3.1-7.6-1.9c-4.5 1.2-5.4 4.8-5.4 4.8"/><path fill="#f80" d="M44.1 36.1c0 6.6 3.2 6.6 3.2 0s-3.2-6.6-3.2 0"/><path fill="#fff" d="M40.4 44c-2.6-2-5.4-8.7-8.6-8.7S25.7 42 23.1 44c-4.1 3.2-15 6.8-15 6.8S19.7 61 31.7 61s23.6-10.2 23.6-10.2c.1 0-10.8-3.6-14.9-6.8"/><ellipse cx="31.8" cy="54.5" fill="#ff94a4" rx="1.7" ry="2.5"/><g fill="#f80"><path d="M40.2 53.1c-1 .6-2.1.8-3.1.8-1.1-.1-2.1-.4-2.9-1.1-.8-.6-1.4-1.5-1.6-2.6l-.8-4.5-.8 4.5c-.2 1-.8 1.9-1.6 2.6-.8.7-1.9 1-2.9 1.1-1.1 0-2.2-.2-3.1-.8-1-.6-1.8-1.5-2.2-2.7.1 1.3.7 2.5 1.7 3.4 1 .9 2.3 1.4 3.5 1.4 1.4.1 2.7-.3 3.8-1.1.6-.4 1.1-1 1.5-1.6.4.6.9 1.2 1.5 1.6 1.1.8 2.5 1.2 3.8 1.1 1.3-.1 2.6-.6 3.6-1.4 1-.9 1.7-2.1 1.7-3.4-.3 1.1-1.1 2.1-2.1 2.7"/><path d="M35.8 44.8c-.8-1-3.3-1.1-4-1.1-.7 0-3.2.1-4 1.1-.6.7-.1 2.5 1.4 4 1 1 1.9 1.3 2.6 1.3.7 0 1.7-.3 2.6-1.3 1.5-1.6 2-3.3 1.4-4"/></g></g><text x="50" y="120" font-size="35" font-family="楷体" font-weight="bold" fill="#ff8800">奶酪云</text><g fill="orange" transform="translate(70 75)"><circle id="d" cx="0" cy="0" r="5"/><circle id="e" cx="20" cy="0" r="5"/><circle id="f" cx="40" cy="0" r="5"/><circle id="g" cx="60" cy="0" r="5"/></g><animate xlink:href="#d" attributeName="cy" begin="0s" dur="0.75s" from="0" keyTimes="0;0.5;1" repeatCount="indefinite" to="-10" values="0;-10;0"/><animate xlink:href="#e" attributeName="cy" begin="0.1s" dur="0.75s" from="0" keyTimes="0;0.5;1" repeatCount="indefinite" to="-10" values="0;-10;0"/><animate xlink:href="#f" attributeName="cy" begin="0.2s" dur="0.75s" from="0" keyTimes="0;0.5;1" repeatCount="indefinite" to="-10" values="0;-10;0"/><animate xlink:href="#g" attributeName="cy" begin="0.3s" dur="1s" from="0" keyTimes="0;0.5;1" repeatCount="indefinite" to="-10" values="0;-10;0"/></svg>
|
||||||
|
After Width: | Height: | Size: 4.3 KiB |
BIN
static/logo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
static/tabbar/activity-fill.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
static/tabbar/activity.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
static/tabbar/classify-fill.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/tabbar/classify.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
static/tabbar/home-fill.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
static/tabbar/home.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/tabbar/mine-fill.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/tabbar/mine.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/tabbar/shopping-cart-fill.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/tabbar/shopping-cart.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
static/tabbar/v2/home-fill.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/tabbar/v2/home.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/tabbar/v2/mine-fill.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
static/tabbar/v2/mine.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
10
uni.promisify.adaptor.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
uni.addInterceptor({
|
||||||
|
returnValue (res) {
|
||||||
|
if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
79
uni.scss
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* 这里是uni-app内置的常用样式变量
|
||||||
|
*
|
||||||
|
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||||
|
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||||
|
*
|
||||||
|
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* uni.scss */
|
||||||
|
@import 'uview-plus/theme.scss';
|
||||||
|
|
||||||
|
/* 颜色变量 */
|
||||||
|
|
||||||
|
/* 行为相关颜色 */
|
||||||
|
$uni-color-primary: #007aff;
|
||||||
|
$uni-color-success: #4cd964;
|
||||||
|
$uni-color-warning: #f0ad4e;
|
||||||
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
|
/* 文字基本颜色 */
|
||||||
|
$uni-text-color:#333;//基本色
|
||||||
|
$uni-text-color-inverse:#fff;//反色
|
||||||
|
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||||
|
$uni-text-color-placeholder: #808080;
|
||||||
|
$uni-text-color-disable:#c0c0c0;
|
||||||
|
|
||||||
|
/* 背景颜色 */
|
||||||
|
$uni-bg-color:#ffffff;
|
||||||
|
$uni-bg-color-grey:#f8f8f8;
|
||||||
|
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||||
|
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||||
|
|
||||||
|
/* 边框颜色 */
|
||||||
|
$uni-border-color:#c8c7cc;
|
||||||
|
|
||||||
|
/* 尺寸变量 */
|
||||||
|
|
||||||
|
/* 文字尺寸 */
|
||||||
|
$uni-font-size-sm:12px;
|
||||||
|
$uni-font-size-base:14px;
|
||||||
|
$uni-font-size-lg:16px;
|
||||||
|
|
||||||
|
/* 图片尺寸 */
|
||||||
|
$uni-img-size-sm:20px;
|
||||||
|
$uni-img-size-base:26px;
|
||||||
|
$uni-img-size-lg:40px;
|
||||||
|
|
||||||
|
/* Border Radius */
|
||||||
|
$uni-border-radius-sm: 2px;
|
||||||
|
$uni-border-radius-base: 3px;
|
||||||
|
$uni-border-radius-lg: 6px;
|
||||||
|
$uni-border-radius-circle: 50%;
|
||||||
|
|
||||||
|
/* 水平间距 */
|
||||||
|
$uni-spacing-row-sm: 5px;
|
||||||
|
$uni-spacing-row-base: 10px;
|
||||||
|
$uni-spacing-row-lg: 15px;
|
||||||
|
|
||||||
|
/* 垂直间距 */
|
||||||
|
$uni-spacing-col-sm: 4px;
|
||||||
|
$uni-spacing-col-base: 8px;
|
||||||
|
$uni-spacing-col-lg: 12px;
|
||||||
|
|
||||||
|
/* 透明度 */
|
||||||
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
|
/* 文章场景相关 */
|
||||||
|
$uni-color-title: #2C405A; // 文章标题颜色
|
||||||
|
$uni-font-size-title:20px;
|
||||||
|
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||||
|
$uni-font-size-subtitle:26px;
|
||||||
|
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||||
|
$uni-font-size-paragraph:15px;
|
||||||
21
uni_modules/uview-plus/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 https://uiadmin.net/uview-plus
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
74
uni_modules/uview-plus/README.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img alt="logo" src="https://uiadmin.net/uview-plus/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
|
||||||
|
</p>
|
||||||
|
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uview-plus 3.0</h3>
|
||||||
|
<h3 align="center">多平台快速开发的UI框架</h3>
|
||||||
|
|
||||||
|
[](https://github.com/ijry/uview-plus)
|
||||||
|
[](https://github.com/ijry/uview-plus)
|
||||||
|
[](https://github.com/ijry/uview-plus/issues)
|
||||||
|
[](https://gitee.com/jry/uview-plus/releases)
|
||||||
|
[](https://en.wikipedia.org/wiki/MIT_License)
|
||||||
|
|
||||||
|
## 说明
|
||||||
|
|
||||||
|
uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
|
||||||
|
|
||||||
|
## 可视化设计
|
||||||
|
|
||||||
|
uview-plus现已推出免费可视化设计,可以方便的进行页面可视化设计,导出源码即可使用。极大提高前端页面开发效率;如产品经理设计师直接使用更可作为高保真高可用原型制作工具,让设计稿即代码,无需传统的设计稿开发还原步骤。
|
||||||
|
|
||||||
|
<img src="https://s3.bmp.ovh/imgs/2024/11/24/fd58d00071e6e5df.png" width="900" height="auto" >
|
||||||
|
<img src="https://s3.bmp.ovh/imgs/2024/11/24/8e85a519fe627fb1.png" width="900" height="auto" >
|
||||||
|
|
||||||
|
|
||||||
|
## 文档
|
||||||
|
[官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com)
|
||||||
|
[备用文档:https://uiadmin.net/uview-plus](https://uiadmin.net/uview-plus)
|
||||||
|
|
||||||
|
|
||||||
|
## 预览
|
||||||
|
|
||||||
|
您可以通过**微信**扫码,查看最佳的演示效果。
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="220" height="220" >
|
||||||
|
|
||||||
|
## 链接
|
||||||
|
|
||||||
|
- [官方文档](https://uview-plus.jiangruyi.com)
|
||||||
|
- [更新日志](https://uview-plus.jiangruyi.com/components/changelog.html)
|
||||||
|
- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html)
|
||||||
|
- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html)
|
||||||
|
|
||||||
|
## 交流反馈
|
||||||
|
|
||||||
|
欢迎加入我们的QQ群交流反馈:[点此跳转](https://uview-plus.jiangruyi.com/components/addQQGroup.html)
|
||||||
|
|
||||||
|
## 关于PR
|
||||||
|
|
||||||
|
> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uview-plus是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
|
||||||
|
> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus)
|
||||||
|
|
||||||
|
请通过[官网安装文档](https://uview-plus.jiangruyi.com/components/install.html)了解更详细的内容
|
||||||
|
|
||||||
|
## 快速上手
|
||||||
|
|
||||||
|
请通过[快速上手](https://uview-plus.jiangruyi.com/components/quickstart.html)了解更详细的内容
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<u-button text="按钮"></u-button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 版权信息
|
||||||
|
uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。
|
||||||
|
|
||||||
581
uni_modules/uview-plus/changelog.md
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
## 3.3.63(2025-01-13)
|
||||||
|
fix: cate-tab支持支付宝小程序
|
||||||
|
|
||||||
|
fix: textarea 修复 placeholder-style
|
||||||
|
|
||||||
|
fix: 修复在图片加载及加载失败时容器宽度
|
||||||
|
|
||||||
|
fix: waterfall组件报错Maximum recursive updates
|
||||||
|
|
||||||
|
## 3.3.62(2025-01-10)
|
||||||
|
feat: sleder滑动选择器双滑块增加外层触发值的变动功能
|
||||||
|
|
||||||
|
fix: picker支持hasInput优化
|
||||||
|
|
||||||
|
## 3.3.61(2024-12-31)
|
||||||
|
fix: 修复微信getSystemInfoSync接口废弃警告
|
||||||
|
|
||||||
|
fix: 'u-status-bar' symbol missing
|
||||||
|
|
||||||
|
## 3.3.60(2024-12-30)
|
||||||
|
feat: 日期组件支持禁用
|
||||||
|
|
||||||
|
fix: ts定义修复 #600
|
||||||
|
|
||||||
|
feat: Tabs组件选中时增加一个active的class #595
|
||||||
|
|
||||||
|
## 3.3.59(2024-12-30)
|
||||||
|
fix: Property "isH5" was accessed during render
|
||||||
|
|
||||||
|
## 3.3.58(2024-12-26)
|
||||||
|
fix: slider组件change事件传参
|
||||||
|
|
||||||
|
## 3.3.57(2024-12-23)
|
||||||
|
fix: slider组件change事件传参
|
||||||
|
|
||||||
|
feat: 更新u-picker组件增加当前选中class类名
|
||||||
|
|
||||||
|
## 3.3.56(2024-12-18)
|
||||||
|
feat: 在u-alert组件中添加关闭事件
|
||||||
|
|
||||||
|
## 3.3.55(2024-12-17)
|
||||||
|
add: swiper增加双向绑定
|
||||||
|
|
||||||
|
## 3.3.54(2024-12-11)
|
||||||
|
add: qrcode支持props控制是否开启点击预览
|
||||||
|
|
||||||
|
add: 新增cate-tab垂直分类组件
|
||||||
|
|
||||||
|
## 3.3.53(2024-12-10)
|
||||||
|
fix: 修复popup居中模式点击内容区域触发关闭
|
||||||
|
|
||||||
|
## 3.3.52(2024-12-09)
|
||||||
|
add: notice-bar支持justifyContent属性
|
||||||
|
|
||||||
|
## 3.3.51(2024-12-09)
|
||||||
|
add: radio增加label插槽
|
||||||
|
|
||||||
|
## 3.3.50(2024-12-05)
|
||||||
|
fix: 优化popup等对禁止背景滚动机制
|
||||||
|
|
||||||
|
add: slider在弹窗使用示例
|
||||||
|
|
||||||
|
fix: card组件类名问题
|
||||||
|
|
||||||
|
## 3.3.49(2024-12-02)
|
||||||
|
fix: 去除album多余的$u引用
|
||||||
|
|
||||||
|
fix: 优化图片组件兼容性
|
||||||
|
|
||||||
|
add: picker组件增加zIndex属性
|
||||||
|
|
||||||
|
add: text增加是否占满剩余空间属性
|
||||||
|
|
||||||
|
add: input颜色示例
|
||||||
|
|
||||||
|
## 3.3.48(2024-11-29)
|
||||||
|
add: 文本行数限制样式提高到10行
|
||||||
|
|
||||||
|
del: 去除不跨端的inputmode
|
||||||
|
## 3.3.47(2024-11-28)
|
||||||
|
fix: 时间选择器在hasInput模式下部分机型键盘弹出
|
||||||
|
|
||||||
|
## 3.3.46(2024-11-26)
|
||||||
|
fix: 修复text传递事件参数
|
||||||
|
|
||||||
|
## 3.3.45(2024-11-24)
|
||||||
|
add: navbar组件支持配置标题颜色
|
||||||
|
|
||||||
|
fix: 边框按钮警告类型下颜色变量使用错误
|
||||||
|
|
||||||
|
## 3.3.43(2024-11-18)
|
||||||
|
fix: 支持瀑布流组件v-model置为[]
|
||||||
|
|
||||||
|
add: 新增字符串路径访问工具方法getValueByPath
|
||||||
|
|
||||||
|
add: 新增float-button悬浮按钮组件
|
||||||
|
|
||||||
|
## 3.3.42(2024-11-15)
|
||||||
|
add: button组件支持stop参数阻止冒泡
|
||||||
|
|
||||||
|
## 3.3.41(2024-11-13)
|
||||||
|
fix: u-radio-group invalid import
|
||||||
|
|
||||||
|
improvement: 优化图片组件宽高及修复事件event传递
|
||||||
|
|
||||||
|
## 3.3.40(2024-11-11)
|
||||||
|
add: 组件radioGroup增加gap属性用于设置item间隔
|
||||||
|
|
||||||
|
fix: 修复H5全局导入
|
||||||
|
|
||||||
|
## 3.3.39(2024-11-04)
|
||||||
|
fix: 修复相册组件
|
||||||
|
|
||||||
|
## 3.3.38(2024-11-04)
|
||||||
|
fix: 修复视频预览报错 #510
|
||||||
|
|
||||||
|
add: album组件增加stop参数支持阻止事件冒泡
|
||||||
|
|
||||||
|
## 3.3.37(2024-10-21)
|
||||||
|
fix: 修复因为修改组件名称前缀,导致h5打包后$parent方法内找不到父组件的问题
|
||||||
|
|
||||||
|
fix: 修复datetime-picker选择2000年以前日期出错
|
||||||
|
|
||||||
|
## 3.3.36(2024-10-09)
|
||||||
|
fix: toast 自动关闭
|
||||||
|
|
||||||
|
feat: 增加微信小程序用户昵称审核完毕回调及修改 ts 定义文件
|
||||||
|
|
||||||
|
## 3.3.35(2024-10-08)
|
||||||
|
feat: modal和picker支持v-model:show双向绑定
|
||||||
|
|
||||||
|
feat: 支持checkbox使用slot自定义label后自带点击事件 #522
|
||||||
|
|
||||||
|
feat: swipe-action支持自动关闭特性及初始化打开状态
|
||||||
|
|
||||||
|
## 3.3.34(2024-09-23)
|
||||||
|
feat: 支持toast设置duration值为-1时不自动关闭
|
||||||
|
|
||||||
|
## 3.3.33(2024-09-18)
|
||||||
|
fix: 修复test.date('008')等验证结果不准确
|
||||||
|
|
||||||
|
## 3.3.32(2024-09-09)
|
||||||
|
fix: u-keyboard名称冲突warning
|
||||||
|
|
||||||
|
## 3.3.31(2024-08-31)
|
||||||
|
feat: qrcode初步支持nvue
|
||||||
|
|
||||||
|
## 3.3.30(2024-08-30)
|
||||||
|
fix: slider兼容step为字符串类型
|
||||||
|
|
||||||
|
## 3.3.29(2024-08-30)
|
||||||
|
fix: 修复tabs组件current参数为字符串处理逻辑
|
||||||
|
|
||||||
|
## 3.3.28(2024-08-26)
|
||||||
|
fix: list组件滑动偏移量不一样取绝对值导致iOS下拉偏移量计算错误
|
||||||
|
|
||||||
|
## 3.3.27(2024-08-22)
|
||||||
|
fix: 修复up-datetime-picker组件toolbarRightSlot定义缺失
|
||||||
|
|
||||||
|
fix: 修复FormItem的rules更新错误的问题
|
||||||
|
|
||||||
|
## 3.3.26(2024-08-22)
|
||||||
|
fix: 批量注册全局组件优化
|
||||||
|
|
||||||
|
## 3.3.25(2024-08-21)
|
||||||
|
fix: 修复slider在app-vue下样式问题
|
||||||
|
|
||||||
|
## 3.3.24(2024-08-19)
|
||||||
|
fix: 修复时间选择器hasInput模式小程序不生效
|
||||||
|
|
||||||
|
feat: 支持H5导入所有组件
|
||||||
|
|
||||||
|
## 3.3.23(2024-08-17)
|
||||||
|
feat: swipe-action增加closeAll方法
|
||||||
|
|
||||||
|
fix: 兼容tabs在某些场景下index小于0时自动设置为0
|
||||||
|
|
||||||
|
add: 通用mixin新增navTo页面跳转方法
|
||||||
|
|
||||||
|
## 3.3.21(2024-08-15)
|
||||||
|
improvement: 优化二维码组件loading及支持预览与长按事件 #351
|
||||||
|
|
||||||
|
fix: 修复swipe-action自动关闭其它功能及组件卸载自动关闭
|
||||||
|
|
||||||
|
## 3.3.20(2024-08-15)
|
||||||
|
refactor: props默认值文件移至组件文件夹内便于查找
|
||||||
|
## 3.3.19(2024-08-14)
|
||||||
|
fix: 修复2被rpx兼容处理只在数字值生效
|
||||||
|
|
||||||
|
add: 增加swiper自定义插槽示例
|
||||||
|
|
||||||
|
## 3.3.18(2024-08-13)
|
||||||
|
feat: 新增支持datetime-picker工具栏插槽及picker插槽支持修复
|
||||||
|
## 3.3.17(2024-08-12)
|
||||||
|
feat: swiper组件增加默认slot便于自定义
|
||||||
|
|
||||||
|
feat: grid新增间隔参数
|
||||||
|
|
||||||
|
feat: picker新增toolbar-right和toolbar-bottom插槽
|
||||||
|
|
||||||
|
## 3.3.16(2024-08-12)
|
||||||
|
fix: 解决swiper中title换行后多余的内容未被遮挡问题
|
||||||
|
|
||||||
|
fix: 修复迷你导航适配异形屏
|
||||||
|
|
||||||
|
## 3.3.15(2024-08-09)
|
||||||
|
fix: 修复默认单位设置为rpx时一些组件高度间距异常
|
||||||
|
|
||||||
|
fix: 修复日历在rpx单位下布局异常
|
||||||
|
|
||||||
|
feat: code-input支持App端展示输入光标
|
||||||
|
|
||||||
|
## 3.3.14(2024-08-09)
|
||||||
|
add: 增加box组件
|
||||||
|
|
||||||
|
add: 增加card卡片组件
|
||||||
|
|
||||||
|
|
||||||
|
## 3.3.13(2024-08-08)
|
||||||
|
feat: input支持调用原生组件的focus和blur方法
|
||||||
|
|
||||||
|
improvement: grid-item条件编译优化
|
||||||
|
|
||||||
|
add: 新增迷你导航组件
|
||||||
|
|
||||||
|
## 3.3.12(2024-08-06)
|
||||||
|
improvement: $u挂载时机调整便于打包分离chunk
|
||||||
|
|
||||||
|
fix: steps新增itemStyle属性名称冲突
|
||||||
|
|
||||||
|
## 3.3.11(2024-08-05)
|
||||||
|
feat: 新增支持upload组件的deletable/maxCount/accept变更监听 #333
|
||||||
|
|
||||||
|
feat: 新增支持tabs在swiper中使用
|
||||||
|
|
||||||
|
feat: 新增FormItem支持独立设置验证规则rules
|
||||||
|
|
||||||
|
fix: 修复index-list未设置$slots.header时索引高亮失效
|
||||||
|
|
||||||
|
## 3.3.10(2024-08-02)
|
||||||
|
fix: 修复index-list偶发的滑动最后一个索引报错top不存在
|
||||||
|
|
||||||
|
fix: 修复gird在QQ、抖音小程序下布局
|
||||||
|
|
||||||
|
feat: 优化step支持自定义样式prop
|
||||||
|
|
||||||
|
feat: action-sheet组件支持v-model:show双向绑定
|
||||||
|
|
||||||
|
fix: 小程序下steps和grid都统一采用grid布局
|
||||||
|
|
||||||
|
fix: 修复支付宝小程序下input类型为数字时双向绑定失效
|
||||||
|
|
||||||
|
feat : form 表单 validate 校验不通过后 error增加字段prop信息 #304
|
||||||
|
|
||||||
|
fix: form组件异步校异常验问题 #393
|
||||||
|
|
||||||
|
## 3.3.9(2024-08-01)
|
||||||
|
fix: 优化获取nvue元素
|
||||||
|
|
||||||
|
feat: modal新增contentTextAlign设置文案对齐方式
|
||||||
|
|
||||||
|
fix: 修复NVUE下tabbar文字不显示 #458
|
||||||
|
|
||||||
|
feat: loading-page增加zIndex属性
|
||||||
|
|
||||||
|
fix: 相册在宽度较小时换行问题
|
||||||
|
|
||||||
|
feat: album相册增加自适应自动换行模式
|
||||||
|
|
||||||
|
feat: album相册增加图片尺寸单位prop
|
||||||
|
|
||||||
|
fix: 修复calendar日历月份居中
|
||||||
|
|
||||||
|
## 3.3.8(2024-07-31)
|
||||||
|
feat: slider支持进度条任意位置触发按钮拖动
|
||||||
|
|
||||||
|
fix: 修复app-vue下modal标题不居中
|
||||||
|
|
||||||
|
fix: #459 TS setConfig 声明异常
|
||||||
|
|
||||||
|
feat: tabs组件增加longPress长按事件
|
||||||
|
|
||||||
|
feat: 新增showRight属性控制collapse右侧图标显隐
|
||||||
|
|
||||||
|
fix: 优化nvue下css警告
|
||||||
|
|
||||||
|
## 3.3.7(2024-07-29)
|
||||||
|
feat: 支持IndexList组件支持在弹窗等场景下使用及联动优化
|
||||||
|
|
||||||
|
feat: popup组件支持v-model:show双向绑定
|
||||||
|
|
||||||
|
feat: 优化tabs的current双向绑定
|
||||||
|
|
||||||
|
fix: checkbox独立使用时checked赋初始值可以,但是手动切换时值没有做双向绑定! #455
|
||||||
|
|
||||||
|
feat: slider组件支持区间双滑块
|
||||||
|
|
||||||
|
fix: toast 支持自定义图标?可传入了决对路径的 icon也没有用 #409
|
||||||
|
|
||||||
|
feat: form-item校验失败时 增加class方便自定义显示错误的展示方式 #394
|
||||||
|
|
||||||
|
fix: up-cell的required配置不生效 #395
|
||||||
|
|
||||||
|
fix: 横向滚动组件,微信小程序编译后会有警告 #415
|
||||||
|
|
||||||
|
fix: u-picker内部对默认值defaultIndex的监听 #425
|
||||||
|
|
||||||
|
feat: toast 组件支持遮掩层穿透 #417
|
||||||
|
|
||||||
|
fix: 兼容vue的slot编译bug #423
|
||||||
|
|
||||||
|
fix: upload 微信小程序 点击预览视频报错 #424
|
||||||
|
|
||||||
|
fix: u-number-box 组件修改【integer, decimalLength, min, max 】props时没有触发绑定值更新 #429
|
||||||
|
|
||||||
|
feat: Tabs组件能否支持自定义插槽 #439
|
||||||
|
|
||||||
|
feat: ActionSheet 可以配置最大高度吗, 我当做select使用了。 #445
|
||||||
|
|
||||||
|
fix: cursor-pointer优化
|
||||||
|
|
||||||
|
feat: 新版slider组件兼容NVUE改造
|
||||||
|
|
||||||
|
feat: 新增slider组件手动实现以支持样式自定义
|
||||||
|
|
||||||
|
perf:补充TS声明提示信息
|
||||||
|
|
||||||
|
修复:ActionSheet 操作菜单cancelText属性为空DOM节点还存在并且可以点击问题
|
||||||
|
|
||||||
|
fix: 去除预留的beforeDestroy兼容容易在某些sdk下不识别条件编译
|
||||||
|
|
||||||
|
## 3.3.6(2024-07-23)
|
||||||
|
feat: u-album组件添加radius,shape参数,定义参考当前u-image参数
|
||||||
|
|
||||||
|
fix: 修复了calendar组件title和日期title未垂直居中的问题
|
||||||
|
|
||||||
|
fix: update:modelValue缺失emit定义
|
||||||
|
|
||||||
|
## 3.3.5(2024-07-10)
|
||||||
|
picker组件支持hasInput模式
|
||||||
|
|
||||||
|
## 3.3.4(2024-07-07)
|
||||||
|
fix: input组件双向绑定问题 #419
|
||||||
|
|
||||||
|
lazy-load完善emit
|
||||||
|
|
||||||
|
优化通用小程序分享
|
||||||
|
|
||||||
|
## 3.3.2(2024-06-27)
|
||||||
|
fix: 在Nvue环境中编译,出现大量警告 #406
|
||||||
|
## 3.3.1(2024-06-27)
|
||||||
|
u-button组件报错,找不到button mixins #407
|
||||||
|
## 3.3.0(2024-06-27)
|
||||||
|
feat: checkbox支持label设置slot
|
||||||
|
|
||||||
|
feat: modal增加customClass
|
||||||
|
|
||||||
|
feat: navbar、popup、tabs、text支持customClass
|
||||||
|
|
||||||
|
fix: cell组建缺少flex布局
|
||||||
|
|
||||||
|
fix: 修复微信小程序真机调试时快速输入出现文本回退问题
|
||||||
|
|
||||||
|
feat: tag增加默认slot
|
||||||
|
|
||||||
|
公共mixin改造为按需导入语法
|
||||||
|
|
||||||
|
refactor: 组件props混入mixin改造为按需导入语法
|
||||||
|
|
||||||
|
fix: u-tabbar 安卓手机点击按钮变蓝问题 #396
|
||||||
|
|
||||||
|
feat: upload组建增加extension属性
|
||||||
|
|
||||||
|
fix: upload组件参数mode添加left
|
||||||
|
|
||||||
|
fix: 修复阴影在非nvue时白色背景色不显示
|
||||||
|
|
||||||
|
## 3.2.24(2024-06-11)
|
||||||
|
fix: 修复时间选择器confirm事件触发时机导致2次才会触发v-model更新
|
||||||
|
## 3.2.23(2024-05-30)
|
||||||
|
fix: #378 H5 u-input 在表单中初始值为空也会触发一次 formValidate(this,"change")事件导致进入页面直接校验了一次
|
||||||
|
|
||||||
|
fix: #373 搜索组件up-search的@clear事件无效
|
||||||
|
|
||||||
|
fix: #372 ActionSheet 组件的取消按钮触发区域太小
|
||||||
|
|
||||||
|
## 3.2.22(2024-05-13)
|
||||||
|
上传组件支持微信小程序预览视频
|
||||||
|
|
||||||
|
修复折叠面板右侧箭头不显示
|
||||||
|
|
||||||
|
修复uxp2px
|
||||||
|
|
||||||
|
## 3.2.21(2024-05-10)
|
||||||
|
fix: loading-icon修复flex布局
|
||||||
|
## 3.2.20(2024-05-10)
|
||||||
|
修复瀑布流大小写#355
|
||||||
|
## 3.2.19(2024-05-10)
|
||||||
|
去除意外的文件引入
|
||||||
|
## 3.2.18(2024-05-09)
|
||||||
|
fix: 349 popup 组件设置 zIndex 属性后,组件渲染异常#
|
||||||
|
feat: 搜索框增加adjustPosition属性
|
||||||
|
fix: #331增加u-action-sheet__cancel
|
||||||
|
优化mixin兼容性
|
||||||
|
feat: #326 up-list增加下拉刷新功能
|
||||||
|
fix: #319 优化up-tabs参数与定义匹配
|
||||||
|
fix: index-list组件微信小程序端使用自定义导航栏异常
|
||||||
|
fix: #285 pickerimmediateChange 写死为true
|
||||||
|
fix: #111 u-scroll-list组件,隐藏指示器后报错, 提示找不到ref
|
||||||
|
list增加微信小程序防抖配置
|
||||||
|
|
||||||
|
## 3.2.17(2024-05-08)
|
||||||
|
fix: 支付宝小程序二维码渲染
|
||||||
|
## 3.2.16(2024-05-06)
|
||||||
|
修复tabs中,当前激活样式的undefined bug
|
||||||
|
|
||||||
|
fix: #341u-code 倒计时没结束前退出,再次进入结束后退出界面,再次进入重新开始倒计时bug
|
||||||
|
|
||||||
|
受到uni-app内置text样式影响修复
|
||||||
|
|
||||||
|
## 3.2.15(2024-04-28)
|
||||||
|
优化时间选择器hasInput模式初始化值
|
||||||
|
## 3.2.14(2024-04-24)
|
||||||
|
去除pleaseSetTranspileDependencies
|
||||||
|
|
||||||
|
http采用useStore
|
||||||
|
|
||||||
|
## 3.2.13(2024-04-22)
|
||||||
|
修复modal标题样式
|
||||||
|
|
||||||
|
优化日期选择器hasInput模式宽度
|
||||||
|
|
||||||
|
## 3.2.12(2024-04-22)
|
||||||
|
修复color应用
|
||||||
|
## 3.2.11(2024-04-18)
|
||||||
|
修复import化带来的问题
|
||||||
|
## 3.2.10(2024-04-17)
|
||||||
|
完善input清空事件App端失效的兼容性
|
||||||
|
|
||||||
|
修复日历组件二次打开后当前月份显示不正确
|
||||||
|
|
||||||
|
## 3.2.9(2024-04-16)
|
||||||
|
组件内uni.$u用法改为import引入
|
||||||
|
|
||||||
|
规范化及兼容性增强
|
||||||
|
|
||||||
|
## 3.2.8(2024-04-15)
|
||||||
|
修复up-tag语法错
|
||||||
|
## 3.2.7(2024-04-15)
|
||||||
|
修复下拉菜单背景色在支付宝小程序无效
|
||||||
|
|
||||||
|
setConfig改为浅拷贝解决无法用import导入代替uni.$u.props设置
|
||||||
|
|
||||||
|
## 3.2.6(2024-04-14)
|
||||||
|
修复某些情况下滑动单元格默认右侧按钮是展开的问题
|
||||||
|
## 3.2.5(2024-04-13)
|
||||||
|
调整分段器尺寸及修复窗口大小改变时重新计算尺寸
|
||||||
|
|
||||||
|
多个组件支持cursor-pointer增强PC端体验
|
||||||
|
|
||||||
|
## 3.2.4(2024-04-12)
|
||||||
|
初步支持typescript
|
||||||
|
## 3.2.3(2024-04-12)
|
||||||
|
fix: 修复square属性在小程序下无效问题
|
||||||
|
|
||||||
|
fix:修复lastIndex异常导致的column异常问题
|
||||||
|
|
||||||
|
fix: alipayapp picker style
|
||||||
|
|
||||||
|
feat(button): 添加用户同意隐私协议事件回调
|
||||||
|
|
||||||
|
fix: input switch password
|
||||||
|
|
||||||
|
fix: 修复u-code组件keepRuning失效问题
|
||||||
|
|
||||||
|
feat: form-item添加labelPosition属性
|
||||||
|
|
||||||
|
新增dropdown组件
|
||||||
|
|
||||||
|
分段器支持内部current值
|
||||||
|
|
||||||
|
优化cell和action-sheet视觉大小
|
||||||
|
|
||||||
|
修复tabs文字换行
|
||||||
|
|
||||||
|
## 3.2.2(2024-04-11)
|
||||||
|
修复换行符问题
|
||||||
|
## 3.2.1(2024-04-11)
|
||||||
|
修复演示H5二维码
|
||||||
|
|
||||||
|
fix: #270 ReadMore 展开阅读更多内容变化兼容
|
||||||
|
|
||||||
|
fix: #238Calendar组件maxDate修改为不能小于minDate
|
||||||
|
|
||||||
|
checkbox支持独立使用
|
||||||
|
|
||||||
|
修复popup中在微信小程序中真机调试滚动失效
|
||||||
|
|
||||||
|
## 3.2.0(2024-04-10)
|
||||||
|
修复轮播图在nvue显示
|
||||||
|
修复疑似u-slider名称被占用导致slider在App下不显示
|
||||||
|
解决微信小程序提示 Some selectors are not allowed in component wxss
|
||||||
|
示例中u-前缀统一为up-
|
||||||
|
增加瀑布流与图片懒加载组件
|
||||||
|
fix: #308修复tag组件缺失iconColor参数
|
||||||
|
fix: #297使用grid布局解决目前编译为抖音小程序无法开启virtualHost
|
||||||
|
## 3.1.52(2024-04-07)
|
||||||
|
工具类方法调用import化改造
|
||||||
|
新增up-copy复制组件
|
||||||
|
## 3.1.51(2024-04-07)
|
||||||
|
优化时间选择器自带输入框格式化显示
|
||||||
|
防止按钮文字换行
|
||||||
|
修复订单列表模板滑动
|
||||||
|
增加u-qrcode二维码组件
|
||||||
|
## 3.1.49(2024-03-27)
|
||||||
|
日期时间组件支持自带输入框
|
||||||
|
fix: popup弹窗滚动穿透问题
|
||||||
|
fix: 修复小程序numberbox bug
|
||||||
|
## 3.1.48(2024-03-18)
|
||||||
|
fix:[plugin:uni:pre-css] Unbalanced delimiter found in string
|
||||||
|
## 3.1.47(2024-03-18)
|
||||||
|
fix: setConfig设置组件默认参数无效问题
|
||||||
|
fix: 修复自定义图标无效问题
|
||||||
|
feat: 增加u-form-item单独设置规则变量
|
||||||
|
fix:#293小程序是自定义导航栏的时候即传了customNavHeight的时候会出现跳转偏移的情况
|
||||||
|
|
||||||
|
## 3.1.46(2024-01-29)
|
||||||
|
beforeUnmount
|
||||||
|
## 3.1.45(2024-01-24)
|
||||||
|
fix: #262ext组件为超链接的情况下size属性不生效
|
||||||
|
fix: #263最新版本3.1.42中微信小程序u-swipe-action-item报错
|
||||||
|
fix: #224最新版本3.1.42中微信小程序u-swipe-action-item报错
|
||||||
|
fix: #263支持支付宝小程序
|
||||||
|
fix: #261u-input在直接修改v-model的绑定值时,每隔一次会无法出发change事件
|
||||||
|
优化折叠面板兼容微信小程序
|
||||||
|
## 3.1.42(2024-01-15)
|
||||||
|
修复u-number-box默认值0时在小程序不显示值
|
||||||
|
优化u-code的timer判断
|
||||||
|
优化支付宝小程序下textarea字数统计兼容
|
||||||
|
优化u-calendar
|
||||||
|
## 3.1.41(2023-11-18)
|
||||||
|
#215优化u-cell图标容器间距问题
|
||||||
|
## 3.1.40(2023-11-16)
|
||||||
|
修复u-slider双向绑定
|
||||||
|
## 3.1.39(2023-11-10)
|
||||||
|
修复头条小程序不支持env(safe-area-inset-bottom)
|
||||||
|
优化#201u-grid 指定列数导致闪烁
|
||||||
|
#193IndexList 索引列表 高度错误
|
||||||
|
其他优化
|
||||||
|
## 3.1.38(2023-10-08)
|
||||||
|
修复u-slider
|
||||||
|
## 3.1.37(2023-09-13)
|
||||||
|
完善emits定义及修复code-input双向数据绑定
|
||||||
|
## 3.1.36(2023-08-08)
|
||||||
|
修复富文本事件名称大小写
|
||||||
|
## 3.1.35(2023-08-02)
|
||||||
|
修复编译到支付宝小程序u-form报错
|
||||||
|
## 3.1.34(2023-07-27)
|
||||||
|
修复App打包uni.$u.mpMixin方式sdk暂时不支持导致报错
|
||||||
|
## 3.1.33(2023-07-13)
|
||||||
|
修复弹窗进入动画、模板页面样式等
|
||||||
|
## 3.1.31(2023-07-11)
|
||||||
|
修复dayjs引用
|
||||||
|
## 3.0.8(2022-07-12)
|
||||||
|
修复u-tag默认宽度撑满容器
|
||||||
|
## 3.0.7(2022-07-12)
|
||||||
|
修复u-navbar自定义插槽演示示例
|
||||||
|
## 3.0.6(2022-07-11)
|
||||||
|
修复u-image缺少emits申明
|
||||||
|
## 3.0.5(2022-07-11)
|
||||||
|
修复u-upload缺少emits申明
|
||||||
|
## 3.0.4(2022-07-10)
|
||||||
|
修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定
|
||||||
|
## 3.0.3(2022-07-09)
|
||||||
|
启用自建演示二维码
|
||||||
|
## 3.0.2(2022-07-09)
|
||||||
|
修复dayjs/clipboard等导致打包报错
|
||||||
|
## 3.0.1(2022-07-09)
|
||||||
|
增加插件市场地址
|
||||||
|
## 3.0.0(2022-07-09)
|
||||||
|
# uview-plus(vue3)初步发布
|
||||||
80
uni_modules/uview-plus/components/u--form/u--form.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<uvForm
|
||||||
|
ref="uForm"
|
||||||
|
:model="model"
|
||||||
|
:rules="rules"
|
||||||
|
:errorType="errorType"
|
||||||
|
:borderBottom="borderBottom"
|
||||||
|
:labelPosition="labelPosition"
|
||||||
|
:labelWidth="labelWidth"
|
||||||
|
:labelAlign="labelAlign"
|
||||||
|
:labelStyle="labelStyle"
|
||||||
|
:customStyle="customStyle"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</uvForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
|
||||||
|
* 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
|
||||||
|
*/
|
||||||
|
import uvForm from '../u-form/u-form.vue';
|
||||||
|
import { props } from '../u-form/props.js';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
export default {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
name: 'u-form',
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
name: 'u--form',
|
||||||
|
// #endif
|
||||||
|
mixins: [mpMixin, props, mixin],
|
||||||
|
components: {
|
||||||
|
uvForm
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.children = []
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
|
||||||
|
setRules(rules) {
|
||||||
|
this.$refs.uForm.setRules(rules)
|
||||||
|
},
|
||||||
|
validate() {
|
||||||
|
/**
|
||||||
|
* 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
|
||||||
|
* 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
|
||||||
|
* 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
|
||||||
|
*/
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.setMpData()
|
||||||
|
// #endif
|
||||||
|
return this.$refs.uForm.validate()
|
||||||
|
},
|
||||||
|
validateField(value, callback) {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.setMpData()
|
||||||
|
// #endif
|
||||||
|
return this.$refs.uForm.validateField(value, callback)
|
||||||
|
},
|
||||||
|
resetFields() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.setMpData()
|
||||||
|
// #endif
|
||||||
|
return this.$refs.uForm.resetFields()
|
||||||
|
},
|
||||||
|
clearValidate(props) {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
this.setMpData()
|
||||||
|
// #endif
|
||||||
|
return this.$refs.uForm.clearValidate(props)
|
||||||
|
},
|
||||||
|
setMpData() {
|
||||||
|
this.$refs.uForm.children = this.children
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
50
uni_modules/uview-plus/components/u--image/u--image.vue
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<uvImage
|
||||||
|
:src="src"
|
||||||
|
:mode="mode"
|
||||||
|
:width="width"
|
||||||
|
:height="height"
|
||||||
|
:shape="shape"
|
||||||
|
:radius="radius"
|
||||||
|
:lazyLoad="lazyLoad"
|
||||||
|
:showMenuByLongpress="showMenuByLongpress"
|
||||||
|
:loadingIcon="loadingIcon"
|
||||||
|
:errorIcon="errorIcon"
|
||||||
|
:showLoading="showLoading"
|
||||||
|
:showError="showError"
|
||||||
|
:fade="fade"
|
||||||
|
:webp="webp"
|
||||||
|
:duration="duration"
|
||||||
|
:bgColor="bgColor"
|
||||||
|
:customStyle="customStyle"
|
||||||
|
@click="$emit('click')"
|
||||||
|
@error="$emit('error')"
|
||||||
|
@load="$emit('load')"
|
||||||
|
>
|
||||||
|
<template v-slot:loading>
|
||||||
|
<slot name="loading"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-slot:error>
|
||||||
|
<slot name="error"></slot>
|
||||||
|
</template>
|
||||||
|
</uvImage>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
|
||||||
|
* 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
|
||||||
|
*/
|
||||||
|
import uvImage from '../u-image/u-image.vue';
|
||||||
|
import { props } from '../u-image/props.js';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
export default {
|
||||||
|
name: 'u--image',
|
||||||
|
mixins: [mpMixin, props, mixin],
|
||||||
|
components: {
|
||||||
|
uvImage
|
||||||
|
},
|
||||||
|
emits: ['click', 'error', 'load']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
74
uni_modules/uview-plus/components/u--input/u--input.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<template>
|
||||||
|
<uvInput
|
||||||
|
<!-- #ifdef VUE2 -->
|
||||||
|
:value="value"
|
||||||
|
@input="e => $emit('input', e)"
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifdef VUE3 -->
|
||||||
|
:modelValue="modelValue"
|
||||||
|
@update:modelValue="e => $emit('update:modelValue', e)"
|
||||||
|
<!-- #endif -->
|
||||||
|
:type="type"
|
||||||
|
:fixed="fixed"
|
||||||
|
:disabled="disabled"
|
||||||
|
:disabledColor="disabledColor"
|
||||||
|
:clearable="clearable"
|
||||||
|
:password="password"
|
||||||
|
:maxlength="maxlength"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:placeholderClass="placeholderClass"
|
||||||
|
:placeholderStyle="placeholderStyle"
|
||||||
|
:showWordLimit="showWordLimit"
|
||||||
|
:confirmType="confirmType"
|
||||||
|
:confirmHold="confirmHold"
|
||||||
|
:holdKeyboard="holdKeyboard"
|
||||||
|
:focus="focus"
|
||||||
|
:autoBlur="autoBlur"
|
||||||
|
:disableDefaultPadding="disableDefaultPadding"
|
||||||
|
:cursor="cursor"
|
||||||
|
:cursorSpacing="cursorSpacing"
|
||||||
|
:selectionStart="selectionStart"
|
||||||
|
:selectionEnd="selectionEnd"
|
||||||
|
:adjustPosition="adjustPosition"
|
||||||
|
:inputAlign="inputAlign"
|
||||||
|
:fontSize="fontSize"
|
||||||
|
:color="color"
|
||||||
|
:prefixIcon="prefixIcon"
|
||||||
|
:suffixIcon="suffixIcon"
|
||||||
|
:suffixIconStyle="suffixIconStyle"
|
||||||
|
:prefixIconStyle="prefixIconStyle"
|
||||||
|
:border="border"
|
||||||
|
:readonly="readonly"
|
||||||
|
:shape="shape"
|
||||||
|
:customStyle="customStyle"
|
||||||
|
:formatter="formatter"
|
||||||
|
:ignoreCompositionEvent="ignoreCompositionEvent"
|
||||||
|
>
|
||||||
|
<!-- #ifdef MP -->
|
||||||
|
<slot name="prefix"></slot>
|
||||||
|
<slot name="suffix"></slot>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef MP -->
|
||||||
|
<slot name="prefix" slot="prefix"></slot>
|
||||||
|
<slot name="suffix" slot="suffix"></slot>
|
||||||
|
<!-- #endif -->
|
||||||
|
</uvInput>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
|
||||||
|
* 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
|
||||||
|
*/
|
||||||
|
import uvInput from '../u-input/u-input.vue';
|
||||||
|
import { props } from '../u-input/props.js';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
export default {
|
||||||
|
name: 'u--input',
|
||||||
|
mixins: [mpMixin, props, mixin],
|
||||||
|
components: {
|
||||||
|
uvInput
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
45
uni_modules/uview-plus/components/u--text/u--text.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<uvText
|
||||||
|
:type="type"
|
||||||
|
:show="show"
|
||||||
|
:text="text"
|
||||||
|
:prefixIcon="prefixIcon"
|
||||||
|
:suffixIcon="suffixIcon"
|
||||||
|
:mode="mode"
|
||||||
|
:href="href"
|
||||||
|
:format="format"
|
||||||
|
:call="call"
|
||||||
|
:openType="openType"
|
||||||
|
:bold="bold"
|
||||||
|
:block="block"
|
||||||
|
:lines="lines"
|
||||||
|
:color="color"
|
||||||
|
:decoration="decoration"
|
||||||
|
:size="size"
|
||||||
|
:iconStyle="iconStyle"
|
||||||
|
:margin="margin"
|
||||||
|
:lineHeight="lineHeight"
|
||||||
|
:align="align"
|
||||||
|
:wordWrap="wordWrap"
|
||||||
|
:customStyle="customStyle"
|
||||||
|
></uvText>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
|
||||||
|
* 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
|
||||||
|
* 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
|
||||||
|
*/
|
||||||
|
import uvText from "../u-text/u-text.vue";
|
||||||
|
import { props } from "../u-text/props.js";
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin.js'
|
||||||
|
import { mixin } from '../../libs/mixin/mixin.js'
|
||||||
|
export default {
|
||||||
|
name: "u--text",
|
||||||
|
mixins: [mpMixin, mixin, props,],
|
||||||
|
components: {
|
||||||
|
uvText,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<template>
|
||||||
|
<uvTextarea
|
||||||
|
:value="value"
|
||||||
|
:modelValue="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:height="height"
|
||||||
|
:confirmType="confirmType"
|
||||||
|
:disabled="disabled"
|
||||||
|
:count="count"
|
||||||
|
:focus="focus"
|
||||||
|
:autoHeight="autoHeight"
|
||||||
|
:fixed="fixed"
|
||||||
|
:cursorSpacing="cursorSpacing"
|
||||||
|
:cursor="cursor"
|
||||||
|
:showConfirmBar="showConfirmBar"
|
||||||
|
:selectionStart="selectionStart"
|
||||||
|
:selectionEnd="selectionEnd"
|
||||||
|
:adjustPosition="adjustPosition"
|
||||||
|
:disableDefaultPadding="disableDefaultPadding"
|
||||||
|
:holdKeyboard="holdKeyboard"
|
||||||
|
:maxlength="maxlength"
|
||||||
|
:border="border"
|
||||||
|
:customStyle="customStyle"
|
||||||
|
:formatter="formatter"
|
||||||
|
:ignoreCompositionEvent="ignoreCompositionEvent"
|
||||||
|
@input="e => $emit('input', e)"
|
||||||
|
@update:modelValue="e => $emit('update:modelValue', e)"
|
||||||
|
></uvTextarea>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
|
||||||
|
* 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
|
||||||
|
*/
|
||||||
|
import uvTextarea from '../u-textarea/u-textarea.vue';
|
||||||
|
import { props } from '../u-textarea/props.js';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
export default {
|
||||||
|
name: 'u--textarea',
|
||||||
|
mixins: [mpMixin, props, mixin],
|
||||||
|
components: {
|
||||||
|
uvTextarea
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* @Author : LQ
|
||||||
|
* @Description :
|
||||||
|
* @version : 1.0
|
||||||
|
* @Date : 2021-08-20 16:44:21
|
||||||
|
* @LastAuthor : LQ
|
||||||
|
* @lastTime : 2021-08-20 16:44:35
|
||||||
|
* @FilePath : /u-view2.0/uview-ui/libs/config/props/actionSheet.js
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// action-sheet组件
|
||||||
|
actionSheet: {
|
||||||
|
show: false,
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
actions: [],
|
||||||
|
index: '',
|
||||||
|
cancelText: '',
|
||||||
|
closeOnClickAction: true,
|
||||||
|
safeAreaInsetBottom: true,
|
||||||
|
openType: '',
|
||||||
|
closeOnClickOverlay: true,
|
||||||
|
round: 0,
|
||||||
|
wrapMaxHeight: '600px'
|
||||||
|
}
|
||||||
|
}
|
||||||
62
uni_modules/uview-plus/components/u-action-sheet/props.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
|
||||||
|
export const props = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 操作菜单是否展示 (默认false)
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.actionSheet.show
|
||||||
|
},
|
||||||
|
// 标题
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.actionSheet.title
|
||||||
|
},
|
||||||
|
// 选项上方的描述信息
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.actionSheet.description
|
||||||
|
},
|
||||||
|
// 数据
|
||||||
|
actions: {
|
||||||
|
type: Array,
|
||||||
|
default: () => defProps.actionSheet.actions
|
||||||
|
},
|
||||||
|
// 取消按钮的文字,不为空时显示按钮
|
||||||
|
cancelText: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.actionSheet.cancelText
|
||||||
|
},
|
||||||
|
// 点击某个菜单项时是否关闭弹窗
|
||||||
|
closeOnClickAction: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.actionSheet.closeOnClickAction
|
||||||
|
},
|
||||||
|
// 处理底部安全区(默认true)
|
||||||
|
safeAreaInsetBottom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.actionSheet.safeAreaInsetBottom
|
||||||
|
},
|
||||||
|
// 小程序的打开方式
|
||||||
|
openType: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.actionSheet.openType
|
||||||
|
},
|
||||||
|
// 点击遮罩是否允许关闭 (默认true)
|
||||||
|
closeOnClickOverlay: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.actionSheet.closeOnClickOverlay
|
||||||
|
},
|
||||||
|
// 圆角值
|
||||||
|
round: {
|
||||||
|
type: [Boolean, String, Number],
|
||||||
|
default: () => defProps.actionSheet.round
|
||||||
|
},
|
||||||
|
// 选项区域最大高度
|
||||||
|
wrapMaxHeight: {
|
||||||
|
type: [String],
|
||||||
|
default: () => defProps.actionSheet.wrapMaxHeight
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,283 @@
|
|||||||
|
|
||||||
|
<template>
|
||||||
|
<u-popup
|
||||||
|
:show="show"
|
||||||
|
mode="bottom"
|
||||||
|
@close="closeHandler"
|
||||||
|
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||||
|
:round="round"
|
||||||
|
>
|
||||||
|
<view class="u-action-sheet">
|
||||||
|
<view
|
||||||
|
class="u-action-sheet__header"
|
||||||
|
v-if="title"
|
||||||
|
>
|
||||||
|
<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
|
||||||
|
<view
|
||||||
|
class="u-action-sheet__header__icon-wrap"
|
||||||
|
@tap.stop="cancel"
|
||||||
|
>
|
||||||
|
<u-icon
|
||||||
|
name="close"
|
||||||
|
size="17"
|
||||||
|
color="#c8c9cc"
|
||||||
|
bold
|
||||||
|
></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text
|
||||||
|
class="u-action-sheet__description"
|
||||||
|
:style="[{
|
||||||
|
marginTop: `${title && description ? 0 : '18px'}`
|
||||||
|
}]"
|
||||||
|
v-if="description"
|
||||||
|
>{{description}}</text>
|
||||||
|
<slot>
|
||||||
|
<u-line v-if="description"></u-line>
|
||||||
|
<scroll-view scroll-y class="u-action-sheet__item-wrap" :style="{maxHeight: wrapMaxHeight}">
|
||||||
|
<view :key="index" v-for="(item, index) in actions">
|
||||||
|
<!-- #ifdef MP -->
|
||||||
|
<button
|
||||||
|
class="u-reset-button"
|
||||||
|
:openType="item.openType"
|
||||||
|
@getuserinfo="onGetUserInfo"
|
||||||
|
@contact="onContact"
|
||||||
|
@getphonenumber="onGetPhoneNumber"
|
||||||
|
@error="onError"
|
||||||
|
@launchapp="onLaunchApp"
|
||||||
|
@opensetting="onOpenSetting"
|
||||||
|
:lang="lang"
|
||||||
|
:session-from="sessionFrom"
|
||||||
|
:send-message-title="sendMessageTitle"
|
||||||
|
:send-message-path="sendMessagePath"
|
||||||
|
:send-message-img="sendMessageImg"
|
||||||
|
:show-message-card="showMessageCard"
|
||||||
|
:app-parameter="appParameter"
|
||||||
|
@tap="selectHandler(index)"
|
||||||
|
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
|
||||||
|
>
|
||||||
|
<!-- #endif -->
|
||||||
|
<view
|
||||||
|
class="u-action-sheet__item-wrap__item"
|
||||||
|
@tap.stop="selectHandler(index)"
|
||||||
|
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
|
||||||
|
:hover-stay-time="150"
|
||||||
|
>
|
||||||
|
<template v-if="!item.loading">
|
||||||
|
<text
|
||||||
|
class="u-action-sheet__item-wrap__item__name"
|
||||||
|
:style="[itemStyle(index)]"
|
||||||
|
>{{ item.name }}</text>
|
||||||
|
<text
|
||||||
|
v-if="item.subname"
|
||||||
|
class="u-action-sheet__item-wrap__item__subname"
|
||||||
|
>{{ item.subname }}</text>
|
||||||
|
</template>
|
||||||
|
<u-loading-icon
|
||||||
|
v-else
|
||||||
|
custom-class="van-action-sheet__loading"
|
||||||
|
size="18"
|
||||||
|
mode="circle"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<!-- #ifdef MP -->
|
||||||
|
</button>
|
||||||
|
<!-- #endif -->
|
||||||
|
<u-line v-if="index !== actions.length - 1"></u-line>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</slot>
|
||||||
|
<u-gap
|
||||||
|
bgColor="#eaeaec"
|
||||||
|
height="6"
|
||||||
|
v-if="cancelText"
|
||||||
|
></u-gap>
|
||||||
|
<view class="u-action-sheet__item-wrap__item u-action-sheet__cancel"
|
||||||
|
hover-class="u-action-sheet--hover" @tap="cancel" v-if="cancelText">
|
||||||
|
<text
|
||||||
|
@touchmove.stop.prevent
|
||||||
|
:hover-stay-time="150"
|
||||||
|
class="u-action-sheet__cancel-text"
|
||||||
|
>{{cancelText}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { openType } from '../../libs/mixin/openType'
|
||||||
|
import { buttonMixin } from '../../libs/mixin/button'
|
||||||
|
import { props } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addUnit } from '../../libs/function/index';
|
||||||
|
/**
|
||||||
|
* ActionSheet 操作菜单
|
||||||
|
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
|
||||||
|
* @tutorial https://ijry.github.io/uview-plus/components/actionSheet.html
|
||||||
|
*
|
||||||
|
* @property {Boolean} show 操作菜单是否展示 (默认 false )
|
||||||
|
* @property {String} title 操作菜单标题
|
||||||
|
* @property {String} description 选项上方的描述信息
|
||||||
|
* @property {Array<Object>} actions 按钮的文字数组,见官方文档示例
|
||||||
|
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
|
||||||
|
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 (默认 true )
|
||||||
|
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 (默认 true )
|
||||||
|
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
|
||||||
|
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
|
||||||
|
* @property {Number|String} round 圆角值,默认无圆角 (默认 0 )
|
||||||
|
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
|
||||||
|
* @property {String} sessionFrom 会话来源,openType="contact"时有效
|
||||||
|
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
|
||||||
|
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
|
||||||
|
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
|
||||||
|
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
|
||||||
|
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
|
||||||
|
*
|
||||||
|
* @event {Function} select 点击ActionSheet列表项时触发
|
||||||
|
* @event {Function} close 点击取消按钮时触发
|
||||||
|
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
|
||||||
|
* @event {Function} contact 客服消息回调,openType="contact"时有效
|
||||||
|
* @event {Function} getphonenumber 获取用户手机号回调,openType="getPhoneNumber"时有效
|
||||||
|
* @event {Function} error 当使用开放能力时,发生错误的回调,openType="error"时有效
|
||||||
|
* @event {Function} launchapp 打开 APP 成功的回调,openType="launchApp"时有效
|
||||||
|
* @event {Function} opensetting 在打开授权设置页后回调,openType="openSetting"时有效
|
||||||
|
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: "u-action-sheet",
|
||||||
|
// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
|
||||||
|
mixins: [openType, buttonMixin, mixin, props],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 操作项目的样式
|
||||||
|
itemStyle() {
|
||||||
|
return (index) => {
|
||||||
|
let style = {};
|
||||||
|
if (this.actions[index].color) style.color = this.actions[index].color
|
||||||
|
if (this.actions[index].fontSize) style.fontSize = addUnit(this.actions[index].fontSize)
|
||||||
|
// 选项被禁用的样式
|
||||||
|
if (this.actions[index].disabled) style.color = '#c0c4cc'
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ["close", "select", "update:show"],
|
||||||
|
methods: {
|
||||||
|
closeHandler() {
|
||||||
|
// 允许点击遮罩关闭时,才发出close事件
|
||||||
|
if(this.closeOnClickOverlay) {
|
||||||
|
this.$emit('update:show', false)
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 点击取消按钮
|
||||||
|
cancel() {
|
||||||
|
this.$emit('update:show', false)
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
selectHandler(index) {
|
||||||
|
const item = this.actions[index]
|
||||||
|
if (item && !item.disabled && !item.loading) {
|
||||||
|
this.$emit('select', item)
|
||||||
|
if (this.closeOnClickAction) {
|
||||||
|
this.$emit('update:show', false)
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../../libs/css/components.scss";
|
||||||
|
$u-action-sheet-reset-button-width:100% !default;
|
||||||
|
$u-action-sheet-title-font-size: 16px !default;
|
||||||
|
$u-action-sheet-title-padding: 12px 30px !default;
|
||||||
|
$u-action-sheet-title-color: $u-main-color !default;
|
||||||
|
$u-action-sheet-header-icon-wrap-right:15px !default;
|
||||||
|
$u-action-sheet-header-icon-wrap-top:15px !default;
|
||||||
|
$u-action-sheet-description-font-size:13px !default;
|
||||||
|
$u-action-sheet-description-color:14px !default;
|
||||||
|
$u-action-sheet-description-margin: 18px 15px !default;
|
||||||
|
$u-action-sheet-item-wrap-item-padding:17px !default;
|
||||||
|
$u-action-sheet-item-wrap-name-font-size:16px !default;
|
||||||
|
$u-action-sheet-item-wrap-subname-font-size:13px !default;
|
||||||
|
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
|
||||||
|
$u-action-sheet-item-wrap-subname-margin-top:10px !default;
|
||||||
|
$u-action-sheet-cancel-text-font-size:16px !default;
|
||||||
|
$u-action-sheet-cancel-text-color:$u-content-color !default;
|
||||||
|
$u-action-sheet-cancel-text-font-size:15px !default;
|
||||||
|
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
|
||||||
|
|
||||||
|
.u-reset-button {
|
||||||
|
width: $u-action-sheet-reset-button-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.u-action-sheet {
|
||||||
|
text-align: center;
|
||||||
|
&__header {
|
||||||
|
position: relative;
|
||||||
|
padding: $u-action-sheet-title-padding;
|
||||||
|
&__title {
|
||||||
|
font-size: $u-action-sheet-title-font-size;
|
||||||
|
color: $u-action-sheet-title-color;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon-wrap {
|
||||||
|
position: absolute;
|
||||||
|
right: $u-action-sheet-header-icon-wrap-right;
|
||||||
|
top: $u-action-sheet-header-icon-wrap-top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
font-size: $u-action-sheet-description-font-size;
|
||||||
|
color: $u-tips-color;
|
||||||
|
margin: $u-action-sheet-description-margin;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item-wrap {
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
padding: $u-action-sheet-item-wrap-item-padding;
|
||||||
|
@include flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-size: $u-action-sheet-item-wrap-name-font-size;
|
||||||
|
color: $u-main-color;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subname {
|
||||||
|
font-size: $u-action-sheet-item-wrap-subname-font-size;
|
||||||
|
color: $u-action-sheet-item-wrap-subname-color;
|
||||||
|
margin-top: $u-action-sheet-item-wrap-subname-margin-top;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__cancel-text {
|
||||||
|
font-size: $u-action-sheet-cancel-text-font-size;
|
||||||
|
color: $u-action-sheet-cancel-text-color;
|
||||||
|
text-align: center;
|
||||||
|
// padding: $u-action-sheet-cancel-text-font-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--hover {
|
||||||
|
background-color: $u-action-sheet-cancel-text-hover-background-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
28
uni_modules/uview-plus/components/u-album/album.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* @Author : LQ
|
||||||
|
* @Description :
|
||||||
|
* @version : 1.0
|
||||||
|
* @Date : 2021-08-20 16:44:21
|
||||||
|
* @LastAuthor : LQ
|
||||||
|
* @lastTime : 2021-08-20 16:47:24
|
||||||
|
* @FilePath : /u-view2.0/uview-ui/libs/config/props/album.js
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// album 组件
|
||||||
|
album: {
|
||||||
|
urls: [],
|
||||||
|
keyName: '',
|
||||||
|
singleSize: 180,
|
||||||
|
multipleSize: 70,
|
||||||
|
space: 6,
|
||||||
|
singleMode: 'scaleToFill',
|
||||||
|
multipleMode: 'aspectFill',
|
||||||
|
maxCount: 9,
|
||||||
|
previewFullImage: true,
|
||||||
|
rowCount: 3,
|
||||||
|
showMore: true,
|
||||||
|
autoWrap: false,
|
||||||
|
unit: 'px',
|
||||||
|
stop: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
86
uni_modules/uview-plus/components/u-album/props.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
export const props = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 图片地址,Array<String>|Array<Object>形式
|
||||||
|
urls: {
|
||||||
|
type: Array,
|
||||||
|
default: () => defProps.album.urls
|
||||||
|
},
|
||||||
|
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||||
|
keyName: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.album.keyName
|
||||||
|
},
|
||||||
|
// 单图时,图片长边的长度
|
||||||
|
singleSize: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.album.singleSize
|
||||||
|
},
|
||||||
|
// 多图时,图片边长
|
||||||
|
multipleSize: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.album.multipleSize
|
||||||
|
},
|
||||||
|
// 多图时,图片水平和垂直之间的间隔
|
||||||
|
space: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.album.space
|
||||||
|
},
|
||||||
|
// 单图时,图片缩放裁剪的模式
|
||||||
|
singleMode: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.album.singleMode
|
||||||
|
},
|
||||||
|
// 多图时,图片缩放裁剪的模式
|
||||||
|
multipleMode: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.album.multipleMode
|
||||||
|
},
|
||||||
|
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
|
||||||
|
maxCount: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.album.maxCount
|
||||||
|
},
|
||||||
|
// 是否可以预览图片
|
||||||
|
previewFullImage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.album.previewFullImage
|
||||||
|
},
|
||||||
|
// 每行展示图片数量,如设置,singleSize和multipleSize将会无效
|
||||||
|
rowCount: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.album.rowCount
|
||||||
|
},
|
||||||
|
// 超出maxCount时是否显示查看更多的提示
|
||||||
|
showMore: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.album.showMore
|
||||||
|
},
|
||||||
|
// 图片形状,circle-圆形,square-方形
|
||||||
|
shape: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.image.shape
|
||||||
|
},
|
||||||
|
// 圆角,单位任意
|
||||||
|
radius: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.image.radius
|
||||||
|
},
|
||||||
|
// 自适应换行
|
||||||
|
autoWrap: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.album.autoWrap
|
||||||
|
},
|
||||||
|
// 单位
|
||||||
|
unit: {
|
||||||
|
type: [String],
|
||||||
|
default: () => defProps.album.unit
|
||||||
|
},
|
||||||
|
// 阻止点击冒泡
|
||||||
|
stop: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.album.stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
279
uni_modules/uview-plus/components/u-album/u-album.vue
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
<template>
|
||||||
|
<view class="u-album">
|
||||||
|
<view
|
||||||
|
class="u-album__row"
|
||||||
|
ref="u-album__row"
|
||||||
|
v-for="(arr, index) in showUrls"
|
||||||
|
:forComputedUse="albumWidth"
|
||||||
|
:key="index"
|
||||||
|
:style="{flexWrap: autoWrap ? 'wrap' : 'nowrap'}"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="u-album__row__wrapper"
|
||||||
|
v-for="(item, index1) in arr"
|
||||||
|
:key="index1"
|
||||||
|
:style="[imageStyle(index + 1, index1 + 1)]"
|
||||||
|
@tap="previewFullImage ? onPreviewTap($event, getSrc(item)) : ''"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:src="getSrc(item)"
|
||||||
|
:mode="
|
||||||
|
urls.length === 1
|
||||||
|
? imageHeight > 0
|
||||||
|
? singleMode
|
||||||
|
: 'widthFix'
|
||||||
|
: multipleMode
|
||||||
|
"
|
||||||
|
:style="[
|
||||||
|
{
|
||||||
|
width: imageWidth,
|
||||||
|
height: imageHeight,
|
||||||
|
borderRadius: shape == 'circle' ? '10000px' : addUnit(radius)
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
></image>
|
||||||
|
<view
|
||||||
|
v-if="
|
||||||
|
showMore &&
|
||||||
|
urls.length > rowCount * showUrls.length &&
|
||||||
|
index === showUrls.length - 1 &&
|
||||||
|
index1 === showUrls[showUrls.length - 1].length - 1
|
||||||
|
"
|
||||||
|
class="u-album__row__wrapper__text"
|
||||||
|
:style="{
|
||||||
|
borderRadius: shape == 'circle' ? '50%' : addUnit(radius),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<up-text
|
||||||
|
:text="`+${urls.length - maxCount}`"
|
||||||
|
color="#fff"
|
||||||
|
:size="multipleSize * 0.3"
|
||||||
|
align="center"
|
||||||
|
customStyle="justify-content: center"
|
||||||
|
></up-text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { props } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addUnit, sleep } from '../../libs/function/index';
|
||||||
|
import test from '../../libs/function/test';
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
|
||||||
|
const dom = uni.requireNativePlugin('dom')
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Album 相册
|
||||||
|
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
|
||||||
|
* @tutorial https://ijry.github.io/uview-plus/components/album.html
|
||||||
|
*
|
||||||
|
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
|
||||||
|
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||||
|
* @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
|
||||||
|
* @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
|
||||||
|
* @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
|
||||||
|
* @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
|
||||||
|
* @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
|
||||||
|
* @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
|
||||||
|
* @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
|
||||||
|
* @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
|
||||||
|
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
|
||||||
|
* @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
|
||||||
|
* @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
|
||||||
|
* @property {Boolean} autoWrap 自适应换行模式,不受rowCount限制,图片会自动换行 (默认 false )
|
||||||
|
* @property {String} unit 图片单位 (默认 px )
|
||||||
|
* @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
|
||||||
|
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'u-album',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 单图的宽度
|
||||||
|
singleWidth: 0,
|
||||||
|
// 单图的高度
|
||||||
|
singleHeight: 0,
|
||||||
|
// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
|
||||||
|
singlePercent: 0.6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
urls: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
if (newVal.length === 1) {
|
||||||
|
this.getImageRect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["albumWidth"],
|
||||||
|
computed: {
|
||||||
|
imageStyle() {
|
||||||
|
return (index1, index2) => {
|
||||||
|
const { space, rowCount, multipleSize, urls } = this,
|
||||||
|
rowLen = this.showUrls.length,
|
||||||
|
allLen = this.urls.length
|
||||||
|
const style = {
|
||||||
|
marginRight: addUnit(space),
|
||||||
|
marginBottom: addUnit(space)
|
||||||
|
}
|
||||||
|
// 如果为最后一行,则每个图片都无需下边框
|
||||||
|
if (index1 === rowLen && !this.autoWrap) style.marginBottom = 0
|
||||||
|
// 每行的最右边一张和总长度的最后一张无需右边框
|
||||||
|
if (!this.autoWrap) {
|
||||||
|
if (
|
||||||
|
index2 === rowCount ||
|
||||||
|
(index1 === rowLen &&
|
||||||
|
index2 === this.showUrls[index1 - 1].length)
|
||||||
|
)
|
||||||
|
style.marginRight = 0
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 将数组划分为二维数组
|
||||||
|
showUrls() {
|
||||||
|
if (this.autoWrap) {
|
||||||
|
return [ this.urls.slice(0, this.maxCount) ];
|
||||||
|
} else {
|
||||||
|
const arr = []
|
||||||
|
this.urls.map((item, index) => {
|
||||||
|
// 限制最大展示数量
|
||||||
|
if (index + 1 <= this.maxCount) {
|
||||||
|
// 计算该元素为第几个素组内
|
||||||
|
const itemIndex = Math.floor(index / this.rowCount)
|
||||||
|
// 判断对应的索引是否存在
|
||||||
|
if (!arr[itemIndex]) {
|
||||||
|
arr[itemIndex] = []
|
||||||
|
}
|
||||||
|
arr[itemIndex].push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
},
|
||||||
|
imageWidth() {
|
||||||
|
return addUnit(
|
||||||
|
this.urls.length === 1 ? this.singleWidth : this.multipleSize, this.unit
|
||||||
|
)
|
||||||
|
},
|
||||||
|
imageHeight() {
|
||||||
|
return addUnit(
|
||||||
|
this.urls.length === 1 ? this.singleHeight : this.multipleSize, this.unit
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
|
||||||
|
// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
|
||||||
|
albumWidth() {
|
||||||
|
let width = 0
|
||||||
|
if (this.urls.length === 1) {
|
||||||
|
width = this.singleWidth
|
||||||
|
} else {
|
||||||
|
width =
|
||||||
|
this.showUrls[0].length * this.multipleSize +
|
||||||
|
this.space * (this.showUrls[0].length - 1)
|
||||||
|
}
|
||||||
|
this.$emit('albumWidth', width)
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addUnit,
|
||||||
|
// 预览图片
|
||||||
|
onPreviewTap(e, url) {
|
||||||
|
const urls = this.urls.map((item) => {
|
||||||
|
return this.getSrc(item)
|
||||||
|
})
|
||||||
|
uni.previewImage({
|
||||||
|
current: url,
|
||||||
|
urls
|
||||||
|
})
|
||||||
|
// 是否阻止事件传播
|
||||||
|
this.stop && this.preventEvent(e)
|
||||||
|
},
|
||||||
|
// 获取图片的路径
|
||||||
|
getSrc(item) {
|
||||||
|
return test.object(item)
|
||||||
|
? (this.keyName && item[this.keyName]) || item.src
|
||||||
|
: item
|
||||||
|
},
|
||||||
|
// 单图时,获取图片的尺寸
|
||||||
|
// 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
|
||||||
|
// 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
|
||||||
|
getImageRect() {
|
||||||
|
const src = this.getSrc(this.urls[0])
|
||||||
|
uni.getImageInfo({
|
||||||
|
src,
|
||||||
|
success: (res) => {
|
||||||
|
// 判断图片横向还是竖向展示方式
|
||||||
|
const isHorizotal = res.width >= res.height
|
||||||
|
this.singleWidth = isHorizotal
|
||||||
|
? this.singleSize
|
||||||
|
: (res.width / res.height) * this.singleSize
|
||||||
|
this.singleHeight = !isHorizotal
|
||||||
|
? this.singleSize
|
||||||
|
: (res.height / res.width) * this.singleWidth
|
||||||
|
},
|
||||||
|
fail: () => {
|
||||||
|
this.getComponentWidth()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 获取组件的宽度
|
||||||
|
async getComponentWidth() {
|
||||||
|
// 延时一定时间,以获取dom尺寸
|
||||||
|
await sleep(30)
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
this.$uGetRect('.u-album__row').then((size) => {
|
||||||
|
this.singleWidth = size.width * this.singlePercent
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
// 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
|
||||||
|
const ref = this.$refs['u-album__row'][0]
|
||||||
|
ref &&
|
||||||
|
dom.getComponentRect(ref, (res) => {
|
||||||
|
this.singleWidth = res.size.width * this.singlePercent
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../libs/css/components.scss';
|
||||||
|
|
||||||
|
.u-album {
|
||||||
|
@include flex(column);
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
@include flex(row);
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
@include flex(row);
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
22
uni_modules/uview-plus/components/u-alert/alert.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* @Author : LQ
|
||||||
|
* @Description :
|
||||||
|
* @version : 1.0
|
||||||
|
* @Date : 2021-08-20 16:44:21
|
||||||
|
* @LastAuthor : LQ
|
||||||
|
* @lastTime : 2021-08-20 16:48:53
|
||||||
|
* @FilePath : /u-view2.0/uview-ui/libs/config/props/alert.js
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// alert警告组件
|
||||||
|
alert: {
|
||||||
|
title: '',
|
||||||
|
type: 'warning',
|
||||||
|
description: '',
|
||||||
|
closable: false,
|
||||||
|
showIcon: false,
|
||||||
|
effect: 'light',
|
||||||
|
center: false,
|
||||||
|
fontSize: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
46
uni_modules/uview-plus/components/u-alert/props.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
export const props = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 显示文字
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.alert.title
|
||||||
|
},
|
||||||
|
// 主题,success/warning/info/error
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.alert.type
|
||||||
|
},
|
||||||
|
// 辅助性文字
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.alert.description
|
||||||
|
},
|
||||||
|
// 是否可关闭
|
||||||
|
closable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.alert.closable
|
||||||
|
},
|
||||||
|
// 是否显示图标
|
||||||
|
showIcon: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.alert.showIcon
|
||||||
|
},
|
||||||
|
// 浅或深色调,light-浅色,dark-深色
|
||||||
|
effect: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.alert.effect
|
||||||
|
},
|
||||||
|
// 文字是否居中
|
||||||
|
center: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.alert.center
|
||||||
|
},
|
||||||
|
// 字体大小
|
||||||
|
fontSize: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.alert.fontSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
251
uni_modules/uview-plus/components/u-alert/u-alert.vue
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<u-transition
|
||||||
|
mode="fade"
|
||||||
|
:show="show"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="u-alert"
|
||||||
|
:class="[`u-alert--${type}--${effect}`]"
|
||||||
|
@tap.stop="clickHandler"
|
||||||
|
:style="[addStyle(customStyle)]"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="u-alert__icon"
|
||||||
|
v-if="showIcon"
|
||||||
|
>
|
||||||
|
<u-icon
|
||||||
|
:name="iconName"
|
||||||
|
size="18"
|
||||||
|
:color="iconColor"
|
||||||
|
></u-icon>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="u-alert__content"
|
||||||
|
:style="[{
|
||||||
|
paddingRight: closable ? '20px' : 0
|
||||||
|
}]"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
class="u-alert__content__title"
|
||||||
|
v-if="title"
|
||||||
|
:style="[{
|
||||||
|
fontSize: addUnit(fontSize),
|
||||||
|
textAlign: center ? 'center' : 'left'
|
||||||
|
}]"
|
||||||
|
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||||
|
>{{ title }}</text>
|
||||||
|
<text
|
||||||
|
class="u-alert__content__desc"
|
||||||
|
v-if="description"
|
||||||
|
:style="[{
|
||||||
|
fontSize: addUnit(fontSize),
|
||||||
|
textAlign: center ? 'center' : 'left'
|
||||||
|
}]"
|
||||||
|
:class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
|
||||||
|
>{{ description }}</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="u-alert__close"
|
||||||
|
v-if="closable"
|
||||||
|
@tap.stop="closeHandler"
|
||||||
|
>
|
||||||
|
<u-icon
|
||||||
|
name="close"
|
||||||
|
:color="iconColor"
|
||||||
|
size="15"
|
||||||
|
></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { props } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addUnit, addStyle } from '../../libs/function/index';
|
||||||
|
/**
|
||||||
|
* Alert 警告提示
|
||||||
|
* @description 警告提示,展现需要关注的信息。
|
||||||
|
* @tutorial https://ijry.github.io/uview-plus/components/alertTips.html
|
||||||
|
*
|
||||||
|
* @property {String} title 显示的文字
|
||||||
|
* @property {String} type 使用预设的颜色 (默认 'warning' )
|
||||||
|
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
|
||||||
|
* @property {Boolean} closable 关闭按钮(默认为叉号icon图标) (默认 false )
|
||||||
|
* @property {Boolean} showIcon 是否显示左边的辅助图标 ( 默认 false )
|
||||||
|
* @property {String} effect 多图时,图片缩放裁剪的模式 (默认 'light' )
|
||||||
|
* @property {Boolean} center 文字是否居中 (默认 false )
|
||||||
|
* @property {String | Number} fontSize 字体大小 (默认 14 )
|
||||||
|
* @property {Object} customStyle 定义需要用到的外部样式
|
||||||
|
* @event {Function} click 点击组件时触发
|
||||||
|
* @event {Function} close 点击关闭按钮时触发
|
||||||
|
* @example <u-alert :title="title" type = "warning" :closable="closable" :description = "description"></u-alert>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'u-alert',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
iconColor() {
|
||||||
|
return this.effect === 'light' ? this.type : '#fff'
|
||||||
|
},
|
||||||
|
// 不同主题对应不同的图标
|
||||||
|
iconName() {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'success':
|
||||||
|
return 'checkmark-circle-fill';
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
return 'close-circle-fill';
|
||||||
|
break;
|
||||||
|
case 'warning':
|
||||||
|
return 'error-circle-fill';
|
||||||
|
break;
|
||||||
|
case 'info':
|
||||||
|
return 'info-circle-fill';
|
||||||
|
break;
|
||||||
|
case 'primary':
|
||||||
|
return 'more-circle-fill';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 'error-circle-fill';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["click","close"],
|
||||||
|
methods: {
|
||||||
|
addUnit,
|
||||||
|
addStyle,
|
||||||
|
// 点击内容
|
||||||
|
clickHandler() {
|
||||||
|
this.$emit('click')
|
||||||
|
},
|
||||||
|
// 点击关闭按钮
|
||||||
|
closeHandler() {
|
||||||
|
this.show = false
|
||||||
|
this.$emit('close')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../../libs/css/components.scss";
|
||||||
|
|
||||||
|
.u-alert {
|
||||||
|
position: relative;
|
||||||
|
background-color: $u-primary;
|
||||||
|
padding: 8px 10px;
|
||||||
|
@include flex(row);
|
||||||
|
align-items: center;
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
|
||||||
|
&--primary--dark {
|
||||||
|
background-color: $u-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--primary--light {
|
||||||
|
background-color: #ecf5ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error--dark {
|
||||||
|
background-color: $u-error;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error--light {
|
||||||
|
background-color: #FEF0F0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--success--dark {
|
||||||
|
background-color: $u-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--success--light {
|
||||||
|
background-color: #f5fff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warning--dark {
|
||||||
|
background-color: $u-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warning--light {
|
||||||
|
background-color: #FDF6EC;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--info--dark {
|
||||||
|
background-color: $u-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--info--light {
|
||||||
|
background-color: #f4f4f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
@include flex(column);
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
color: $u-main-color;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__desc {
|
||||||
|
color: $u-main-color;
|
||||||
|
font-size: 14px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title--dark,
|
||||||
|
&__desc--dark {
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text--primary--light,
|
||||||
|
&__text--primary--light {
|
||||||
|
color: $u-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text--success--light,
|
||||||
|
&__text--success--light {
|
||||||
|
color: $u-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text--warning--light,
|
||||||
|
&__text--warning--light {
|
||||||
|
color: $u-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text--error--light,
|
||||||
|
&__text--error--light {
|
||||||
|
color: $u-error;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text--info--light,
|
||||||
|
&__text--info--light {
|
||||||
|
color: $u-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close {
|
||||||
|
position: absolute;
|
||||||
|
top: 11px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* @Author : LQ
|
||||||
|
* @Description :
|
||||||
|
* @version : 1.0
|
||||||
|
* @Date : 2021-08-20 16:44:21
|
||||||
|
* @LastAuthor : LQ
|
||||||
|
* @lastTime : 2021-08-20 16:49:55
|
||||||
|
* @FilePath : /u-view2.0/uview-ui/libs/config/props/avatarGroup.js
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// avatarGroup 组件
|
||||||
|
avatarGroup: {
|
||||||
|
urls: [],
|
||||||
|
maxCount: 5,
|
||||||
|
shape: 'circle',
|
||||||
|
mode: 'scaleToFill',
|
||||||
|
showMore: true,
|
||||||
|
size: 40,
|
||||||
|
keyName: '',
|
||||||
|
gap: 0.5,
|
||||||
|
extraValue: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
54
uni_modules/uview-plus/components/u-avatar-group/props.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
export const props = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 头像图片组
|
||||||
|
urls: {
|
||||||
|
type: Array,
|
||||||
|
default: () => defProps.avatarGroup.urls
|
||||||
|
},
|
||||||
|
// 最多展示的头像数量
|
||||||
|
maxCount: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.avatarGroup.maxCount
|
||||||
|
},
|
||||||
|
// 头像形状
|
||||||
|
shape: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatarGroup.shape
|
||||||
|
},
|
||||||
|
// 图片裁剪模式
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatarGroup.mode
|
||||||
|
},
|
||||||
|
// 超出maxCount时是否显示查看更多的提示
|
||||||
|
showMore: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.avatarGroup.showMore
|
||||||
|
},
|
||||||
|
// 头像大小
|
||||||
|
size: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.avatarGroup.size
|
||||||
|
},
|
||||||
|
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||||
|
keyName: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatarGroup.keyName
|
||||||
|
},
|
||||||
|
// 头像之间的遮挡比例
|
||||||
|
gap: {
|
||||||
|
type: [String, Number],
|
||||||
|
validator(value) {
|
||||||
|
return value >= 0 && value <= 1
|
||||||
|
},
|
||||||
|
default: () => defProps.avatarGroup.gap
|
||||||
|
},
|
||||||
|
// 需额外显示的值
|
||||||
|
extraValue: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: () => defProps.avatarGroup.extraValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<view class="u-avatar-group">
|
||||||
|
<view
|
||||||
|
class="u-avatar-group__item"
|
||||||
|
v-for="(item, index) in showUrl"
|
||||||
|
:key="index"
|
||||||
|
:style="{
|
||||||
|
marginLeft: index === 0 ? 0 : addUnit(-size * gap)
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<u-avatar
|
||||||
|
:size="size"
|
||||||
|
:shape="shape"
|
||||||
|
:mode="mode"
|
||||||
|
:src="testObject(item) ? keyName && item[keyName] || item.url : item"
|
||||||
|
></u-avatar>
|
||||||
|
<view
|
||||||
|
class="u-avatar-group__item__show-more"
|
||||||
|
v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
|
||||||
|
@tap="clickHandler"
|
||||||
|
>
|
||||||
|
<up-text
|
||||||
|
color="#ffffff"
|
||||||
|
:size="size * 0.4"
|
||||||
|
:text="`+${extraValue || urls.length - showUrl.length}`"
|
||||||
|
align="center"
|
||||||
|
customStyle="justify-content: center"
|
||||||
|
></up-text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { props } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addUnit } from '../../libs/function/index';
|
||||||
|
import test from '../../libs/function/test';
|
||||||
|
/**
|
||||||
|
* AvatarGroup 头像组
|
||||||
|
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||||
|
* @tutorial https://ijry.github.io/uview-plus/components/avatar.html
|
||||||
|
*
|
||||||
|
* @property {Array} urls 头像图片组 (默认 [] )
|
||||||
|
* @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
|
||||||
|
* @property {String} shape 头像形状( 'circle' (默认) | 'square' )
|
||||||
|
* @property {String} mode 图片裁剪模式(默认 'scaleToFill' )
|
||||||
|
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
|
||||||
|
* @property {String | Number} size 头像大小 (默认 40 )
|
||||||
|
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||||
|
* @property {String | Number} gap 头像之间的遮挡比例(0.4代表遮挡40%) (默认 0.5 )
|
||||||
|
* @property {String | Number} extraValue 需额外显示的值
|
||||||
|
* @event {Function} showMore 头像组更多点击
|
||||||
|
* @example <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'u-avatar-group',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showUrl() {
|
||||||
|
return this.urls.slice(0, this.maxCount)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["showMore"],
|
||||||
|
methods: {
|
||||||
|
addUnit,
|
||||||
|
testObject: test.object,
|
||||||
|
clickHandler() {
|
||||||
|
this.$emit('showMore')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../../libs/css/components.scss";
|
||||||
|
|
||||||
|
.u-avatar-group {
|
||||||
|
@include flex;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
margin-left: -10px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&--no-indent {
|
||||||
|
// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__show-more {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
@include flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
28
uni_modules/uview-plus/components/u-avatar/avatar.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* @Author : LQ
|
||||||
|
* @Description :
|
||||||
|
* @version : 1.0
|
||||||
|
* @Date : 2021-08-20 16:44:21
|
||||||
|
* @LastAuthor : LQ
|
||||||
|
* @lastTime : 2021-08-20 16:49:22
|
||||||
|
* @FilePath : /u-view2.0/uview-ui/libs/config/props/avatar.js
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// avatar 组件
|
||||||
|
avatar: {
|
||||||
|
src: '',
|
||||||
|
shape: 'circle',
|
||||||
|
size: 40,
|
||||||
|
mode: 'scaleToFill',
|
||||||
|
text: '',
|
||||||
|
bgColor: '#c0c4cc',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: 18,
|
||||||
|
icon: '',
|
||||||
|
mpAvatar: false,
|
||||||
|
randomBgColor: false,
|
||||||
|
defaultUrl: '',
|
||||||
|
colorIndex: '',
|
||||||
|
name: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
81
uni_modules/uview-plus/components/u-avatar/props.js
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
import test from '../../libs/function/test';
|
||||||
|
export const props = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 头像图片路径(不能为相对路径)
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.src
|
||||||
|
},
|
||||||
|
// 头像形状,circle-圆形,square-方形
|
||||||
|
shape: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.shape
|
||||||
|
},
|
||||||
|
// 头像尺寸
|
||||||
|
size: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.avatar.size
|
||||||
|
},
|
||||||
|
// 裁剪模式
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.mode
|
||||||
|
},
|
||||||
|
// 显示的文字
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.text
|
||||||
|
},
|
||||||
|
// 背景色
|
||||||
|
bgColor: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.bgColor
|
||||||
|
},
|
||||||
|
// 文字颜色
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.color
|
||||||
|
},
|
||||||
|
// 文字大小
|
||||||
|
fontSize: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.avatar.fontSize
|
||||||
|
},
|
||||||
|
// 显示的图标
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.icon
|
||||||
|
},
|
||||||
|
// 显示小程序头像,只对百度,微信,QQ小程序有效
|
||||||
|
mpAvatar: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.avatar.mpAvatar
|
||||||
|
},
|
||||||
|
// 是否使用随机背景色
|
||||||
|
randomBgColor: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.avatar.randomBgColor
|
||||||
|
},
|
||||||
|
// 加载失败的默认头像(组件有内置默认图片)
|
||||||
|
defaultUrl: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.defaultUrl
|
||||||
|
},
|
||||||
|
// 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
||||||
|
colorIndex: {
|
||||||
|
type: [String, Number],
|
||||||
|
// 校验参数规则,索引在0-19之间
|
||||||
|
validator(n) {
|
||||||
|
return test.range(n, [0, 19]) || n === ''
|
||||||
|
},
|
||||||
|
default: () => defProps.avatar.colorIndex
|
||||||
|
},
|
||||||
|
// 组件标识符
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.avatar.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
180
uni_modules/uview-plus/components/u-avatar/u-avatar.vue
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="u-avatar"
|
||||||
|
:class="[`u-avatar--${shape}`]"
|
||||||
|
:style="[{
|
||||||
|
backgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : random(0, 19)] : bgColor) : 'transparent',
|
||||||
|
width: addUnit(size),
|
||||||
|
height: addUnit(size),
|
||||||
|
}, addStyle(customStyle)]"
|
||||||
|
@tap="clickHandler"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU -->
|
||||||
|
<open-data
|
||||||
|
v-if="mpAvatar && allowMp"
|
||||||
|
type="userAvatarUrl"
|
||||||
|
:style="[{
|
||||||
|
width: addUnit(size),
|
||||||
|
height: addUnit(size)
|
||||||
|
}]"
|
||||||
|
/>
|
||||||
|
<!-- #endif -->
|
||||||
|
<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU -->
|
||||||
|
<template v-if="mpAvatar && allowMp"></template>
|
||||||
|
<!-- #endif -->
|
||||||
|
<u-icon
|
||||||
|
v-else-if="icon"
|
||||||
|
:name="icon"
|
||||||
|
:size="fontSize"
|
||||||
|
:color="color"
|
||||||
|
></u-icon>
|
||||||
|
<up-text
|
||||||
|
v-else-if="text"
|
||||||
|
:text="text"
|
||||||
|
:size="fontSize"
|
||||||
|
:color="color"
|
||||||
|
align="center"
|
||||||
|
customStyle="justify-content: center"
|
||||||
|
></up-text>
|
||||||
|
<image
|
||||||
|
class="u-avatar__image"
|
||||||
|
v-else
|
||||||
|
:class="[`u-avatar__image--${shape}`]"
|
||||||
|
:src="avatarUrl || defaultUrl"
|
||||||
|
:mode="mode"
|
||||||
|
@error="errorHandler"
|
||||||
|
:style="[{
|
||||||
|
width: addUnit(size),
|
||||||
|
height: addUnit(size)
|
||||||
|
}]"
|
||||||
|
></image>
|
||||||
|
</slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { props } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addStyle, addUnit, random } from '../../libs/function/index';
|
||||||
|
const base64Avatar =
|
||||||
|
"data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
|
||||||
|
/**
|
||||||
|
* Avatar 头像
|
||||||
|
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
|
||||||
|
* @tutorial https://ijry.github.io/uview-plus/components/avatar.html
|
||||||
|
*
|
||||||
|
* @property {String} src 头像路径,如加载失败,将会显示默认头像(不能为相对路径)
|
||||||
|
* @property {String} shape 头像形状 ( circle (默认) | square)
|
||||||
|
* @property {String | Number} size 头像尺寸,可以为指定字符串(large, default, mini),或者数值 (默认 40 )
|
||||||
|
* @property {String} mode 头像图片的裁剪类型,与uni的image组件的mode参数一致,如效果达不到需求,可尝试传widthFix值 (默认 'scaleToFill' )
|
||||||
|
* @property {String} text 用文字替代图片,级别优先于src
|
||||||
|
* @property {String} bgColor 背景颜色,一般显示文字时用 (默认 '#c0c4cc' )
|
||||||
|
* @property {String} color 文字颜色 (默认 '#ffffff' )
|
||||||
|
* @property {String | Number} fontSize 文字大小 (默认 18 )
|
||||||
|
* @property {String} icon 显示的图标
|
||||||
|
* @property {Boolean} mpAvatar 显示小程序头像,只对百度,微信,QQ小程序有效 (默认 false )
|
||||||
|
* @property {Boolean} randomBgColor 是否使用随机背景色 (默认 false )
|
||||||
|
* @property {String} defaultUrl 加载失败的默认头像(组件有内置默认图片)
|
||||||
|
* @property {String | Number} colorIndex 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
|
||||||
|
* @property {String} name 组件标识符 (默认 'level' )
|
||||||
|
* @property {Object} customStyle 定义需要用到的外部样式
|
||||||
|
*
|
||||||
|
* @event {Function} click 点击组件时触发 index: 用户传递的标识符
|
||||||
|
* @example <u-avatar :src="src" mode="square"></u-avatar>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'u-avatar',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 如果配置randomBgColor参数为true,在图标或者文字的模式下,会随机从中取出一个颜色值当做背景色
|
||||||
|
colors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',
|
||||||
|
'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',
|
||||||
|
'#73d1f1',
|
||||||
|
'#80a7dc'
|
||||||
|
],
|
||||||
|
avatarUrl: this.src,
|
||||||
|
allowMp: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 监听头像src的变化,赋值给内部的avatarUrl变量,因为图片加载失败时,需要修改图片的src为默认值
|
||||||
|
// 而组件内部不能直接修改props的值,所以需要一个中间变量
|
||||||
|
src: {
|
||||||
|
immediate: true,
|
||||||
|
handler(newVal) {
|
||||||
|
this.avatarUrl = newVal
|
||||||
|
// 如果没有传src,则主动触发error事件,用于显示默认的头像,否则src为''空字符等的时候,会无内容展示
|
||||||
|
if(!newVal) {
|
||||||
|
this.errorHandler()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
imageStyle() {
|
||||||
|
const style = {}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.init()
|
||||||
|
},
|
||||||
|
emits: ["click"],
|
||||||
|
methods: {
|
||||||
|
addStyle,
|
||||||
|
addUnit,
|
||||||
|
random,
|
||||||
|
init() {
|
||||||
|
// 目前只有这几个小程序平台具有open-data标签
|
||||||
|
// 其他平台可以通过uni.getUserInfo类似接口获取信息,但是需要弹窗授权(首次),不合符组件逻辑
|
||||||
|
// 故目前自动获取小程序头像只支持这几个平台
|
||||||
|
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU
|
||||||
|
this.allowMp = true
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
|
||||||
|
isImg() {
|
||||||
|
return this.src.indexOf('/') !== -1
|
||||||
|
},
|
||||||
|
// 图片加载时失败时触发
|
||||||
|
errorHandler() {
|
||||||
|
this.avatarUrl = this.defaultUrl || base64Avatar
|
||||||
|
},
|
||||||
|
clickHandler(e) {
|
||||||
|
this.$emit('click', this.name, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../../libs/css/components.scss";
|
||||||
|
|
||||||
|
.u-avatar {
|
||||||
|
@include flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&--circle {
|
||||||
|
border-radius: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--square {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
&--circle {
|
||||||
|
border-radius: 100px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--square {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
27
uni_modules/uview-plus/components/u-back-top/backtop.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* @Author : LQ
|
||||||
|
* @Description :
|
||||||
|
* @version : 1.0
|
||||||
|
* @Date : 2021-08-20 16:44:21
|
||||||
|
* @LastAuthor : LQ
|
||||||
|
* @lastTime : 2021-08-20 16:50:18
|
||||||
|
* @FilePath : /u-view2.0/uview-ui/libs/config/props/backtop.js
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// backtop组件
|
||||||
|
backtop: {
|
||||||
|
mode: 'circle',
|
||||||
|
icon: 'arrow-upward',
|
||||||
|
text: '',
|
||||||
|
duration: 100,
|
||||||
|
scrollTop: 0,
|
||||||
|
top: 400,
|
||||||
|
bottom: 100,
|
||||||
|
right: 20,
|
||||||
|
zIndex: 9,
|
||||||
|
iconStyle: {
|
||||||
|
color: '#909399',
|
||||||
|
fontSize: '19px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
uni_modules/uview-plus/components/u-back-top/props.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
export const props = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 返回顶部的形状,circle-圆形,square-方形
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.backtop.mode
|
||||||
|
},
|
||||||
|
// 自定义图标
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.backtop.icon
|
||||||
|
},
|
||||||
|
// 提示文字
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.backtop.text
|
||||||
|
},
|
||||||
|
// 返回顶部滚动时间
|
||||||
|
duration: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.backtop.duration
|
||||||
|
},
|
||||||
|
// 滚动距离
|
||||||
|
scrollTop: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.backtop.scrollTop
|
||||||
|
},
|
||||||
|
// 距离顶部多少距离显示,单位px
|
||||||
|
top: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.backtop.top
|
||||||
|
},
|
||||||
|
// 返回顶部按钮到底部的距离,单位px
|
||||||
|
bottom: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.backtop.bottom
|
||||||
|
},
|
||||||
|
// 返回顶部按钮到右边的距离,单位px
|
||||||
|
right: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.backtop.right
|
||||||
|
},
|
||||||
|
// 层级
|
||||||
|
zIndex: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: () => defProps.backtop.zIndex
|
||||||
|
},
|
||||||
|
// 图标的样式,对象形式
|
||||||
|
iconStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => defProps.backtop.iconStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
133
uni_modules/uview-plus/components/u-back-top/u-back-top.vue
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<u-transition
|
||||||
|
mode="fade"
|
||||||
|
:customStyle="backTopStyle"
|
||||||
|
:show="show"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="u-back-top"
|
||||||
|
:style="[contentStyle]"
|
||||||
|
v-if="!$slots.default && !$slots.$default"
|
||||||
|
@click="backToTop"
|
||||||
|
>
|
||||||
|
<u-icon
|
||||||
|
:name="icon"
|
||||||
|
:custom-style="iconStyle"
|
||||||
|
></u-icon>
|
||||||
|
<text
|
||||||
|
v-if="text"
|
||||||
|
class="u-back-top__text"
|
||||||
|
>{{text}}</text>
|
||||||
|
</view>
|
||||||
|
<slot v-else />
|
||||||
|
</u-transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { props } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addUnit, addStyle, getPx, deepMerge, error } from '../../libs/function/index';
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
const dom = weex.requireModule('dom')
|
||||||
|
// #endif
|
||||||
|
/**
|
||||||
|
* backTop 返回顶部
|
||||||
|
* @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
|
||||||
|
* @tutorial https://uview-plus.jiangruyi.com/components/backTop.html
|
||||||
|
*
|
||||||
|
* @property {String} mode 返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
|
||||||
|
* @property {String} icon 自定义图标 (默认 'arrow-upward' ) 见官方文档示例
|
||||||
|
* @property {String} text 提示文字
|
||||||
|
* @property {String | Number} duration 返回顶部滚动时间 (默认 100)
|
||||||
|
* @property {String | Number} scrollTop 滚动距离 (默认 0 )
|
||||||
|
* @property {String | Number} top 距离顶部多少距离显示,单位px (默认 400 )
|
||||||
|
* @property {String | Number} bottom 返回顶部按钮到底部的距离,单位px (默认 100 )
|
||||||
|
* @property {String | Number} right 返回顶部按钮到右边的距离,单位px (默认 20 )
|
||||||
|
* @property {String | Number} zIndex 层级 (默认 9 )
|
||||||
|
* @property {Object<Object>} iconStyle 图标的样式,对象形式 (默认 {color: '#909399',fontSize: '19px'})
|
||||||
|
* @property {Object} customStyle 定义需要用到的外部样式
|
||||||
|
*
|
||||||
|
* @example <u-back-top :scrollTop="scrollTop"></u-back-top>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'u-back-top',
|
||||||
|
mixins: [mpMixin, mixin, props],
|
||||||
|
computed: {
|
||||||
|
backTopStyle() {
|
||||||
|
// 动画组件样式
|
||||||
|
const style = {
|
||||||
|
bottom: addUnit(this.bottom),
|
||||||
|
right: addUnit(this.right),
|
||||||
|
width: '40px',
|
||||||
|
height: '40px',
|
||||||
|
position: 'fixed',
|
||||||
|
zIndex: 10,
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
show() {
|
||||||
|
return getPx(this.scrollTop) > getPx(this.top)
|
||||||
|
},
|
||||||
|
contentStyle() {
|
||||||
|
const style = {}
|
||||||
|
let radius = 0
|
||||||
|
// 是否圆形
|
||||||
|
if(this.mode === 'circle') {
|
||||||
|
radius = '100px'
|
||||||
|
} else {
|
||||||
|
radius = '4px'
|
||||||
|
}
|
||||||
|
// 为了兼容安卓nvue,只能这么分开写
|
||||||
|
style.borderTopLeftRadius = radius
|
||||||
|
style.borderTopRightRadius = radius
|
||||||
|
style.borderBottomLeftRadius = radius
|
||||||
|
style.borderBottomRightRadius = radius
|
||||||
|
return deepMerge(style, addStyle(this.customStyle))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ["click"],
|
||||||
|
methods: {
|
||||||
|
backToTop() {
|
||||||
|
// #ifdef APP-NVUE
|
||||||
|
if (!this.$parent.$refs['u-back-top']) {
|
||||||
|
error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
|
||||||
|
}
|
||||||
|
dom.scrollToElement(this.$parent.$refs['u-back-top'], {
|
||||||
|
offset: 0
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef APP-NVUE
|
||||||
|
uni.pageScrollTo({
|
||||||
|
scrollTop: 0,
|
||||||
|
duration: this.duration
|
||||||
|
});
|
||||||
|
// #endif
|
||||||
|
this.$emit('click')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '../../libs/css/components.scss';
|
||||||
|
$u-back-top-flex:1 !default;
|
||||||
|
$u-back-top-height:100% !default;
|
||||||
|
$u-back-top-background-color:#E1E1E1 !default;
|
||||||
|
$u-back-top-tips-font-size:12px !default;
|
||||||
|
.u-back-top {
|
||||||
|
@include flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
flex:$u-back-top-flex;
|
||||||
|
height: $u-back-top-height;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: $u-back-top-background-color;
|
||||||
|
|
||||||
|
&__tips {
|
||||||
|
font-size:$u-back-top-tips-font-size;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
27
uni_modules/uview-plus/components/u-badge/badge.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* @Author : LQ
|
||||||
|
* @Description :
|
||||||
|
* @version : 1.0
|
||||||
|
* @Date : 2021-08-20 16:44:21
|
||||||
|
* @LastAuthor : LQ
|
||||||
|
* @lastTime : 2021-08-23 19:51:50
|
||||||
|
* @FilePath : /u-view2.0/uview-ui/libs/config/props/badge.js
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
// 徽标数组件
|
||||||
|
badge: {
|
||||||
|
isDot: false,
|
||||||
|
value: '',
|
||||||
|
show: true,
|
||||||
|
max: 999,
|
||||||
|
type: 'error',
|
||||||
|
showZero: false,
|
||||||
|
bgColor: null,
|
||||||
|
color: null,
|
||||||
|
shape: 'circle',
|
||||||
|
numberType: 'overflow',
|
||||||
|
offset: [],
|
||||||
|
inverted: false,
|
||||||
|
absolute: false
|
||||||
|
}
|
||||||
|
}
|
||||||
79
uni_modules/uview-plus/components/u-badge/props.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
export const props = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 是否显示圆点
|
||||||
|
isDot: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.badge.isDot
|
||||||
|
},
|
||||||
|
// 显示的内容
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: () => defProps.badge.value
|
||||||
|
},
|
||||||
|
// 显示的内容
|
||||||
|
modelValue: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: () => defProps.badge.modelValue
|
||||||
|
},
|
||||||
|
// 是否显示
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.badge.show
|
||||||
|
},
|
||||||
|
// 最大值,超过最大值会显示 '{max}+'
|
||||||
|
max: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: () => defProps.badge.max
|
||||||
|
},
|
||||||
|
// 主题类型,error|warning|success|primary
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.badge.type
|
||||||
|
},
|
||||||
|
// 当数值为 0 时,是否展示 Badge
|
||||||
|
showZero: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.badge.showZero
|
||||||
|
},
|
||||||
|
// 背景颜色,优先级比type高,如设置,type参数会失效
|
||||||
|
bgColor: {
|
||||||
|
type: [String, null],
|
||||||
|
default: () => defProps.badge.bgColor
|
||||||
|
},
|
||||||
|
// 字体颜色
|
||||||
|
color: {
|
||||||
|
type: [String, null],
|
||||||
|
default: () => defProps.badge.color
|
||||||
|
},
|
||||||
|
// 徽标形状,circle-四角均为圆角,horn-左下角为直角
|
||||||
|
shape: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.badge.shape
|
||||||
|
},
|
||||||
|
// 设置数字的显示方式,overflow|ellipsis|limit
|
||||||
|
// overflow会根据max字段判断,超出显示`${max}+`
|
||||||
|
// ellipsis会根据max判断,超出显示`${max}...`
|
||||||
|
// limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
|
||||||
|
numberType: {
|
||||||
|
type: String,
|
||||||
|
default: () => defProps.badge.numberType
|
||||||
|
},
|
||||||
|
// 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
||||||
|
offset: {
|
||||||
|
type: Array,
|
||||||
|
default: () => defProps.badge.offset
|
||||||
|
},
|
||||||
|
// 是否反转背景和字体颜色
|
||||||
|
inverted: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.badge.inverted
|
||||||
|
},
|
||||||
|
// 是否绝对定位
|
||||||
|
absolute: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => defProps.badge.absolute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
177
uni_modules/uview-plus/components/u-badge/u-badge.vue
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
<template>
|
||||||
|
<text
|
||||||
|
v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
|
||||||
|
:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
|
||||||
|
:style="[addStyle(customStyle), badgeStyle]"
|
||||||
|
class="u-badge"
|
||||||
|
>{{ isDot ? '' :showValue }}</text>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { props } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addStyle, addUnit } from '../../libs/function/index';
|
||||||
|
/**
|
||||||
|
* badge 徽标数
|
||||||
|
* @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
|
||||||
|
* @tutorial https://uview-plus.jiangruyi.com/components/badge.html
|
||||||
|
*
|
||||||
|
* @property {Boolean} isDot 是否显示圆点 (默认 false )
|
||||||
|
* @property {String | Number} value 显示的内容
|
||||||
|
* @property {Boolean} show 是否显示 (默认 true )
|
||||||
|
* @property {String | Number} max 最大值,超过最大值会显示 '{max}+' (默认999)
|
||||||
|
* @property {String} type 主题类型,error|warning|success|primary (默认 'error' )
|
||||||
|
* @property {Boolean} showZero 当数值为 0 时,是否展示 Badge (默认 false )
|
||||||
|
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
|
||||||
|
* @property {String} color 字体颜色 (默认 '#ffffff' )
|
||||||
|
* @property {String} shape 徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
|
||||||
|
* @property {String} numberType 设置数字的显示方式,overflow|ellipsis|limit (默认 'overflow' )
|
||||||
|
* @property {Array}} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
|
||||||
|
* @property {Boolean} inverted 是否反转背景和字体颜色(默认 false )
|
||||||
|
* @property {Boolean} absolute 是否绝对定位(默认 false )
|
||||||
|
* @property {Object} customStyle 定义需要用到的外部样式
|
||||||
|
* @example <u-badge :type="type" :count="count"></u-badge>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'u-badge',
|
||||||
|
mixins: [mpMixin, props, mixin],
|
||||||
|
computed: {
|
||||||
|
// 是否将badge中心与父组件右上角重合
|
||||||
|
boxStyle() {
|
||||||
|
let style = {};
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
// 整个组件的样式
|
||||||
|
badgeStyle() {
|
||||||
|
const style = {}
|
||||||
|
if(this.color) {
|
||||||
|
style.color = this.color
|
||||||
|
}
|
||||||
|
if (this.bgColor && !this.inverted) {
|
||||||
|
style.backgroundColor = this.bgColor
|
||||||
|
}
|
||||||
|
if (this.absolute) {
|
||||||
|
style.position = 'absolute'
|
||||||
|
// 如果有设置offset参数
|
||||||
|
if(this.offset.length) {
|
||||||
|
// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
|
||||||
|
const top = this.offset[0]
|
||||||
|
const right = this.offset[1] || top
|
||||||
|
style.top = addUnit(top)
|
||||||
|
style.right = addUnit(right)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
},
|
||||||
|
showValue() {
|
||||||
|
switch (this.numberType) {
|
||||||
|
case "overflow":
|
||||||
|
return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
|
||||||
|
break;
|
||||||
|
case "ellipsis":
|
||||||
|
return Number(this.value) > Number(this.max) ? "..." : this.value
|
||||||
|
break;
|
||||||
|
case "limit":
|
||||||
|
return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
|
||||||
|
Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
|
||||||
|
1e3 * 100) / 100 + "k" : this.value
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return Number(this.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../../libs/css/components.scss";
|
||||||
|
|
||||||
|
$u-badge-primary: $u-primary !default;
|
||||||
|
$u-badge-error: $u-error !default;
|
||||||
|
$u-badge-success: $u-success !default;
|
||||||
|
$u-badge-info: $u-info !default;
|
||||||
|
$u-badge-warning: $u-warning !default;
|
||||||
|
$u-badge-dot-radius: 100px !default;
|
||||||
|
$u-badge-dot-size: 8px !default;
|
||||||
|
$u-badge-dot-right: 4px !default;
|
||||||
|
$u-badge-dot-top: 0 !default;
|
||||||
|
$u-badge-text-font-size: 11px !default;
|
||||||
|
$u-badge-text-right: 10px !default;
|
||||||
|
$u-badge-text-padding: 2px 5px !default;
|
||||||
|
$u-badge-text-align: center !default;
|
||||||
|
$u-badge-text-color: #FFFFFF !default;
|
||||||
|
|
||||||
|
.u-badge {
|
||||||
|
border-top-right-radius: $u-badge-dot-radius;
|
||||||
|
border-top-left-radius: $u-badge-dot-radius;
|
||||||
|
border-bottom-left-radius: $u-badge-dot-radius;
|
||||||
|
border-bottom-right-radius: $u-badge-dot-radius;
|
||||||
|
@include flex;
|
||||||
|
line-height: $u-badge-text-font-size;
|
||||||
|
text-align: $u-badge-text-align;
|
||||||
|
font-size: $u-badge-text-font-size;
|
||||||
|
color: $u-badge-text-color;
|
||||||
|
|
||||||
|
&--dot {
|
||||||
|
height: $u-badge-dot-size;
|
||||||
|
width: $u-badge-dot-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--inverted {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--not-dot {
|
||||||
|
padding: $u-badge-text-padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--horn {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
background-color: $u-badge-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--primary--inverted {
|
||||||
|
color: $u-badge-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error {
|
||||||
|
background-color: $u-badge-error;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error--inverted {
|
||||||
|
color: $u-badge-error;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--success {
|
||||||
|
background-color: $u-badge-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--success--inverted {
|
||||||
|
color: $u-badge-success;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--info {
|
||||||
|
background-color: $u-badge-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--info--inverted {
|
||||||
|
color: $u-badge-info;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warning {
|
||||||
|
background-color: $u-badge-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warning--inverted {
|
||||||
|
color: $u-badge-warning;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
27
uni_modules/uview-plus/components/u-box/props.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { defineMixin } from '../../libs/vue'
|
||||||
|
import defProps from '../../libs/config/props.js'
|
||||||
|
|
||||||
|
export const propsBox = defineMixin({
|
||||||
|
props: {
|
||||||
|
// 背景色
|
||||||
|
bgColors: {
|
||||||
|
type: [Array],
|
||||||
|
default: ['#EEFCFF', '#FCF8FF', '#FDF8F2']
|
||||||
|
},
|
||||||
|
// 高度
|
||||||
|
height: {
|
||||||
|
type: [String],
|
||||||
|
default: "160px"
|
||||||
|
},
|
||||||
|
// 圆角
|
||||||
|
borderRadius: {
|
||||||
|
type: [String],
|
||||||
|
default: "6px"
|
||||||
|
},
|
||||||
|
// 间隔
|
||||||
|
gap: {
|
||||||
|
type: [String],
|
||||||
|
default: "15px"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
92
uni_modules/uview-plus/components/u-box/u-box.vue
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<template>
|
||||||
|
<view class="u-box" :style="[{height: height}, addStyle(customStyle)]">
|
||||||
|
<view class="u-box__left" :style="{borderRadius: borderRadius, backgroundColor: bgColors[0]}">
|
||||||
|
<slot name="left">左</slot>
|
||||||
|
</view>
|
||||||
|
<view class="u-box__gap" :style="{width: gap, height: height}"></view>
|
||||||
|
<view class="u-box__right">
|
||||||
|
<view class="u-box__right-top" :style="{borderRadius: borderRadius, backgroundColor: bgColors[1]}">
|
||||||
|
<slot name="rightTop">右上</slot>
|
||||||
|
</view>
|
||||||
|
<view class="u-box__right-gap" :style="{height: gap}"></view>
|
||||||
|
<view class="u-box__right-bottom" :style="{borderRadius: borderRadius, backgroundColor: bgColors[2]}">
|
||||||
|
<slot name="rightBottom">右下</slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { propsBox } from './props';
|
||||||
|
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||||
|
import { mixin } from '../../libs/mixin/mixin';
|
||||||
|
import { addStyle } from '../../libs/function/index';
|
||||||
|
import test from '../../libs/function/test';
|
||||||
|
/**
|
||||||
|
* box 盒子
|
||||||
|
* @description box盒子一般为左边一个盒子,右侧两个等高的半盒组成,常用于App首页座位重点突出。
|
||||||
|
* @tutorial https://uview-plus.jiangruyi.com/components/box.html
|
||||||
|
* @property {Array} bgColors 背景色
|
||||||
|
* @property {String} height 高度
|
||||||
|
* @property {String} borderRadius 圆角
|
||||||
|
* @property {Object} customStyle 定义需要用到的外部样式
|
||||||
|
*
|
||||||
|
* @event {Function} click 点击cell列表时触发
|
||||||
|
* @example <up-box colors=['blue', 'red', 'yellow'] height="200px"></up-box>
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'up-box',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mixins: [mpMixin, mixin, propsBox],
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
emits: [],
|
||||||
|
methods: {
|
||||||
|
addStyle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "../../libs/css/components.scss";
|
||||||
|
|
||||||
|
.u-box {
|
||||||
|
/* #ifndef APP-NVUE */
|
||||||
|
/* #endif */
|
||||||
|
@include flex();
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
@include flex();
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
&__gap {
|
||||||
|
@include flex();
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
&__right {
|
||||||
|
@include flex();
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__right-top {
|
||||||
|
@include flex();
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__right-bottom {
|
||||||
|
@include flex();
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||