迁移版本
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>
|
||||