创建项目
Some checks failed
deploy / deploy (push) Has been cancelled

This commit is contained in:
sjeam 2025-07-22 09:14:43 +08:00
parent 69ce8df718
commit 013373d61f
612 changed files with 80464 additions and 2 deletions

22
.editorconfig Normal file
View File

@ -0,0 +1,22 @@
# 告诉EditorConfig插件这是根文件不用继续往上查找
root = true
# 匹配全部文件
[*]
# 设置字符集
charset = utf-8
# 缩进风格可选space、tab
indent_style = space
# 缩进的空格数
indent_size = 2
# 结尾换行符可选lf、cr、crlf
end_of_line = lf
# 在文件结尾插入新行
insert_final_newline = true
# 删除一行中的前后空格
trim_trailing_whitespace = true
# 匹配md结尾的文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

38
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: deploy
on:
push:
branches: [main] # master 分支有 push 时触发
paths-ignore: # 下列文件的变更不触发部署,可以自行添加
- README.md
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: Node环境
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: 安装构建
run: |
pnpm install
pnpm build:h5
# 部署到 GitHub pages
- name: 部署
uses: peaceiris/actions-gh-pages@v4 # 使用部署到 GitHub pages 的 action
with:
github_token: ${{ secrets.GITHUB_TOKEN }} # secret 名
commit_message: 自动部署 # 部署时的 git 提交信息,自由填写
publish_dir: ./dist/build/h5 # 部署打包后的 dist 目录

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
#user
.hbuilderx
unpackage
/stats.html
# pnpm-lock.yaml
yarn.lock
package-lock.json

12
.npmrc Normal file
View File

@ -0,0 +1,12 @@
# 设置npm包的下载源为国内镜像加速包下载
registry=https://registry.npmmirror.com/
# 将依赖包提升到node_modules根目录减少嵌套层级
shamefully-hoist=true
# 关闭严格的对等依赖检查,避免因对等依赖版本不匹配而安装失败
strict-peer-dependencies=false
# 自动安装对等依赖,无需手动安装
auto-install-peers=true
# 对等依赖去重,减少重复安装
dedupe-peer-dependents=true
# 使用提升模式链接依赖与npm兼容性更好
node-linker=hoisted

21
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
/*
1.
2.
3.
*/
"recommendations": [
// "antfu.vite", // /
// "antfu.iconify", // hover
"antfu.unocss", // CSS
"vue.volar", // Vue 3
"dbaeumer.vscode-eslint", // ESLint
// "editorConfig.editorConfig", // EditorConfig
// "uni-helper.uni-highlight-vscode", //
// "uni-helper.uni-app-snippets-vscode" // uni-app
// "uni-helper.uni-ui-snippets-vscode" // uni-ui
// "uni-helper.uni-helper-vscode" // Uni Helper
]
}

36
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,36 @@
{
// prettier使 eslint
"prettier.enable": false,
//
"editor.formatOnSave": false,
//
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml"
],
// json
"files.associations": {
"manifest.json": "jsonc",
"pages.json": "jsonc"
},
// Unknown at rule @apply
"css.customData": [
".vscode/unocss.json"
],
"i18n-ally.localesPaths": [
"src/locales",
"src/locales/langs"
]
}

11
.vscode/unocss.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"version": 1.1,
"atDirectives": [
{
"name": "@apply"
},
{
"name": "@screen"
}
]
}

48
.vscode/vue3.code-snippets vendored Normal file
View File

@ -0,0 +1,48 @@
{
// Place your 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"Print Vue3 SFC": {
"scope": "vue",
"prefix": "v3",
"body": [
"<template>",
" <view class=\"\">$1</view>",
"</template>\n",
"<script lang=\"ts\" setup>",
"//$2",
"</script>\n",
"<style lang=\"scss\" scoped>",
"//$3",
"</style>\n",
],
},
"Print style": {
"scope": "vue",
"prefix": "st",
"body": ["<style lang=\"scss\" scoped>", "//", "</style>\n"],
},
"Print script": {
"scope": "vue",
"prefix": "sc",
"body": ["<script lang=\"ts\" setup>", "//$3", "</script>\n"],
},
"Print template": {
"scope": "vue",
"prefix": "te",
"body": ["<template>", " <view class=\"\">$1</view>", "</template>\n"],
},
}

67
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,67 @@
# 贡献指南
感谢你对本项目的兴趣!我们欢迎任何形式的贡献,无论是报告 bug、提交功能请求、改进文档还是直接提交代码。为了使贡献过程更加顺利请按照以下指南进行操作。
## 如何开始
1. **Fork 项目**:首先将项目 `Fork` 到你的 `GitHub` 账户。
2. **克隆到本地**:将你 `Fork` 后的仓库克隆到本地。
```bash
git clone https://github.com/你的用户名/uniapp-vue3-template.git
```
3. **创建分支**:在进行任何修改之前,先创建一个新的分支。
```bash
git checkout -b feature/your-feature-name
```
4. **进行修改**:根据你的需求进行代码修改或文档编辑。
5. **提交更改**:完成修改后,使用以下命令提交更改:
```bash
pnpm cz
```
6. **推送分支**:将你的分支推送到 GitHub。
```bash
git push origin feature/your-feature-name
```
7. **创建 Pull Request**:在 `GitHub` 上,提交你的 `Pull Request`,描述清楚你所做的更改。
## 提交代码规范
- **代码格式**:只要你安装了依赖项,就不用担心代码风格。`Git` 钩子会在提交时为你格式化和修复它们。
- **简洁明了的提交信息**:提交信息应简明扼要,说明改动的目的和内容。建议使用以下格式:
```
[类型]:简要说明
```
例如:
```
feat: 添加新功能
fix: 修复 bug
docs: 更新文档
style: 格式调整
refactor: 代码重构
test: 添加/修改测试
```
## 贡献前请注意
- **讨论功能请求**:在提交功能请求之前,确保这个功能是项目当前的方向。
- **修复 bug**:在修复 `bug` 之前,先检查现有的 `issue` 列表,看看是否有人已经报告了相同的问题。
- **文档改进**:欢迎任何形式的文档改进。文档有时会被遗漏,但它对于项目的可用性至关重要。
## 测试
在提交 `Pull Request` 之前,请确保测试成功通过。如果有修改涉及到核心功能,请确保你的改动不会破坏现有的功能。
## 行为规范
希望在这个项目中创建一个友好和包容的社区。请遵循以下行为准则:
- 尊重他人,避免人身攻击、歧视或骚扰。
- 提供有建设性的反馈,不论是代码还是讨论。
- 对不同的观点保持开放的态度,愿意倾听他人的建议。
## 相关文档
- [项目主页](https://github.com/oyjt/uniapp-vue3-template)
- [问题追踪](https://github.com/oyjt/uniapp-vue3-template/issues)
感谢你的贡献!期待与你的合作!

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 江阳小道
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.

328
README.md
View File

@ -1,3 +1,327 @@
# caipu_nui
# uniapp 团队协作开发实践模板(Vue3)
菜谱小程序
[![GitHub Repo stars](https://img.shields.io/github/stars/oyjt/uniapp-vue3-template?style=flat&logo=github)](https://github.com/oyjt/uniapp-vue3-template)
[![GitHub forks](https://img.shields.io/github/forks/oyjt/uniapp-vue3-template?style=flat&logo=github)](https://github.com/oyjt/uniapp-vue3-template)
[![node version](https://img.shields.io/badge/node-%3E%3D18-green)](https://github.com/oyjt/uniapp-vue3-template)
[![pnpm version](https://img.shields.io/badge/pnpm-%3E%3D8-green)](https://github.com/oyjt/uniapp-vue3-template)
[![GitHub package.json version (subfolder of monorepo)](https://img.shields.io/github/package-json/v/oyjt/uniapp-vue3-template)](https://github.com/oyjt/uniapp-vue3-template)
[![GitHub License](https://img.shields.io/github/license/oyjt/uniapp-vue3-template)](https://github.com/oyjt/uniapp-vue3-template)
使用uniapp+vite+vue3+typescript+uview-plus+unocss 搭建的适合团队协作的快速开发模版
[uview-plus官方文档](https://uiadmin.net/uview-plus/)
本项目集众多项目的优点,打造最适合团队协作开发的项目模板。
在线预览地址:[https://oyjt.github.io/uniapp-vue3-template/](https://oyjt.github.io/uniapp-vue3-template/)
### 特性
- [x] 集成`uview-plus3.0 ui`
- [x] 支持多环境打包构建
- [x] 使用`pinia`状态管理
- [x] 封装网络请求,并支持`Typescript`
- [x] 支持路径别名
- [x] 支持自动加载组件和`API`
- [x] 自动校验`git`提交代码格式
- [x] 集成`ESLint``StyleLint``EditorConfig`代码格式规范
- [x] `Typescript`支持
- [x] 集成`UnoCSS`
- [x] 集成`iconify`图标库
- [x] 集成`z-paging`下拉刷新功能
- [x] 添加页面跳转拦截,登录权限校验
- [x] 支持`token`无感刷新
- [x] 项目分包
- [x] 集成小程序隐私协议授权组件
- [x] 项目构建自动删除本地图片并替换本地图片路径为线上图片
- [x] 集成包体积视图分析插件
- [x] 支持国际化
- [x] 集成`alova`网络请求(具体使用请切换到 [feature/alova](https://github.com/oyjt/uniapp-vue3-template/tree/feature/alova) 分支)
- [x] 集成`axios`网络请求(具体使用请切换到 [feature/axios](https://github.com/oyjt/uniapp-vue3-template/tree/feature/axios) 分支)
- [x] 支持新的`wot-design-uni`库(具体使用请切换到[feature/wot-design-uni](https://github.com/oyjt/uniapp-vue3-template/tree/feature/wot-design-uni)分支),[wot-design-uni官方文档](https://wot-design-uni.cn/)
### uniapp插件推荐
- [uniapp 插件精选https://github.com/oyjt/awesome-uniapp](https://github.com/oyjt/awesome-uniapp)
### 目录结构
项目中采用目前最新的技术方案来实现,目录结构清晰。
```
uniapp-vue3-project
├ build vite配置统一管理
│ ├ config
│ └ plugins
├ env 环境变量
├ scripts 一些脚本
│ ├ post-upgrade.js 依赖库清理
│ └ verify-commit.js git提交检验
├ src
│ ├ api 接口管理
│ ├ components 公共组件
│ ├ hooks 常用hooks封装
│ ├ locale 国际化语言管理
│ ├ pages 页面管理
│ ├ plugins 插件管理
│ ├ router 路由管理
│ ├ static 静态资源
│ ├ store 状态管理
│ ├ utils 一些工具
│ ├ App.vue
│ ├ main.ts
│ ├ manifest.json 项目配置
│ ├ pages.json 页面配置
│ └ uni.scss 全局scss变量
├ types 全局typescript类型文件
│ ├ auto-imports.d.ts
│ ├ components.d.ts
│ ├ global.d.ts
│ └ module.d.ts
├ LICENSE
├ README.md
├ cz.config.js cz-git配置
├ eslint.config.js eslint配置
├ index.html
├ package.json
├ pnpm-lock.yaml
├ stylelint.config.js stylelint配置
├ tsconfig.json
├ uno.config.ts unocss配置
└ vite.config.ts vite配置
```
#### vite插件管理
```
build
├ config vite配置
│ ├ index.ts 入口文件
│ └ proxy.ts 跨域代理配置
└ plugins vite插件
├ autoImport.ts 自动导入api
├ cleanImage.ts 自动清理图片文件
├ component.ts 自动导入组件
├ index.ts 入口文件
├ replaceUrl.ts 自动替换图片地址为CDN地址
├ unocss.ts unocss配置
└ visualizer.ts 包体积视图分析
```
#### 接口管理
```
api
├ common 通用api
│ ├ index.ts
│ └ types.ts
├ user 用户相关api
│ ├ index.ts
│ └ types.ts
└ index.ts 入口文件
```
#### hooks管理
```
hooks
├ use-clipboard 剪切板
│ └ index.ts
├ use-loading loading
│ └ index.ts
├ use-modal 模态框
│ └ index.ts
├ use-permission 校验权限
│ └ index.ts
├ use-share 分享
│ └ index.ts
└ index.ts 入口文件
```
### 页面管理
```
pages
├ common 公共页面分包common
│ ├ login
│ │ └ index.vue
│ └ webview
│ └ index.vue
└ tab 主页面(主包)
├ home
│ └ index.vue
├ list
│ └ index.vue
└ user
└ index.vue
```
#### 状态管理
```
store
├ modules
│ ├ app app状态
│ │ ├ index.ts
│ │ └ types.ts
│ └ user 用户状态
│ ├ index.ts
│ └ types.ts
└ index.ts 入口文件
```
### 工具方法
```
utils
├ auth token相关方法
│ └ index.ts
├ common 通用方法
│ └ index.ts
├ modals 弹窗相关方法
│ └ index.ts
├ request 网络请求相关方法
│ ├ index.ts
│ ├ interceptors.ts
│ ├ status.ts
│ └ types.ts
└ index.ts 入口文件
```
### 使用方法
```bash
# 安装依赖
pnpm install
# 启动H5
pnpm dev:h5
# 启动微信小程序
pnpm dev:mp-weixin
```
### 发布
```bash
# 构建开发环境
pnpm build:h5
pnpm build:mp-weixin
# 构建测试环境
pnpm build:h5-test
pnpm build:mp-weixin-test
# 构建生产环境
pnpm build:h5-prod
pnpm build:mp-weixin-prod
```
### 代码提交
```bash
pnpm cz
```
### 更新uniapp版本
更新uniapp相关依赖到最新正式版
```bash
npx @dcloudio/uvm@latest
```
或者执行下面的命令
```bash
pnpm uvm
```
在升级完后,会自动添加很多无用依赖,执行下面的代码减小保体积
```
pnpm uvm-rm
```
### `v3` 代码块
`vue` 文件中,输入 `v3``tab` 即可快速生成页面模板,可以大大加快页面生成。
> 原理:基于 VSCode 代码块生成。
### 登录鉴权
1. 页面如果需要登录才能访问,只需在 `pages.json` 文件中需要鉴权的页面下设置 `needLogin` 属性设置为 `true` 即可,比如
```
{
"pages": [
{
"path": "pages/test/test",
"needLogin": true,
"style": {
"navigationBarTitleText": "",
},
}
]
}
```
2. 如果有`tab`页面需要登录才能访问,上面的设置在小程序中点击`tabbar`时无效因为在小程序中点击tabbar不会触发`uni.switchTab`方法,下面是官方给出的回复及解决方案。
> 拦截uni.switchTab本身没有问题。但是在微信小程序端点击tabbar的底层逻辑并不是触发uni.switchTab。所以误认为拦截无效此类场景的解决方案是在tabbar页面的页面生命周期onShow中处理。
可参考`pages/tab/user/index.vue`中的代码,核心代码如下:
```
<script setup lang="ts">
// 引入鉴权hooks
import { usePermission } from "@/hooks";
onShow(async () => {
console.log("tabbar page onShow");
const hasPermission = await usePermission();
console.log(hasPermission ? "已登录" : "未登录,拦截跳转");
});
</script>
```
### 注意事项
1. 微信小程序开发者工具中内置的打包分析不准确,本项目使用了`rollup-plugin-visualizer`来分析小程序包体积,默认不开启,有需要的移除相关注释即可
2. 自动构建处理本地图片资源,使用了`vite-plugin-clean-build``vite-plugin-replace-image-url`这两个插件,默认不开启相关功能,如果需要使用再`build/vite/plugins/index.ts`文件中移除相关注释即可
3. 使用`vite-plugin-replace-image-url`插件,想要图片自动替换生效,需要在项目中使用绝对路径引入图片资源,如下示例所示。
示例一style中的图片使用
```
<template>
<view :style="`background-image: url('${bgImg}')`">
...
</view>
</template>
<script setup lang="ts">
import bgImg from '@/static/images/bg_img.png';
</script>
```
示例二js中的图片使用
```
<script setup lang="ts">
import walletIcon from '@/static/images/icon_wallet.png';
const menuList = [
{
name: 'wallet',
title: '钱包',
icon: walletIcon,
},
...
];
</script>
```
示例二css中的图片使用
```
<style lang="scss">
.icon {
background-image: url('@/static/images/icon.png')
}
</style>
```
4. 部分用户构建微信小程序如下错误,原因是微信开发者工具缺失了对应的依赖。
```
This @babel/plugin-proposal-private-property-in-object version is not meant to
be imported.
```
此时升级微信开发者工具,或者安装`@babel/plugin-proposal-private-property-in-object`依赖即可解决问题。
### 捐赠
如果你觉得这个项目对你有帮助,你可以请作者喝饮料🍹
<p align='center'>
<img alt="微信收款码" src="./src/static/images/pay.png" height="330" style="display:inline-block; height:330px;">
</p>

1
build/config/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './proxy';

17
build/config/proxy.ts Normal file
View File

@ -0,0 +1,17 @@
import type { ProxyOptions } from 'vite';
type ProxyTargetList = Record<string, ProxyOptions>;
export const createViteProxy = (env: Record<string, string>) => {
const { VITE_APP_PROXY, VITE_API_PREFIX, VITE_API_BASE_URL } = env;
// 不使用代理直接返回
if (!JSON.parse(VITE_APP_PROXY)) return undefined;
const proxy: ProxyTargetList = {
[VITE_API_PREFIX]: {
target: VITE_API_BASE_URL,
changeOrigin: true,
rewrite: path => path.replace(new RegExp(`^${VITE_API_PREFIX}`), ''),
},
};
return proxy;
};

View File

@ -0,0 +1,13 @@
/**
* @name AutoImportDeps
* @description
*/
import AutoImport from 'unplugin-auto-import/vite';
export const AutoImportDeps = () => {
return AutoImport({
imports: ['vue', 'uni-app', 'pinia'],
dts: 'types/auto-imports.d.ts',
vueTemplate: true,
});
};

View File

@ -0,0 +1,12 @@
/**
* @name cleanImagePlugin
* @description
*/
import CleanBuild from 'vite-plugin-clean-build';
export const CleanImagePlugin = () => {
return CleanBuild({
outputDir: 'dist/build/mp-weixin',
patterns: ['static/images/**', '!static/images/logo.png', '!static/images/tabbar/**'],
});
};

View File

@ -0,0 +1,11 @@
/**
* @name AutoRegistryComponents
* @description
*/
import Components from 'unplugin-vue-components/vite';
export const AutoRegistryComponents = () => {
return Components({
dts: 'types/components.d.ts',
});
};

43
build/plugins/index.ts Normal file
View File

@ -0,0 +1,43 @@
/**
* @name createVitePlugins
* @description plugins数组统一调用
*/
import type { PluginOption } from 'vite';
import uniPlugin from '@dcloudio/vite-plugin-uni';
import ViteRestart from 'vite-plugin-restart';
import { AutoImportDeps } from './autoImport';
// import { ConfigImageminPlugin } from './imagemin';
// import { ReplaceUrlPlugin } from './replaceUrl';
import { AutoRegistryComponents } from './component';
import { ConfigUnoCSSPlugin } from './unocss';
export default function createVitePlugins(isBuild: boolean) {
const vitePlugins: (PluginOption | PluginOption[])[] = [
// UnoCSS配置
ConfigUnoCSSPlugin(),
// 自动按需引入依赖
AutoImportDeps(),
// 自动按需引入组件(注意:需注册至 uni 之前,否则不会生效)
AutoRegistryComponents(),
// uni支持(兼容性写法当type为module时必须要这样写)
(uniPlugin as any).default(),
ViteRestart({
// 通过这个插件在修改vite.config.js文件则不需要重新运行也生效配置
restart: ['vite.config.ts'],
}),
];
if (isBuild) {
const buildPlugins: (PluginOption | PluginOption[])[] = [
// 图片资源自动转换为网络资源
// ReplaceUrlPlugin(),
// 自动清除本地图片
// CleanImagePlugin(),
// 打包视图分析
// VisualizerPlugin(),
];
vitePlugins.push(...buildPlugins);
}
return vitePlugins;
}

View File

@ -0,0 +1,13 @@
/**
* @name ReplaceImageUrl
* @description
*/
import replaceImageUrl from 'vite-plugin-replace-image-url';
export const ReplaceUrlPlugin = () => {
return replaceImageUrl({
publicPath: 'https://photo.example.com/miniprogram',
sourceDir: 'src/static',
verbose: true,
});
};

9
build/plugins/unocss.ts Normal file
View File

@ -0,0 +1,9 @@
/**
* @name ConfigUnoCSSPlugin
* @description UnoCSS相关配置
*/
import UnoCSS from 'unocss/vite';
export const ConfigUnoCSSPlugin = () => {
return UnoCSS();
};

View File

@ -0,0 +1,13 @@
/**
* @name VisualizerPlugin
* @description
*/
import { visualizer } from 'rollup-plugin-visualizer';
export const VisualizerPlugin = () => {
return visualizer({
emitFile: false,
filename: 'stats.html', // 分析图生成的文件名
open: true, // 如果存在本地服务端口,将在打包后自动展示
});
};

58
cz.config.js Normal file
View File

@ -0,0 +1,58 @@
/** @type {import('cz-git').CommitizenGitOptions} */
export default {
alias: { fd: 'docs: fix typos' },
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 \'|\' 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 \'|\' 换行 :\n',
footerPrefixesSelect: '选择关联issue前缀可选:',
customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?',
},
types: [
{ value: 'feat', name: 'feat: 新增功能 | A new feature', emoji: ':sparkles:' },
{ value: 'fix', name: 'fix: 修复缺陷 | A bug fix', emoji: ':bug:' },
{ value: 'docs', name: 'docs: 文档更新 | Documentation only changes', emoji: ':memo:' },
{ value: 'style', name: 'style: 代码格式 | Changes that do not affect the meaning of the code', emoji: ':lipstick:' },
{ value: 'refactor', name: 'refactor: 代码重构 | A code change that neither fixes a bug nor adds a feature', emoji: ':recycle:' },
{ value: 'perf', name: 'perf: 性能提升 | A code change that improves performance', emoji: ':zap:' },
{ value: 'test', name: 'test: 测试相关 | Adding missing tests or correcting existing tests', emoji: ':white_check_mark:' },
{ value: 'build', name: 'build: 构建相关 | Changes that affect the build system or external dependencies', emoji: ':package:' },
{ value: 'ci', name: 'ci: 持续集成 | Changes to our CI configuration files and scripts', emoji: ':ferris_wheel:' },
{ value: 'chore', name: 'chore: 其他修改 | Other changes that don\'t modify src or test files', emoji: ':hammer:' },
{ value: 'revert', name: 'revert: 回退代码 | Reverts a previous commit', emoji: ':rewind:' },
],
useEmoji: false,
emojiAlign: 'center',
useAI: false,
aiNumber: 1,
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixes: [{ value: 'closed', name: 'closed: 标记 ISSUES 已完成' }],
customIssuePrefixAlign: 'top',
emptyIssuePrefixAlias: 'skip',
customIssuePrefixAlias: 'custom',
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true,
minSubjectLength: 0,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: '',
};

23
env/.env vendored Normal file
View File

@ -0,0 +1,23 @@
# 页面标题
VITE_APP_TITLE=uniapp-vue3模板项目
# 开发环境配置
VITE_APP_ENV=development
# 接口地址
VITE_API_BASE_URL=https://test.shop.lihaink.cn
# 端口号
VITE_APP_PORT=9527
# h5是否需要配置代理
VITE_APP_PROXY=true
# API代理前缀
VITE_API_PREFIX= https://test.shop.lihaink.cn
# 删除console
VITE_DROP_CONSOLE=false
# Tencent Map Key
VITE_APP_MAP_KEY= "SMJBZ-WCHK4-ZPZUA-DSIXI-XDDVQ-XWFX7"

8
env/.env.development vendored Normal file
View File

@ -0,0 +1,8 @@
# 开发环境配置
VITE_APP_ENV=development
# 接口地址
VITE_API_BASE_URL=http://localhost:8080
# 删除console
VITE_DROP_CONSOLE=false

8
env/.env.production vendored Normal file
View File

@ -0,0 +1,8 @@
# 生产环境配置
VITE_APP_ENV=production
# 接口地址
VITE_API_BASE_URL=http://localhost:8080/prod
# 删除console
VITE_DROP_CONSOLE=true

8
env/.env.test vendored Normal file
View File

@ -0,0 +1,8 @@
# 预发布环境配置
VITE_APP_ENV=staging
# 接口地址
VITE_API_BASE_URL=http://localhost:8080/staging
# 删除console
VITE_DROP_CONSOLE=true

53
eslint.config.js Normal file
View File

@ -0,0 +1,53 @@
import antfu from '@antfu/eslint-config';
export default antfu(
{
unocss: true,
ignores: [
'dist/**',
'.vscode/**',
'.idea/**',
'node_modules/**',
'src/uni_modules/**',
'src/manifest.json',
'src/pages.json',
'README.md',
],
},
{
rules: {
// vue顶级标签的顺序
'vue/block-order': ['error', {
order: ['template', 'script', 'style'],
}],
// 需要尾随逗号
'comma-dangle': ['error', 'only-multiline'],
// 允许console
'no-console': 'off',
// 需要分号
'style/semi': ['error', 'always'],
// 块内的空行
'padded-blocks': ['error', 'never'],
// 顶级函数应使用 function 关键字声明
'antfu/top-level-function': 'off',
// 全局的 process 不能用
'node/prefer-global/process': 'off',
// 禁止未使用的捕获组
'regexp/no-unused-capturing-group': 'off',
// 允许接口和类型别名中的成员之间使用三个分隔符
'style/member-delimiter-style': ['error', {
multiline: {
delimiter: 'semi',
requireLast: true,
},
singleline: {
delimiter: 'semi',
requireLast: false,
},
multilineDetection: 'brackets',
}],
// if 语句后需要换行
'antfu/if-newline': 'off',
},
},
);

20
index.html Normal file
View File

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

132
package.json Normal file
View File

@ -0,0 +1,132 @@
{
"name": "uniapp-vue3-project",
"type": "module",
"version": "1.3.1",
"description": "uniapp 团队协作开发实践模板(Vue3)",
"author": {
"name": "江阳小道",
"email": "oyjt001@gmail.com",
"github": "https://github.com/oyjt"
},
"license": "MIT",
"homepage": "https://github.com/oyjt/uniapp-vue3-template",
"repository": {
"type": "git",
"url": "https://github.com/oyjt/uniapp-vue3-template.git"
},
"keywords": [
"Vue3",
"uniapp",
"uniapp-vue3-template",
"Vite5",
"TypeScript",
"uview-plus",
"uniapp template",
"UnoCSS"
],
"engines": {
"node": ">=18",
"pnpm": ">=8"
},
"scripts": {
"preinstall": "npx only-allow pnpm",
"uvm": "npx @dcloudio/uvm@latest",
"uvm-rm": "node ./scripts/post-upgrade.js",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:h5-test": "uni --mode test",
"dev:h5-pro": "uni --mode production",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-weixin-test": "uni -p mp-weixin --mode test",
"dev:mp-weixin-prod": "uni -p mp-weixin --mode production",
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:h5-test": "uni build --mode test",
"build:h5-prod": "uni build --mode production",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-weixin-test": "uni build -p mp-weixin --mode test",
"build:mp-weixin-prod": "uni build -p mp-weixin --mode production",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"type-check": "vue-tsc --noEmit",
"eslint": "eslint \"src/**/*.{js,jsx,ts,tsx,vue}\"",
"eslint:fix": "eslint \"src/**/*.{js,jsx,ts,tsx,vue}\" --fix",
"stylelint": "stylelint \"src/**/*.{vue,scss,css,sass,less}\"",
"stylelint:fix": "stylelint \"src/**/*.{vue,scss,css,sass,less}\" --fix",
"cz": "git add . && npx czg",
"postinstall": "simple-git-hooks",
"clean": "npx rimraf node_modules",
"clean:cache": "npx rimraf node_modules/.cache"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-4060420250429001",
"@dcloudio/uni-app-plus": "3.0.0-4060420250429001",
"@dcloudio/uni-components": "3.0.0-4060420250429001",
"@dcloudio/uni-h5": "3.0.0-4060420250429001",
"@dcloudio/uni-mp-weixin": "3.0.0-4060420250429001",
"dayjs": "^1.11.13",
"pinia": "2.2.4",
"pinia-plugin-persistedstate": "4.1.3",
"uview-plus": "^3.4.28",
"vue": "3.4.21",
"vue-i18n": "9.1.9",
"z-paging": "^2.8.4"
},
"devDependencies": {
"@antfu/eslint-config": "4.13.0",
"@dcloudio/types": "^3.4.8",
"@dcloudio/uni-automator": "3.0.0-4060420250429001",
"@dcloudio/uni-cli-shared": "3.0.0-4060420250429001",
"@dcloudio/uni-stacktracey": "3.0.0-4060420250429001",
"@dcloudio/vite-plugin-uni": "3.0.0-4060420250429001",
"@esbuild/darwin-arm64": "0.25.1",
"@esbuild/darwin-x64": "0.25.1",
"@iconify-json/mdi": "^1.2.3",
"@rollup/rollup-darwin-arm64": "4.38.0",
"@rollup/rollup-darwin-x64": "4.38.0",
"@types/node": "^22.15.17",
"@uni-helper/uni-app-types": "1.0.0-alpha.6",
"@unocss/eslint-plugin": "^0.63.6",
"@unocss/preset-icons": "^0.63.6",
"czg": "^1.11.0",
"eslint": "^9.26.0",
"lint-staged": "^16.0.0",
"miniprogram-api-typings": "^4.0.7",
"picocolors": "^1.1.1",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "1.79.6",
"sass-loader": "^16.0.4",
"simple-git-hooks": "^2.13.0",
"stylelint": "^16.19.1",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-standard": "^38.0.0",
"stylelint-config-standard-vue": "^1.0.0",
"typescript": "^5.8.3",
"unocss": "0.63.6",
"unocss-preset-weapp": "^66.0.1",
"unplugin-auto-import": "0.19.0",
"unplugin-vue-components": "^28.5.0",
"vite": "5.2.8",
"vite-plugin-clean-build": "^1.4.1",
"vite-plugin-replace-image-url": "^1.4.1",
"vite-plugin-restart": "^0.4.2",
"vue-tsc": "^2.2.10"
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged",
"commit-msg": "node ./scripts/verify-commit.js"
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx}": "eslint --fix",
"*.{scss,css,style,html}": "stylelint --fix",
"*.vue": [
"eslint --fix",
"stylelint --fix"
]
}
}

13277
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

36
scripts/post-upgrade.js Normal file
View File

@ -0,0 +1,36 @@
// # 执行 `pnpm upgrade` 后会升级 `uniapp` 相关依赖
// # 在升级完后,会自动添加很多无用依赖,这需要删除以减小依赖包体积
// # 只需要执行下面的命令即可
import { exec } from 'node:child_process';
// 定义要执行的命令
const dependencies = [
'@dcloudio/uni-app-harmony',
// TODO: 如果需要某个平台的小程序,请手动删除或注释掉
'@dcloudio/uni-mp-alipay',
'@dcloudio/uni-mp-baidu',
'@dcloudio/uni-mp-jd',
'@dcloudio/uni-mp-kuaishou',
'@dcloudio/uni-mp-lark',
'@dcloudio/uni-mp-qq',
'@dcloudio/uni-mp-toutiao',
'@dcloudio/uni-mp-xhs',
'@dcloudio/uni-quickapp-webview',
'@dcloudio/uni-mp-harmony',
// vue 已经内置了 @vue/runtime-core这里移除掉
'@vue/runtime-core',
];
// 使用exec执行命令
exec(`pnpm remove ${dependencies.join(' ')}`, (error, stdout, stderr) => {
if (error) {
// 如果有错误,打印错误信息
console.error(`执行出错: ${error}`);
return;
}
// 打印正常输出
console.log(`stdout: ${stdout}`);
// 如果有错误输出,也打印出来
console.error(`stderr: ${stderr}`);
});

28
scripts/verify-commit.js Normal file
View File

@ -0,0 +1,28 @@
/**
* 提交信息校验
* @link https://github.com/toplenboren/simple-git-hooks
* @see 参考https://github.com/vuejs/vue-next/blob/master/.github/commit-convention.md
*/
import { readFileSync } from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import pico from 'picocolors';
const msgPath = path.resolve('.git/COMMIT_EDITMSG');
const msg = readFileSync(msgPath, 'utf-8').trim();
const commitRE
= /^(?:revert: )?(?:feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip|mod|release|strengthen)(?:\(.+\))?: .{1,50}/;
if (!commitRE.test(msg)) {
console.log(pico.yellow(`\n提交的信息: ${msg}\n`));
console.error(
` ${pico.white(pico.bgRed(' 格式错误 '))} ${pico.red(
'无效的提交信息格式.',
)}\n\n${
pico.red(' 正确的提交消息格式. 例如:\n\n')
} ${pico.green('feat: add a new feature')}\n`
+ ` ${pico.green('fix: fixed an bug')}`,
);
process.exit(1);
}

22
src/App.vue Normal file
View File

@ -0,0 +1,22 @@
<script setup lang="ts">
import { mpUpdate } from '@/utils/index';
onLaunch(() => {
console.log('App Launch');
// #ifdef MP-WEIXIN
mpUpdate();
// #endif
});
onShow(() => {
console.log('App Show');
});
onHide(() => {
console.log('App Hide');
});
</script>
<style lang="scss">
/* 每个页面公共css */
@import 'uview-plus/index.scss';
@import '@/static/styles/common.scss';
</style>

24
src/api/common/index.ts Normal file
View File

@ -0,0 +1,24 @@
/**
*
*/
import type { SendCodeReq, SendCodeRes, UploadRes } from './types';
// import type { CommonRes } from '@/api/common/types';
import { get,post, upload } from '@/utils/request';
// 文件上传
export const uploadFile = (filePath: string) =>
upload<UploadRes>('/common/upload', { filePath, name: 'file' });
// 发送验证码
export const sendCode = (data: SendCodeReq) => post<SendCodeRes>('/sendCode', { data });
// repeatSubmit 防止重复提交 auth 传token
/** 获取列表 get实列 */
export const goodsMenu = (data:any) => get('/api/config', { data, custom: { toast: false } });
/** 登录 post 实列 */
export const goodsLogin = (data:any) => post('/api/auth/login', { data, custom: { toast: false } });

21
src/api/common/types.ts Normal file
View File

@ -0,0 +1,21 @@
export interface CommonReq {
[key: string]: any;
}
export interface CommonRes {
[key: string]: any;
}
export interface UploadRes {
file: string;
url: string;
}
export interface SendCodeReq {
phone: number;
code: number;
}
export interface SendCodeRes {
code: number;
}

4
src/api/index.ts Normal file
View File

@ -0,0 +1,4 @@
import * as CommonApi from './common';
import * as UserApi from './user';
export { CommonApi, UserApi };

22
src/api/user/index.ts Normal file
View File

@ -0,0 +1,22 @@
/**
*
*/
import type { CommonRes } from '@/api/common/types';
import type { LoginByCodeReq, LoginByCodeRes, LoginReq, LoginRes, ProfileReq, ProfileRes } from './types';
import { get, post } from '@/utils/request';
/** 获取用户信息 */
export const profile = (params?: ProfileReq) => get<ProfileRes>('/user/profile', { params });
/** 登录 */
export const login = (data: LoginReq) => post<LoginRes>('/user/login', { data, custom: { auth: false } });
/** 验证码登录 */
export const loginByCode = (data: LoginByCodeReq) => post<LoginByCodeRes>('/user/loginByCode', { data });
/** 退出登录 */
export const logout = () => post<CommonRes>('/user/logout');
export const goodsMenu = () => get<CommonRes>('/api/config');
// export const goodsMenu = (data:any) => get<CommonRes>('/api/config', { data, custom: { toast: false } });

30
src/api/user/types.ts Normal file
View File

@ -0,0 +1,30 @@
export interface ProfileReq {
user_id?: string;
}
export interface ProfileRes {
user_id?: string;
user_name?: string;
avatar?: string;
token?: string;
}
export interface LoginReq {
phone: string;
code: string;
}
export interface LoginRes {
token: string;
user_id: number;
user_name: string;
avatar: string;
}
export interface LoginByCodeReq {
code: string;
}
export interface LoginByCodeRes {
[key: string]: any;
}

0
src/components/.gitkeep Normal file
View File

View File

@ -0,0 +1,214 @@
<template>
<u-popup :show="modelValue" round="20" @close="closeAgreePrivacy">
<view class="p-30rpx">
<view class="text-lg text-black font-bold">
<span>{{ initTitle }}</span>
</view>
<view class="flex flex-col">
<span class="pt-30rpx text-black font-bold">{{ initSubTitle }}</span>
<span class="pt-30rpx text-sm text-black">1.为向您提供基本的服务我们会遵循正当合法必要的原则收集和使用必要的信息</span>
<span class="pt-30rpx text-sm text-black">2.基于您的授权我们可能会收集和使用您的相关信息您有权拒绝或取消授权</span>
<span class="pt-30rpx text-sm text-black">3.未经您的授权同意我们不会将您的信息共享给第三方或用于您未授权的其他用途</span>
<span class="pt-30rpx text-sm text-black">4.详细信息请您完整阅读<text class="text-decoration" @click="openPrivacyContract">{{
initPrivacyContractName
}}</text></span>
</view>
<view class="mt-30rpx flex items-center justify-around pt-10rpx">
<view style="min-width: 100px">
<button class="button button-default" @click="disagree">
拒绝
</button>
</view>
<view style="min-width: 100px">
<button
:id="agreePrivacyId"
class="button button-primary"
open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="agree"
>
同意
</button>
</view>
</view>
</view>
</u-popup>
</template>
<script setup lang="ts">
interface AgreePrivacyProps {
modelValue: boolean;
//
title: string;
//
subTitle: string;
//
disableCheckPrivacy: boolean;
// id idagree-btn
agreePrivacyId: string;
}
const props = withDefaults(defineProps<AgreePrivacyProps>(), {
modelValue: false,
title: '',
subTitle: '',
disableCheckPrivacy: true,
agreePrivacyId: 'agree-btn',
});
const emit = defineEmits(['update:modelValue', 'needPrivacyAuthorization', 'agree', 'disagree']);
//
const initTitle = ref<string>('隐私政策概要');
//
const initSubTitle = ref<string>('');
//
const initPrivacyContractName = ref<string>('隐私政策');
//
function openAgreePrivacy() {
emit('update:modelValue', true);
}
//
function closeAgreePrivacy() {
emit('update:modelValue', false);
}
//
function initData() {
initTitle.value = props.title || initTitle.value;
initSubTitle.value
= props.subTitle
|| `亲爱的用户,感谢您一直以来的支持!为了更好地保护您的权益,同时遵守相关监管要求,请认真阅读${initPrivacyContractName.value},特向您说明如下:`;
}
//
function checkPrivacySetting() {
wx.getPrivacySetting({
success: (res: any) => {
//
if (res.needAuthorization) {
initPrivacyContractName.value = res.privacyContractName;
initData();
//
if (!props.disableCheckPrivacy) {
//
openAgreePrivacy();
}
}
else {
//
// wx.getUserProfile()
}
},
fail: (e: any) => {
console.log(e);
},
});
}
//
function openPrivacyContract() {
wx.openPrivacyContract({
success: () => {}, //
fail: (e: any) => {
uni.$u.toast(`打开失败:${e}`);
}, //
});
}
//
function agree(e: any) {
const buttonId = e.target.id || 'agree-btn';
emit('agree', buttonId);
emit('update:modelValue', false);
}
//
function disagree() {
emit('disagree');
closeAgreePrivacy();
}
onMounted(() => {
//
checkPrivacySetting();
// //
// wx.onNeedPrivacyAuthorization((resolve, eventInfo) => {
// emit('update:modelValue', true);
// //
// emit('needPrivacyAuthorization', resolve, eventInfo);
// });
});
</script>
<style scoped lang="scss">
.button {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
//height: 80rpx;
padding: 10px 20px;
font-size: 14px;
font-weight: 500;
line-height: 1.5;
color: #fff;
text-align: center;
text-decoration: none;
border-radius: 18rpx;
//border-width: 1px;
//border-style: solid;
}
.button-lg {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
//height: 80rpx;
padding: 12px 22px;
font-size: 14px;
font-weight: 700;
line-height: 1.5;
color: #fff;
text-align: center;
text-decoration: none;
border-radius: 20rpx;
//border-width: 1px;
//border-style: solid;
}
.button-default {
color: #07c160;
background-color: rgb(0 0 0 / 5%);
}
.button-primary {
color: #fff;
background-color: #07c160;
}
button {
padding: 0;
margin: 0;
line-height: inherit;
background-color: transparent;
border-radius: 0;
outline: none;
}
button::after {
border: none;
}
.text-decoration {
color: #07c160;
text-decoration: underline;
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<view>
<picker
range-key="label"
:range="langOptions"
:value="langIndex"
@change="handleLangChange"
>
<slot>
<view class="i-mdi-language" :style="langStyle" />
</slot>
</picker>
</view>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const props = defineProps({
size: {
type: Number,
default: 40,
required: false,
},
});
const emit = defineEmits(['change']);
const { locale, t } = useI18n();
const langStyle = {
fontSize: `${props.size}rpx`,
};
const langOptions = computed(() => {
return [
{ label: t('locale.en'), value: 'en' },
{ label: t('locale.zh-hans'), value: 'zh-Hans' },
];
});
const langIndex = computed(() => {
return langOptions.value.findIndex((item) => {
return item.value === locale.value;
});
});
function handleLangChange(event: any) {
const lang = langOptions.value[event.detail.value].value;
locale.value = lang;
uni.setLocale(lang);
emit('change', lang);
}
</script>
<style lang="scss" scoped></style>

8
src/hooks/index.ts Normal file
View File

@ -0,0 +1,8 @@
import useClipboard from './use-clipboard';
import useLoading from './use-loading';
import useLocation from './use-location';
import useModal from './use-modal';
import usePermission from './use-permission';
import useShare from './use-share';
export { useClipboard, useLoading, useLocation, useModal, usePermission, useShare };

View File

@ -0,0 +1,33 @@
/**
*
* @example
* const {setClipboardData, getClipboardData} = useClipboard()
* // 设置剪切板
* setClipboardData({data: '1234567890'})
* // 获取剪切板
* const data = await getClipboardData()
*/
export default function useClipboard() {
const setClipboardData = ({ data, showToast = true }: UniApp.SetClipboardDataOptions) => {
return new Promise<string>((resolve, reject) => {
uni.setClipboardData({
data,
showToast,
success: ({ data }) => resolve(data),
fail: error => reject(error),
});
});
};
const getClipboardData = () => {
return new Promise<string>((resolve, reject) => {
uni.getClipboardData({
success: ({ data }) => resolve(data),
fail: error => reject(error),
});
});
};
return {
setClipboardData,
getClipboardData,
};
}

View File

@ -0,0 +1,24 @@
/**
* loading
* @example
* const {showLoading, hideLoading} = useLoading()
* // 显示loading
* showLoading()
* // 隐藏loading
* hideLoading()
*/
export default function useLoading() {
const showLoading = (content = '加载中') => {
uni.showLoading({
title: content,
mask: true,
});
};
const hideLoading = () => {
uni.hideLoading();
};
return {
showLoading,
hideLoading,
};
}

View File

@ -0,0 +1,329 @@
import { env } from 'process';
import type { AddressInfo, LocationInfo, LocationOptions } from './types';
const map_key = import.meta.env.VITE_APP_MAP_KEY;
/**
* hooks
* -
* -
* -
* -
*/
export default function useLocation() {
// 当前位置信息
const location = ref<LocationInfo | null>(null);
// 定位状态
const isLocating = ref(false);
// 是否正在监听位置
const isWatching = ref(false);
// 定位错误信息
const error = ref<any>(null);
// 历史位置
const historyLocations = ref<LocationInfo[]>([]);
// 监听位置的定时器ID
let watchId: number | null = null;
/**
*
* @param options
*/
const getLocation = (options: LocationOptions = {}) => {
isLocating.value = true;
error.value = null;
const defaultOptions: LocationOptions = {
type: 'gcj02',
altitude: false,
isHighAccuracy: false,
};
const finalOptions = { ...defaultOptions, ...options };
return new Promise<LocationInfo>((resolve, reject) => {
uni.getLocation({
type: finalOptions.type,
altitude: finalOptions.altitude,
isHighAccuracy: finalOptions.isHighAccuracy,
highAccuracyExpireTime: finalOptions.highAccuracyExpireTime,
success: (res) => {
// 更新当前位置
const locationData: LocationInfo = {
...res,
timestamp: Date.now(),
};
location.value = locationData;
// 添加到历史记录
historyLocations.value.push(locationData);
// 只保留最近的20条记录
if (historyLocations.value.length > 20) {
historyLocations.value.shift();
}
finalOptions.success && finalOptions.success(res);
resolve(locationData);
},
fail: (err) => {
error.value = err;
finalOptions.fail && finalOptions.fail(err);
reject(err);
},
complete: () => {
isLocating.value = false;
finalOptions.complete && finalOptions.complete();
},
});
});
};
/**
* 使
* @param latitude
* @param longitude
*/
const getAddress = (latitude: number, longitude: number) => {
return new Promise<AddressInfo>((resolve, reject) => {
// console.log('getAddress', `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=${map_key}`);
// #ifdef APP-PLUS
uni.request({
url: `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=${map_key}`,
success: (res: any) => {
if (res.data && res.data.status === 0) {
const addressComponent = res.data.result.address_component;
const formattedAddress = res.data.result.formatted_addresses.recommend;
const addressInfo: AddressInfo = {
nation: addressComponent.nation,
province: addressComponent.province,
city: addressComponent.city,
district: addressComponent.district,
street: addressComponent.street,
streetNum: addressComponent.street_number,
poiName: res.data.result.poi_count > 0 ? res.data.result.pois[0].title : '',
cityCode: res.data.result.ad_info.city_code,
};
if (location.value) {
location.value.address = addressInfo;
location.value.formatted = formattedAddress;
}
resolve(addressInfo);
}
else {
reject(new Error('获取地址信息失败'));
}
},
fail: (err) => {
reject(err);
},
});
// #endif
// #ifndef APP-PLUS
// 其他平台可以使用uni.getLocation的geocode参数获取仅App和微信小程序支持
// 或者使用其他地图服务的API
reject(new Error('当前平台不支持地址解析'));
// #endif
});
};
/**
*
*/
const stopWatchLocation = () => {
if (watchId !== null) {
clearInterval(watchId);
watchId = null;
}
isWatching.value = false;
};
/**
*
* @param options
* @param interval
*/
const watchLocation = (options: LocationOptions = {}, interval: number = 5000) => {
// 已经在监听,先停止
if (isWatching.value) {
stopWatchLocation();
}
isWatching.value = true;
// 首次定位
getLocation(options).catch((err) => {
console.error('监听位置首次定位失败', err);
});
// 定时获取位置
watchId = window.setInterval(() => {
if (isWatching.value) {
getLocation(options).catch((err) => {
console.error('监听位置更新失败', err);
});
}
}, interval);
return watchId;
};
/**
*
* @param lat1
* @param lon1
* @param lat2
* @param lon2
* @returns
*/
const calculateDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {
const R = 6371000; // 地球半径,单位米
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLon = ((lon2 - lon1) * Math.PI) / 180;
const a
= Math.sin(dLat / 2) * Math.sin(dLat / 2)
+ Math.cos((lat1 * Math.PI) / 180)
* Math.cos((lat2 * Math.PI) / 180)
* Math.sin(dLon / 2)
* Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
return distance;
};
/**
*
* @param targetLat
* @param targetLon
* @returns -1
*/
const getDistanceFromCurrent = (targetLat: number, targetLon: number): number => {
if (!location.value) {
return -1;
}
return calculateDistance(
location.value.latitude,
location.value.longitude,
targetLat,
targetLon,
);
};
/**
*
* @param distance
* @returns
*/
const formatDistance = (distance: number): string => {
if (distance < 0) {
return '未知距离';
}
else if (distance < 1000) {
return `${Math.round(distance)}`;
}
else {
return `${(distance / 1000).toFixed(1)}公里`;
}
};
/**
*
* @param latitude
* @param longitude
* @param name
* @param address
*/
const openLocation = (
latitude: number,
longitude: number,
name: string = '',
address: string = '',
) => {
return new Promise<void>((resolve, reject) => {
uni.openLocation({
latitude,
longitude,
name,
address,
success: () => resolve(),
fail: err => reject(err),
});
});
};
/**
*
*/
const chooseLocation = () => {
return new Promise<UniApp.ChooseLocationSuccess>((resolve, reject) => {
uni.chooseLocation({
success: (res) => {
// 更新当前位置
if (res.latitude && res.longitude) {
const locationData: LocationInfo = {
latitude: res.latitude,
longitude: res.longitude,
accuracy: 0,
verticalAccuracy: 0,
horizontalAccuracy: 0,
altitude: 0,
speed: 0,
timestamp: Date.now(),
address: {
province: '',
city: '',
district: '',
street: '',
poiName: res.name,
},
formatted: res.address,
};
location.value = locationData;
}
resolve(res);
},
fail: err => reject(err),
});
});
};
// 自动清理
onUnmounted(() => {
stopWatchLocation();
});
return {
// 状态
location,
isLocating,
isWatching,
error,
historyLocations,
// 方法
getLocation,
getAddress,
watchLocation,
stopWatchLocation,
calculateDistance,
getDistanceFromCurrent,
formatDistance,
openLocation,
chooseLocation,
};
}

View File

@ -0,0 +1,30 @@
// 定位选项
export interface LocationOptions {
type?: 'wgs84' | 'gcj02';
altitude?: boolean;
isHighAccuracy?: boolean;
highAccuracyExpireTime?: number;
success?: (res: UniApp.GetLocationSuccess) => void;
fail?: (err: any) => void;
complete?: () => void;
}
// 地址信息
export interface AddressInfo {
nation?: string;
province?: string;
city?: string;
district?: string;
street?: string;
streetNum?: string;
poiName?: string;
postalCode?: string;
cityCode?: string;
}
// 位置信息
export interface LocationInfo extends UniApp.GetLocationSuccess {
address?: AddressInfo;
formatted?: string;
timestamp?: number;
}

View File

@ -0,0 +1,24 @@
/**
* Dialog
* @example
* const {showModal} = useModal()
* showModal('提示内容')
*/
export default function useModal() {
const showModal = (content: string, options: UniApp.ShowModalOptions) => {
return new Promise((resolve, reject) => {
uni.showModal({
title: '温馨提示',
content,
showCancel: false,
confirmColor: '#1677FF',
success: res => resolve(res),
fail: () => reject(new Error('Alert 调用失败 !')),
...options,
});
});
};
return {
showModal,
};
}

View File

@ -0,0 +1,10 @@
import { hasPerm } from '@/plugins/permission';
import { currentRoute } from '@/router';
// 对某些特殊场景需要在页面onShow生命周期中校验权限:
// 1.微信小程序端点击tabbar的底层逻辑不触发uni.switchTab
// 2.h5在浏览器地址栏输入url后跳转不触发uni的路由api
// 3.首次启动加载的页面不触发uni的路由api
export default async function usePermission() {
return hasPerm(currentRoute());
}

View File

@ -0,0 +1,48 @@
import type { ShareOptions } from './types';
/**
*
* @param {object} options
* @example
* // 必须要调用onShareAppMessageonShareTimeline才能正常分享
* // 因为小程序平台必须在注册页面时主动配置onShareAppMessage, onShareTimeline才可以
* // 组合式API是运行时才能注册框架不可能默认给每个页面都开启这两个分享所以必须在页面代码里包含这两个API的字符串才会主动去注册。
* // 相关说明链接https://ask.dcloud.net.cn/question/150353
* const {onShareAppMessage, onShareTimeline} = useShare({title: '分享标题', path: 'pages/index/index', query: 'id=1', imageUrl: 'https://xxx.png'})
* onShareAppMessage()
* onShareTimeline()
*/
export default function useShare(options?: ShareOptions) {
// #ifdef MP-WEIXIN
const title = options?.title ?? '';
const path = options?.path ?? '';
const query = options?.query ?? '';
const imageUrl = options?.imageUrl ?? '';
const shareApp = (params: ShareOptions = {}) => {
onShareAppMessage(() => {
return {
title,
path: path ? `${path}${query ? `?${query}` : ''}` : '',
imageUrl,
...params,
};
});
};
const shareTime = (params: ShareOptions = {}) => {
onShareTimeline(() => {
return {
title,
query: options?.query ?? '',
imageUrl,
...params,
};
});
};
return {
onShareAppMessage: shareApp,
onShareTimeline: shareTime,
};
// #endif
}

View File

@ -0,0 +1,6 @@
export interface ShareOptions {
title?: string;
path?: string;
query?: string;
imageUrl?: string;
}

21
src/locales/index.ts Normal file
View File

@ -0,0 +1,21 @@
import type { App } from 'vue';
import { createI18n } from 'vue-i18n';
import en from './langs/en';
import zhHans from './langs/zh-Hans';
const i18n = createI18n({
legacy: false, // 必须设置false才能使用Composition API
globalInjection: true, // 为每个组件注入$为前缀的全局属性和函数
locale: uni.getLocale(),
messages: {
en,
'zh-Hans': zhHans,
},
});
function setupI18n(app: App) {
app.use(i18n);
}
export { i18n };
export default setupI18n;

11
src/locales/langs/en.ts Normal file
View File

@ -0,0 +1,11 @@
export default {
locale: {
'auto': 'System',
'en': 'English',
'zh-hans': 'Chinese',
},
home: {
'intro': 'Welcome to uni-app demo',
'toggle-langs': 'Change languages',
},
};

View File

@ -0,0 +1,11 @@
export default {
locale: {
'auto': '系统',
'en': '英语',
'zh-hans': '中文',
},
home: {
'intro': '欢迎来到uni-app演示',
'toggle-langs': '切换语言',
},
};

14
src/main.ts Normal file
View File

@ -0,0 +1,14 @@
import App from '@/App.vue';
import setupPlugins from '@/plugins';
import { createSSRApp } from 'vue';
// 引入UnoCSS
import 'virtual:uno.css';
export function createApp() {
const app = createSSRApp(App);
app.use(setupPlugins);
return {
app,
};
}

85
src/manifest.json Normal file
View File

@ -0,0 +1,85 @@
{
"name" : "",
"appid" : "",
"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" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"h5" : {
"router" : {
"mode" : "hash",
"base" : "/uniapp-vue3-template/"
},
"sdkConfigs" : {
"maps" : {
"tencent" : {
"key" : "SMJBZ-WCHK4-ZPZUA-DSIXI-XDDVQ-XWFX7"
}
}
}
}
}

113
src/pages.json Normal file
View File

@ -0,0 +1,113 @@
{
"easycom": {
"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",
"^(?!z-paging-refresh|z-paging-load-more)z-paging(.*)": "z-paging/components/z-paging$1/z-paging$1.vue"
}
},
"pages": [
{
"path": "pages/tab/user/index",
"style": {
"navigationStyle": "custom"
},
"needLogin": true
},
{
"path": "pages/tab/home/index",
"style": {
"navigationBarTitleText": "菜单分类",
"navigationStyle": "custom"
},
"needLogin": true
},
{
"path": "pages/tab/list/index",
"style": {
"navigationBarTitleText": "食谱清单",
"navigationStyle": "custom"
},
"needLogin": true
},
],
"subPackages": [
{
"root": "pages/common",
"pages": [
{
"path": "login/index",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "webview/index",
"style": {
"navigationBarTitleText": "网页"
}
},
{
"path": "404/index",
"style": {
"navigationBarTitleText": "404",
"navigationStyle": "custom"
}
},
{
"path": "goods/detail",
"style": {
"navigationBarTitleText": "菜谱详情",
"navigationStyle": "custom"
}
},
{
"path": "goods/order",
"style": {
"navigationBarTitleText": "订单详情",
"navigationStyle": "custom"
}
}
]
}
],
"preloadRule": {
"pages/tab/home/index": {
"network": "all",
"packages": ["pages/common"]
}
},
"tabBar": {
"color": "#1b233b",
"selectedColor": "#59CB56",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [{
"iconPath": "static/images/tabbar/icon_home.png",
"selectedIconPath": "static/images/tabbar/icon_home_selected.png",
"pagePath": "pages/tab/home/index",
"text": "菜单分类"
},
{
"iconPath": "static/images/tabbar/icon_list.png",
"selectedIconPath": "static/images/tabbar/icon_list_selected.png",
"pagePath": "pages/tab/list/index",
"text": "食谱清单"
},
{
"iconPath": "static/images/tabbar/icon_me.png",
"selectedIconPath": "static/images/tabbar/icon_me_selected.png",
"pagePath": "pages/tab/user/index",
"text": "我的"
}]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}

View File

@ -0,0 +1,35 @@
<template>
<div class="not-found">
<u-navbar left-icon-size="40rpx" @left-click="handleBack" />
<u-empty
mode="page"
text-size="20"
text="页面不存在"
icon="/static/images/404.png"
width="380"
height="380"
/>
</div>
</template>
<script lang="ts" setup>
import { HOME_PATH } from '@/router';
function handleBack() {
uni.$u.route({
type: 'switchTab',
url: HOME_PATH,
});
}
</script>
<style lang="scss" scoped>
.not-found {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
overflow: auto;
}
</style>

View File

@ -0,0 +1,240 @@
<template>
<view >
<view class="address-window popup-main bg-f" :class="address.address==true?'on':''">
<view class='title font-500'>选择地址<text class='iconfont icon-ic_close popup-close' @tap='close'></text></view>
<scroll-view scroll-y="true" class='list'>
<view class='item acea-row row-between-wrapper' :class='active==index?"t-color":""' v-for="(item,index) in addressList"
@tap='tapAddress(index,item.address_id)' :key='index'>
<text class='iconfont icon-ic_location5' :class='active==index?"t-color":""'></text>
<view class='address'>
<view class='name font-bold' :class='active==index?"t-color":""'>{{item.real_name}}<text class='phone'>{{item.phone}}</text></view>
<view class='line1'>{{item.province}}{{item.city}}{{item.district}}{{item.street || ''}}{{item.detail}}</view>
</view>
<text class='iconfont icon-complete' :class='active==index?"t-color":""'></text>
</view>
</scroll-view>
<!-- 无地址 -->
<view class='pictrue' v-if="!is_loading && !addressList.length">
<image :src="`${domain}/static/images/noAddress.png`"></image>
<view>暂无地址</view>
</view>
<view class='addressBnt' @tap='goAddressPages'>添加新地址</view>
</view>
<view class='mask' catchtouchmove='true' :hidden='address.address==false' @tap='close'></view>
</view>
</template>
<script>
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2024 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEBCRMEB
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
// import { getAddressList } from '@/api/user.js';
// import {
// orderAddressLst,
// }from "@/api/order";
// import { mapGetters } from "vuex";
// import { HTTP_REQUEST_URL } from '@/config/app';
export default {
props: {
pagesUrl: {
type: String,
default: '',
},
uid: {
type: Number,
default: 0,
},
tourist_unique_key: {
type: String,
default: '',
},
address: {
type: Object,
default: function() {
return {
address: true,
addressId: 0,
};
}
},
isLog: {
type: Boolean,
default: false,
},
},
// computed: mapGetters(['viewColor']),
data() {
return {
domain: '',
active: 0,
//
addressList: [
{
province: '广东省',
city: '广州市',
district: '天河区',
street: '东风路',
detail: '123号',
real_name: '张三',
phone: '13811111111',
address_id: 1,
},
{
province: '广东省',
city: '广州市',
district: '天河区',
street: '东风路',
detail: '123号',
real_name: '张三',
phone: '13811111111',
address_id: 1,
},
{
province: '广东省',
city: '广州市',
district: '天河区',
street: '东风路',
detail: '123号',
real_name: '张三',
phone: '13811111111',
address_id: 1,
}
],
is_loading: true
};
},
methods: {
tapAddress: function(index, addressid) {
this.active = index;
this.$emit('OnChangeAddress', addressid);
},
close: function() {
this.$emit('changeClose');
// this.$emit('changeTextareaStatus');
},
goAddressPages: function() {
this.$emit('changeClose');
// this.$emit('changeTextareaStatus');
uni.navigateTo({
url: this.pagesUrl
});
},
getAddressList: function() {
let that = this;
// orderAddressLst({
// page: 1,
// limit: 5,
// uid: that.uid,
// tourist_unique_key: that.tourist_unique_key,
// }).then(res => {
// let addressList = res.data.list;
// //
// for (let i = 0; i < res.data.list.length; i++) {
// if (addressList[i].address_id == that.address.addressId) {
// that.active = i;
// }
// }
// that.$set(that, 'addressList', addressList);
// that.is_loading = false;
// })
}
}
}
</script>
<style scoped lang="scss">
/* #ifndef APP-NVUE */
// uViewnvueflex-direction: column;
// nvueflex-direction: column;
view, scroll-view, swiper-item {
display: flex;
flex-direction: column;
flex-shrink: 0;
flex-grow: 0;
flex-basis: auto;
align-items: stretch;
align-content: flex-start;
}
/* #endif */
// scroll-view
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
.address-window .title {
height: 123rpx;
line-height: 123rpx;
}
.address-window .title .iconfont {
position: absolute;
right: 28rpx;
top: 30rpx;
}
.address-window .list{
max-height: 650rpx;
}
.address-window .list .item {
margin-left: 30rpx;
padding-right: 30rpx;
border-bottom: 1px solid #f5f5f5;
height: 129rpx;
font-size: 25rpx;
color: #333;
}
.address-window .list .item .iconfont {
font-size: 37rpx;
color: #2c2c2c;
}
.address-window .list .item .iconfont.icon-complete {
font-size: 30rpx;
color: #fff;
}
.address-window .list .item .address {
width: 560rpx;
}
.address-window .list .item .address .name {
font-size: 28rpx;
color: #282828;
margin-bottom: 4rpx;
}
.address-window .list .item .address .name .phone {
margin-left: 18rpx;
}
.address-window .addressBnt {
font-size: 28rpx;
color: #fff;
width: 690rpx;
height: 88rpx;
border-radius: 50rpx;
text-align: center;
line-height: 88rpx;
margin: 85rpx auto;
background-color: var(--view-theme);
}
.address-window .pictrue {
text-align: center;
}
.address-window .pictrue image,.address-window .pictrue uni-image {
width: 414rpx;
height: 305rpx;
}
.address-window .pictrue view{
color: #999;
}
.t-color {
color: var(--view-theme)!important;
}
</style>

View File

@ -0,0 +1,183 @@
<style>
.flex-sub-cart {
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100%;
/* height: 100%; */
bottom: 20rpx;
position: fixed;
border:none;
}
.box-botton{
height: 80rpx;
border: 2px solid #59cb56;
border-radius: 40rpx;
background-color: #5acb5617;
margin: 0rpx 20rpx!important;
}
.detail_box {
border-top: 2rpx solid #dfdfdf;
border-radius: 5rpx;
background-color: #ffffff;
}
</style>
<template>
<view class="fv-page flex-col" style="">
<up-navbar class="" style="" :autoBack="true" bgColor="#00000000" :fixed="true" titleColor="#595757FF"
leftIconColor="#FFFFFFFF">
</up-navbar>
<up-swiper height="480rpx" :list="list4" keyName="url" :autoplay="false"></up-swiper>
<view class="detail_box">
<up-cell
class=" "
style=""
title="青椒泡椒肉丁"
label="现在是毛豆大上市的季节,煮毛豆炒青豆都好吃,特别营养。毛豆中还含有丰富的食物纤维,不仅能改善便秘,还有利于血压和胆固醇的降低。"
>
</up-cell>
<view class="p-2">
<view class="flex box-border flex-row pb-2 ps-2 pe-2" style="" @click="addCart">
<up-text class="" size="28rpx"
:text="'食材配置'" :flex1="true" align="left" wordWrap="normal"
:show="true" lines="1" decoration="none">
</up-text>
<up-text
class=""
color="#59CB56"
size="28rpx"
text="加入食谱清单"
:flex1="true"
prefixIcon="plus"
align="right"
wordWrap="normal"
:show="true"
iconStyle="font-size: 28rpx;font-weight: bold;margin-right:10rpx;color: #59CB56;"
decoration="none"
>
</up-text>
</view>
<view class="flex box-border flex-row p-2" style=""
v-for="(item,index) in goods_list_detail" :key="index"
>
<up-text class="" size="26rpx"
color="#9b9b9b"
:text="item.name+item.label" :flex1="true" align="left" wordWrap="normal"
:show="true" lines="1" decoration="none">
</up-text>
<up-text
class=""
color="#9b9b9b"
size="24rpx"
:text="item.num+item.unit_name"
:flex1="true"
align="right"
wordWrap="normal"
:show="true"
iconStyle="font-size: 28rpx;font-weight: bold;margin-right:10rpx;color: #59CB56;"
decoration="none"
>
</up-text>
</view>
</view>
</view>
<view class="flex box-border flex-sub-cart" @click="addCart">
<u-icon style="margin: 20rpx;" size="80rpx" slot="right" color="#59CB56" name="shopping-cart-fill"></u-icon>
<u-text class="box-botton" style="" color="#59CB56" size="26rpx"
:text="'加入食谱清单'" :flex1="true" align="center" wordWrap="normal" :show="true"
iconStyle="26rpx" decoration="none">
</u-text>
</view>
</view>
</template>
<script setup>
import { reactive } from 'vue';
// import { ref, reactive, watch, computed,
// onBeforeMount, onMounted, onBeforeUnmount, onUnmounted,
// onBeforeUpdate, onUpdated, nextTick, defineProps, toRaw } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
//
onLoad((options) => {
// loadData()
})
onShow(() => {
})
// 使 reactive
const list4 = reactive([
{
url: 'https://cdn.uviewui.com/uview/resources/video.mp4',
title: '昨夜星辰昨夜风,画楼西畔桂堂东',
poster: 'https://cdn.uviewui.com/uview/swiper/swiper1.png'
},
{
url: 'https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png',
title: '身无彩凤双飞翼,心有灵犀一点通',
// url URLURL
// url URL
},
{
url: 'https://cdn.uviewui.com/uview/swiper/swiper3.png',
title: '谁念西风独自凉,萧萧黄叶闭疏窗,沉思往事立残阳',
// url URLURL
//
},
]);
const goods_list_detail = [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
];
//
function addCart() {
uni.switchTab({
url: '/pages/tab/list/index'
});
}
</script>

View File

@ -0,0 +1,507 @@
<style>
.flex {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex-sub {
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100%;
/* height: 100%; */
bottom: 20rpx;
position: fixed;
border: 1px solid #59cb56;
border-radius: 40rpx;
background-color: #ffffffdc;
}
.u-cell__body {
padding: 0rpx 30rpx !important;
}
.ellipsis {
white-space: nowrap; /* 防止文字换行 */
overflow: hidden; /* 隐藏超出部分的文字 */
text-overflow: ellipsis; /* 在末尾显示省略号 */
width: 280rpx; /* 确保元素有宽度 */
}
.flex-column {
position: static;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
margin: 0px;
color: rgb(51, 51, 51);
background: rgb(255, 255, 255);
border-radius: 0px;
border-color: rgb(255, 255, 255);
border-width: 0px;
border-style: solid;
}
.flex-row {
position: static;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
margin: 0px;
color: rgb(51, 51, 51);
border-radius: 0px;
border-color: rgb(255, 255, 255);
border-width: 0px;
border-style: solid;
}
</style>
<template>
<view>
<up-navbar
class=" "
:autoBack="true"
style="font-weight: bold;"
leftIcon="arrow-left"
title="提交订单"
titleColor="#303133"
bgColor="#FFFFFFFF"
titleWidth="600rpx"
height="80rpx"
leftIconSize="40rpx"
leftIconColor="#303133"
:safeAreaInsetTop="true"
:placeholder="true"
:fixed="true"
></up-navbar>
<view
class="flex box-border flex-row p-4"
style=""
>
<up-checkbox-group>
<up-checkbox
class=""
shape="circle"
activeColor="#59CB56"
v-model:checked="aloneChecked"
@change="handleAllCheckChange"
>
</up-checkbox>
</up-checkbox-group>
<up-text
class=""
size="24rpx"
:text="'食谱清单('+checkboxValueNum+')'"
:flex1="true"
align="left"
wordWrap="normal"
:show="true"
iconStyle="22rpx"
decoration="none"
>
</up-text>
<up-text
class=""
size="24rpx"
text="删除已选"
:flex1="true"
prefixIcon="trash"
align="right"
wordWrap="normal"
:show="true"
type="#59CB56"
iconStyle="font-size: 36rpx;font-weight: bold;"
decoration="none"
>
</up-text>
</view>
<view
class="flex box-border flex-sub"
>
<u-text
class=""
style="margin: 0rpx 0rpx 0rpx 20rpx;"
color="#59CB56"
size="26rpx"
:text="'共计'+checkboxValueNum+'件'"
:flex1="true"
align="left"
wordWrap="normal"
:show="true"
iconStyle="26rpx"
decoration="none"
>
</u-text>
<up-button
class=""
style=""
text="提交订单"
type="primary"
color="#59CB56"
shape="circle"
size="normal"
custom-style="width:200rpx"
@click="handleSubmit"
>
</up-button>
</view>
<z-paging
ref="pagingRefSC"
v-model="matchedItems_sc"
@query="changeSC('')"
style="bottom: 100rpx;top:180rpx"
class="fv-page flex-col px-4"
>
<view
v-for="(item, index) in matchedItems_sc"
:key="index"
>
<u-cell>
<template #icon>
<up-image
class="flex flex-row"
style="border:1px solid #59CB56;border-radius: 100%;"
:src="item.image"
mode="aspectFill"
width="120rpx"
height="120rpx"
shape="square"
:lazyLoad="true"
duration="500"
bgColor="#f3f4f6NaN"
:showMenuByLongpress="true"
>
</up-image>
</template>
<template #title>
<view class="h-60 flex">
<text
class=" ellipsis"
style="font-size: 24rpx;font-weight: bold;"
>营养{{ item.name }}</text>
<up-text
class="text-black"
size="24rpx"
:text="item.num+item.unit_name"
:flex1="true"
suffixIcon=""
align="right"
wordWrap="normal"
:show="true"
type="#59CB56"
iconStyle="font-size: 36rpx;font-weight: bold;"
decoration="none"
>
</up-text>
</view>
</template>
<template #label>
<view class="h-60 flex">
<text
class=" text-gray ellipsis"
style="font-size: 22rpx;"
>菜谱{{ item.label }}</text>
<!-- <up-text
class="text-black"
size="24rpx"
text="编辑数量"
:flex1="true"
prefixIcon="edit-pen"
align="right"
wordWrap="normal"
:show="true"
iconStyle="font-size: 36rpx;font-weight: bold;"
decoration="none"
>
</up-text> -->
</view>
</template>
</u-cell>
</view>
</z-paging>
</view>
</template>
<!-- 食谱清单 -->
<script setup lang="ts">
import {
ref,
reactive,
watch,
computed,
onBeforeMount,
onMounted,
onBeforeUnmount,
onUnmounted,
onBeforeUpdate,
onUpdated,
nextTick,
defineProps,
toRaw,
} from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { func } from "@/uni_modules/uview-plus/libs/function/test";
//
onLoad((options) => {
// loadData();
});
onShow(() => {});
// function loadData(pageNo: number, pageSize: number) {
// console.log('[ pageNo ] >', pageNo);
// console.log('[ pageSize ] >', pageSize);
// // pageNopageSize
// // pagingRef.value.complete()z-paging
// setTimeout(() => {
// // 1
// const list = [];
// for (let i = 0; i < 30; i++)
// list.push(urls[uni.$u.random(0, urls.length - 1)]);
// pagingRef.value?.complete(list);
// }, 1000);
// // this.$request
// // .queryList({ pageNo, pageSize })
// // .then(res => {
// // // dataListcomplete
// // pagingRef.value.complete(res.data.list);
// // })
// // .catch(res => {
// // // pagingRef.value.complete(false)
// // // catchz-paging
// // // uni.$emit('z-paging-error-emit');
// // pagingRef.value.complete(false);
// // });
// }
// export default {
// data() {
// return {
// pageNo: 1,
// pageSize: 10,
// total: 0,
// list: [],
// loading: false,
// finished: false,
// paging: true,
// };
// },
// methods: {
// loadData() {
// this.loading = true;
// // pageNopageSize
// }
// }
// }
const goods_list = [
{
id: 1,
name: "大份芒果芒果",
label: "描述信息",
image: " ",
disabled: true,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 2,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 3,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 4,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 5,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 6,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 7,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 8,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 9,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 10,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
];
// const menu_list =[{'name':'','icon':'photo'},{'name':'','icon':'photo'}]
const menu_list = [{ name: "食谱清单" }, { name: "食菜配置" }];
const listLoading = ref(false);
const dataPage = ref({
page: 0,
limit: 10,
total: 0,
});
//
const keyword_sp = ref("");
const keyword_sc = ref("");
//
const pagingRefSP = ref<InstanceType<typeof zPaging> | null>(null);
const pagingRefSC = ref<InstanceType<typeof zPaging> | null>(null);
function changeSC(e) {
// console.log(e);
try {
//
const regex = new RegExp(e, "i");
// goods_list 使 filter matchedItems_sp.value
matchedItems_sc.value = goods_list.filter((item) => regex.test(item.name));
} catch (error) {
console.error("匹配过程中发生错误:", error);
matchedItems_sc.value = []; // matchedItems
}
// console.log(matchedItems_sc.value);
pagingRefSC.value?.complete([]);
pagingRefSC.value?.complete(matchedItems_sc.value);
}
function changeSP(e) {
// console.log(e);
try {
//
const regex = new RegExp(e, "i");
// goods_list 使 filter matchedItems_sp.value
matchedItems_sp.value = goods_list.filter((item) => regex.test(item.name));
// console.log(matchedItems_sp.value);
} catch (error) {
console.error("匹配过程中发生错误:", error);
matchedItems_sp.value = []; // matchedItems
}
pagingRefSP.value?.complete([]);
pagingRefSP.value?.complete(matchedItems_sp.value);
}
// checkbox
const checkboxValueNum = ref(0);
const checkboxValue = reactive([]);
const matchedItems_sp = reactive(goods_list);
const matchedItems_sc = reactive(goods_list);
const aloneChecked = ref(false);
function checkboxChange(e) {
checkboxValue.length = 0;
e.forEach((item) => {
if (!checkboxValue.includes(item)) {
checkboxValue.push(item);
}
});
console.log(checkboxValue);
checkboxValueNum.value = checkboxValue.length;
}
function handleAllCheckChange(e) {
checkboxValue.length = 0;
if (e) {
// matchedItems_sp id checkboxValue
matchedItems_sp.forEach((item) => {
if (!checkboxValue.includes(item.id)) {
checkboxValue.push(item.id);
}
});
}
console.log(e);
console.log(checkboxValue);
checkboxValueNum.value = checkboxValue.length;
}
// //
// const loadData = (refresh = false) => {
// listLoading.value = true;
// //
// listLoading.value = false;
// };
const currentTab1 = ref(0);
// tab
function changeTab(e) {
console.log(e);
}
//
function handleClick(value) {
console.log(value);
uni.navigateTo({
url: `/pages/common/goods/detail?id=${value.name}`
});
}
//
function handleSubmit() {
uni.navigateTo({
url: `/pages/common/goods/order`
});
}
</script>

View File

@ -0,0 +1,309 @@
<style>
.ellipsis {
white-space: nowrap;
/* 防止文字换行 */
overflow: hidden;
/* 隐藏超出部分的文字 */
text-overflow: ellipsis;
/* 在末尾显示省略号 */
width: 420rpx;
/* 确保元素有宽度 */
}
</style>
<template>
<view>
<up-navbar class=" " :autoBack="true" style="font-weight: bold;" leftIcon="arrow-left" title="提交订单"
titleColor="#303133" bgColor="#FFFFFFFF" titleWidth="600rpx" height="80rpx" leftIconSize="40rpx"
leftIconColor="#303133" :safeAreaInsetTop="true" :placeholder="true" :fixed="true"></up-navbar>
<view class="address_box">
<view class=" pt-2 pb-2 ps-2 pe-2" style="">
<text class="ellipsis" style="margin: 0rpx 0rpx 0rpx 40rpx;text-align:left;display: inline-block;width: calc(100% - 100rpx);" >江阳区学校2016工作室放假咯解fafsf方法 <span>18181941463</span> </text>
</view>
<view class="flex box-border flex-row pb-2 ps-2 pe-2" style="" @click="getopenLocation">
<up-text class="" size="26rpx" :text="'四川省泸州市江阳区龙马大道海吉星农贸市场32号附一号2楼307号'" :flex1="true" align="left"
wordWrap="normal" :show="true" prefixIcon="map"
iconStyle="font-size:32rpx;color:#59CB56;margin-right:10rpx;" lines="1" decoration="none">
</up-text>
<u-icon slot="right" name="arrow-right"></u-icon>
</view>
<view class="flex box-border flex-row pb-2 ps-2 pe-2" style="">
<up-text class="" size="26rpx" color="#59CB56"
:text="'配送时间 7月17日(周三) 19:00-21:00 送达'" :flex1="true" align="left" prefixIcon="clock" wordWrap="normal"
:show="true" iconStyle="font-size:32rpx;color:#59CB56;margin-right:10rpx;" lines="1" decoration="none">
</up-text>
<u-icon slot="right" name="arrow-right"></u-icon>
</view>
</view>
<z-paging ref="pagingRefSC" v-model="matchedItems_sc" @query="changeSC('')" style="bottom: 110rpx;top:300rpx"
class="fv-page flex-col px-2">
<view v-for="(item, index) in matchedItems_sc" :key="index">
<u-cell>
<template #icon>
<up-image class="flex flex-row" style="border:1px solid #59CB56;border-radius: 5rpx;"
:src="item.image" mode="aspectFill" width="120rpx" height="120rpx" shape="square"
:lazyLoad="true" duration="500" bgColor="#f3f4f6NaN" :showMenuByLongpress="true">
</up-image>
</template>
<template #title>
<view class="h-60 flex">
<text class=" ellipsis" style="font-size: 24rpx;font-weight: bold;">营养{{ item.name
}}</text>
<up-text style="margin-right: 10rpx;" class="text-black" size="24rpx" :text="item.num + item.unit_name" :flex1="true"
suffixIcon="" align="right" wordWrap="normal" :show="true" type="#59CB56"
iconStyle="font-size: 36rpx;font-weight: bold;" decoration="none">
</up-text>
</view>
</template>
<template #label>
<view class="h-60 flex">
<text class=" text-gray ellipsis" style="font-size: 22rpx;">菜谱{{ item.label }}</text>
</view>
</template>
</u-cell>
</view>
</z-paging>
<view class="flex box-border flex-sub" style=" bottom: 20rpx;">
<u-text class="" style="margin: 0rpx 0rpx 0rpx 20rpx;" color="#59CB56" size="26rpx"
:text="'共计' + checkboxValueNum + '件'" :flex1="true" align="left" wordWrap="normal" :show="true"
iconStyle="26rpx" decoration="none">
</u-text>
<up-button class="" style="" text="提交订单" type="primary" color="#59CB56" shape="circle" size="normal"
custom-style="width:200rpx" @click="handleSubmit">
</up-button>
</view>
</view>
</template>
<!-- 食谱清单 -->
<script setup lang="ts">
import { useShare,useLocation } from '@/hooks';
const {openLocation } = useLocation();
//
function getopenLocation(){
openLocation(32.05, 32.05);
}
import {
ref,
reactive,
} from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { func } from "@/uni_modules/uview-plus/libs/function/test";
//
onLoad((options) => {
// loadData();
});
onShow(() => { });
const goods_list = [
{
id: 1,
name: "大份芒果芒果",
label: "描述信息",
image: " ",
disabled: true,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 2,
name: "新鲜西瓜不甜不要钱,随便吃哦,华友很多方式JFK了就爱上基金大幅方法是飞机离开房间发士大夫十分恐惧撒发范德萨发生",
label: "描述信息,新鲜西瓜不甜不要钱,随便吃哦,华友很多方式JFK了就爱上基金大幅方法是飞机",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 3,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 4,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 5,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 6,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 7,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 8,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 9,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
{
id: 10,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
},
];
// const menu_list =[{'name':'','icon':'photo'},{'name':'','icon':'photo'}]
const menu_list = [{ name: "食谱清单" }, { name: "食菜配置" }];
const listLoading = ref(false);
const dataPage = ref({
page: 0,
limit: 10,
total: 0,
});
//
const keyword_sp = ref("");
const keyword_sc = ref("");
//
const pagingRefSP = ref<InstanceType<typeof zPaging> | null>(null);
const pagingRefSC = ref<InstanceType<typeof zPaging> | null>(null);
function changeSC (e) {
// console.log(e);
try {
//
const regex = new RegExp(e, "i");
// goods_list 使 filter matchedItems_sp.value
matchedItems_sc.value = goods_list.filter((item) => regex.test(item.name));
} catch (error) {
console.error("匹配过程中发生错误:", error);
matchedItems_sc.value = []; // matchedItems
}
// console.log(matchedItems_sc.value);
pagingRefSC.value?.complete([]);
pagingRefSC.value?.complete(matchedItems_sc.value);
}
function changeSP (e) {
// console.log(e);
try {
//
const regex = new RegExp(e, "i");
// goods_list 使 filter matchedItems_sp.value
matchedItems_sp.value = goods_list.filter((item) => regex.test(item.name));
// console.log(matchedItems_sp.value);
} catch (error) {
console.error("匹配过程中发生错误:", error);
matchedItems_sp.value = []; // matchedItems
}
pagingRefSP.value?.complete([]);
pagingRefSP.value?.complete(matchedItems_sp.value);
}
// checkbox
const checkboxValueNum = ref(0);
const checkboxValue = reactive([]);
const matchedItems_sp = reactive(goods_list);
const matchedItems_sc = reactive(goods_list);
const aloneChecked = ref(false);
function checkboxChange (e) {
checkboxValue.length = 0;
e.forEach((item) => {
if (!checkboxValue.includes(item)) {
checkboxValue.push(item);
}
});
console.log(checkboxValue);
checkboxValueNum.value = checkboxValue.length;
}
function handleAllCheckChange (e) {
checkboxValue.length = 0;
if (e) {
// matchedItems_sp id checkboxValue
matchedItems_sp.forEach((item) => {
if (!checkboxValue.includes(item.id)) {
checkboxValue.push(item.id);
}
});
}
console.log(e);
console.log(checkboxValue);
checkboxValueNum.value = checkboxValue.length;
}
// //
// const loadData = (refresh = false) => {
// listLoading.value = true;
// //
// listLoading.value = false;
// };
const currentTab1 = ref(0);
// tab
function changeTab (e) {
console.log(e);
}
//
function handleClick (value) {
console.log(value);
uni.navigateTo({
url: `/pages/common/goods/detail?id=${value.name}`
});
}
//
function handleSubmit () {
// uni.navigateTo({
// url: `/pages/common/goods/order`
// });
console.log('提交订单');
}
</script>

View File

@ -0,0 +1,180 @@
<template>
<view>
<view class="login-form-wrap">
<view class="title">
欢迎登录
</view>
<input v-model="tel" class="u-border-bottom" type="number" placeholder="请输入手机号">
<view class="u-border-bottom my-40rpx flex">
<input v-model="code" class="flex-1" type="number" placeholder="请输入验证码">
<view>
<u-code ref="uCodeRef" @change="codeChange" />
<u-button :text="tips" type="success" size="mini" @click="getCode" />
</view>
</view>
<button class="login-btn" :style="[inputStyle]" @tap="submit">
登录 <text class="i-mdi-login" />
</button>
<view class="alternative">
<view class="password">
密码登录
</view>
<view class="issue flex items-center">
遇到问题 <text class="i-mdi-help" />
</view>
</view>
</view>
<view class="login-type-wrap">
<view class="item wechat">
<view class="icon">
<u-icon size="35" name="weixin-fill" color="rgb(83,194,64)" />
</view>
微信
</view>
<view class="item QQ">
<view class="icon">
<u-icon size="35" name="qq-fill" color="rgb(17,183,233)" />
</view>
QQ
</view>
</view>
<view class="hint">
登录代表同意
<text class="link">
用户协议隐私政策
</text>
并授权使用您的账号信息如昵称头像收获地址以便您统一管理
</view>
</view>
</template>
<script setup lang="ts">
import type { CSSProperties } from 'vue';
import { HOME_PATH, isTabBarPath, LOGIN_PATH, removeQueryString } from '@/router';
import { setToken } from '@/utils/auth';
import uCode from 'uview-plus/components/u-code/u-code.vue';
// import { useUserStore } from '@/store';
// const userStore = useUserStore();
const tel = ref<string>('18502811111');
const code = ref<string>('1234');
const tips = ref<string>();
const uCodeRef = ref<InstanceType<typeof uCode> | null>(null);
let redirect = HOME_PATH;
const inputStyle = computed<CSSProperties>(() => {
const style = {} as CSSProperties;
if (tel.value && code.value) {
style.color = '#fff';
style.backgroundColor = uni.$u.color.warning;
}
return style;
});
function codeChange(text: string) {
tips.value = text;
}
function getCode() {
if (uCodeRef.value?.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码',
});
setTimeout(() => {
uni.hideLoading();
uni.$u.toast('验证码已发送');
//
uCodeRef.value?.start();
}, 1000);
}
else {
uni.$u.toast('倒计时结束后再发送');
}
}
async function submit() {
if (!uni.$u.test.mobile(Number(tel.value))) {
uni.$u.toast('请输入正确的手机号');
return;
}
if (!code.value) {
uni.$u.toast('请输入验证码');
return;
}
//
// const res = await userStore.login({ phone: tel.value, code: code.value }).catch(() => {
// uni.$u.toast('');
// });
// if (!res) return;
setToken('1234567890');
setTimeout(() => {
uni.$u.route({
type: isTabBarPath(redirect) ? 'switchTab' : 'redirectTo',
url: redirect,
});
}, 800);
}
onLoad((options: any) => {
if (options.redirect && removeQueryString(options.redirect) !== LOGIN_PATH) {
redirect = decodeURIComponent(options.redirect);
}
});
</script>
<style lang="scss" scoped>
.login-form-wrap {
@apply mt-80rpx mx-auto mb-0 w-600rpx;
.title {
@apply mb-100rpx text-60rpx text-left font-500;
}
input {
@apply pb-6rpx mb-10rpx text-left;
}
.tips {
@apply mt-8rpx mb-60rpx;
color: $u-info;
}
.login-btn {
@apply flex items-center justify-center py-12rpx px-0 text-30rpx bg-#fdf3d0 border-none;
color: $u-tips-color;
&::after {
@apply border-none;
}
}
.alternative {
@apply flex justify-between mt-30rpx;
color: $u-tips-color;
}
}
.login-type-wrap {
@apply flex justify-between pt-350rpx px-150rpx pb-150rpx;
.item {
@apply flex items-center flex-col text-28rpx;
color: $u-content-color;
}
}
.hint {
@apply px-40rpx py-20rpx text-24rpx;
color: $u-tips-color;
.link {
color: $u-warning;
}
}
</style>

View File

@ -0,0 +1,12 @@
<template>
<web-view class="h-full" :src="url" />
</template>
<script setup lang="ts">
const url = ref<string>('');
onLoad((options: any) => {
if (options.url)
url.value = decodeURIComponent(options.url);
});
</script>

View File

@ -0,0 +1,835 @@
<style lang='scss'>
.ellipsis {
white-space: nowrap;
/* 防止文字换行 */
overflow: hidden;
/* 隐藏超出部分的文字 */
text-overflow: ellipsis;
/* 在末尾显示省略号 */
// width: 260rpx!important;
width: calc(100% - 80rpx) !important;
/* 确保元素有宽度 */
}
.ellipsis_text {
width: 340rpx !important;
// width: calc(100% - 80rpx)!important;
}
.zp-l-text-rpx {
display: none !important;
}
html,
body {
height: 0px;
}
</style>
<template>
<view>
<up-navbar class=" " style="font-weight: bold;" leftIcon="" title="食谱菜单" titleColor="#303133" bgColor="#FFFFFFFF"
titleWidth="600rpx" height="80rpx" leftIconSize="40rpx" leftIconColor="#303133" :safeAreaInsetTop="true"
:placeholder="true" :fixed="true"></up-navbar>
<up-search class="flex,flex-row p-1" style="" shape="round" bgColor="#f2f2f2" placeholder="输入食谱" :clearabled="true"
:showAction="false" inputAlign="left" borderColor="transparent" searchIconColor="#909399" color="#606266"
placeholderColor="#909399" searchIcon="search" margin="10rpx" maxlength="-1" height="60rpx" v-model="keyword_sp"
@change="changeSP">
</up-search>
<u-scroll-list :indicator="false">
<view class="scroll-list" style="flex-direction: row;">
<view class="scroll-list__goods-item " v-for="(item, index) in list" :key="index"
:class="(index === menuIndex) ? 'row-active' : ''" @click="handleClick(index)"
:style="{ backgroundColor: item.bgColor }">
<image class="scroll-list__goods-item__image" :src="item.image" :class="(index === menuIndex) ? 'border-row-active' : ''"></image>
<text class="scroll-list__goods-item__text">{{ item.name }}</text>
</view>
</view>
</u-scroll-list>
<z-paging ref="pagingRefSP" v-model="matchedItems_sp" loading-more-default-text=""
style="bottom: 100rpx;top:360rpx;width: 200rpx;right:0; padding: 0px;" class="fv-page flex-col px-4">
<u-scroll-list :indicator="false">
<view class="scroll-list" style="flex-direction: column;">
<view class="scroll-list__goods-item-column " v-for="(item, index) in list" :key="index"
:class="(index === menuIndex) ? 'column-active' : ''" @click="handleClick(index)"
:style="{ backgroundColor: item.bgColor }">
<!-- <image class="scroll-list__goods-item__image" :src="item.image"></image> -->
<slot name="tabItem">
</slot>
<!-- <view style="padding: 20px 0px;"> </view> -->
<text class="scroll-list__goods-item__text">{{ item.name }}</text>
</view>
</view>
</u-scroll-list>
</z-paging>
<z-paging ref="pagingRefSP" v-model="matchedItems_sp" @query="changeSP"
style="bottom: 100rpx;top:360rpx; width: calc(100% - 220rpx);left: unset; background-color: #FFFFFFFF;"
class="fv-page flex-col px-4">
<view v-for="(item, index) in goods_list" :key="index">
<up-cell :border='false' @click="goodsDetail(item.id)">
<template #icon>
<up-image :src="item.image" width="140rpx" height="140rpx"></up-image>
</template>
<template v-slot:title>
<view class="h-50 text-md ellipsis" style="font-size: 24rpx;">
{{ item.name }}
</view>
<view class="h-50 flex">
<text class=" text-gray ellipsis" style="font-size: 22rpx;">主料{{ item.label }}</text>
</view>
<view class="h-50 flex ellipsis_text">
<text class=" text-md text-gray ellipsis" style="font-size: 22rpx;">营养{{ item.label }}</text>
<up-icon align="right" name="plus-circle" color="#59CB56" size="42rpx"></up-icon>
</view>
</template>
</up-cell>
</view>
</z-paging>
</view>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { CommonApi, UserApi } from "@/api";
export default defineComponent({
data () {
return {
keyword_sp: '',
menuIndex: 0,
matchedItems_sp: [],
tabList: [],
list1: [{
title: "白菜",
children: [
{
title: "水煮肉片",
cover: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
price: 88,
},
],
},
{
title: "配料",
children: [
{
title: "酸菜鱼",
cover: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
price: 99,
},
],
}],
goods_list: [
{
id: 1,
name: "大份芒果芒果",
label: "描述信息,大份芒果芒果,加多加糖",
image: " ",
disabled: true,
num: 22,
unit_name: "斤",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 2,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 44,
unit_name: "件",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 5,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 6,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 7,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 8,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 9,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 10,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 3,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 42,
unit_name: "份",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 4,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 34236,
unit_name: "个",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "青椒肉丝",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 5,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 6,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 7,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 8,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 9,
name: "大份芒果芒果",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
{
id: 10,
name: "新鲜西瓜不甜不要钱,随便吃哦",
label: "描述信息",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "个",
default_num: 100,
info: [
{
id: 1,
name: "猪肉",
label: "",
image: " ",
disabled: true,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 2,
name: "毛豆",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
{
id: 3,
name: "食用油",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "g",
default_num: 100,
},
{
id: 4,
name: "豆瓣酱",
label: "(建议食材)",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
disabled: false,
num: 100,
unit_name: "ml",
default_num: 100,
},
]
},
],
list: [
{
name: "全部",
image: "",
},
{
name: "水果1",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品2",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品3",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品4",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
}, {
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
{
name: "商品5",
image: "https://s3.bmp.ovh/imgs/2024/12/16/35bc6d28ab1c8bc7.png",
},
],
};
},
created () {
this.handleClick(0)
},
methods: {
handleClick (index: number) {
// menuIndex.value = index;
this.menuIndex = index;
CommonApi.goodsMenu({ key: 123 }).catch((res) => {
if (res.status == 200) {
// this.tabList = res.data.menuList;
this.tabList = this.list1
console.log(res);
} else {
uni.$u.toast(res.message);
}
});
},
//
goodsDetail (id: number) {
console.log(id);
uni.navigateTo({
url: `/pages/common/goods/detail?id=${id}`
});
},
changeSP () {
console.log("请求食谱数据");
try {
//
const regex = new RegExp(this.keyword_sp, "i");
// goods_list 使 filter matchedItems_sp.value
this.matchedItems_sp = this.goods_list.filter((item) => regex.test(item.name));
// console.log(matchedItems_sp.value);
} catch (error) {
console.error("匹配过程中发生错误:", error);
this.matchedItems_sp = []; // matchedItems
}
this.$refs.pagingRefSP.complete();
this.$refs.pagingRefSP.complete(this.matchedItems_sp);
}
},
});
</script>

1081
src/pages/tab/list/index.vue Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,83 @@
<style>
.detail_box {
border: 1rpx solid #dfdfdf;
border-radius: 20rpx;
margin: 20rpx;
background-color: #ffffff;
}
html, body {
height: 0px;
}
</style>
<template>
<view class="page-wrap p-2">
<view class="flex detail_box p-4 bg-white">
<view class="mr-10rpx">
<u-avatar src="/static/images/logo.png" size="70" />
</view>
<view class="flex-1">
<view class="pb-20rpx font-size-36rpx">
uni-app
</view>
<view class="u-tips-color font-size-28rpx" @click="toCopy">
微信号:uni-app
</view>
</view>
<view class="ml-10rpx p-10rpx">
<u-icon name="scan" color="#969799" />
</view>
<view class="ml-10rpx p-10rpx">
<u-icon name="arrow-right" color="#969799" />
</view>
</view>
<view class="detail_box bg-white">
<u-cell icon="" title="昵称" is-link rightIcon="lock" value="小花猫" :border="false" size="large"
customStyle="padding:20rpx;font-size:28rpx" />
<u-cell icon="" title="ID" is-link rightIcon="lock" value="26" :border="false" size="large"
customStyle="padding:20rpx;font-size:28rpx" />
<u-cell icon="" title="手机号" is-link rightIcon="lock" value="18181941463" :border="false" size="large"
customStyle="padding:20rpx;font-size:28rpx" />
<u-cell icon="order" title="订单详情" is-link value="" :border="false" size="large"
customStyle="padding:20rpx;font-size:28rpx" />
</view>
<view class="p-2 bg-white">
<u-button class="p-2" size="large" text="退出登录" type="primary" color="#59CB56" shape="circle"
custom-style="width:100%;" @click="loginOut">
</u-button>
</view>
</view>
</template>
<script setup lang="ts">
import { HOME_PATH, isTabBarPath, LOGIN_PATH, removeQueryString } from '@/router';
import { useClipboard, usePermission } from '@/hooks';
import { setToken } from '@/utils/auth';
// import { useUserStore } from '@/store';
// const userStore = useUserStore();
let redirect = HOME_PATH;
async function loginOut() {
// 退
// const res = await userStore.logout().catch(() => {
// uni.$u.toast('退');
// });
// if (!res) return;
setToken('');
setTimeout(() => {
uni.$u.route({
type: isTabBarPath(redirect) ? 'switchTab' : 'redirectTo',
url: redirect,
});
}, 800);
}
// tabbaruni.switchTabonShow
onShow(async () => {
const hasPermission = await usePermission();
console.log(hasPermission ? '已登录' : '未登录,拦截跳转');
});
</script>

21
src/plugins/index.ts Normal file
View File

@ -0,0 +1,21 @@
import type { App } from 'vue';
import setupI18n from '@/locales';
import setupStore from '@/store';
import setupRequest from '@/utils/request';
import setupPermission from './permission';
import setupUI from './ui';
export default {
install(app: App) {
// UI扩展配置
setupUI(app);
// 状态管理
setupStore(app);
// 国际化
setupI18n(app);
// 路由拦截
setupPermission();
// 网络请求
setupRequest();
},
};

57
src/plugins/permission.ts Normal file
View File

@ -0,0 +1,57 @@
import {
ERROR404_PATH,
isPathExists,
LOGIN_PATH,
removeQueryString,
routes,
} from '@/router';
import { isLogin } from '@/utils/auth';
// 白名单路由
const whiteList = ['/'];
routes.forEach((item) => {
if (item.needLogin !== true) {
whiteList.push(item.path);
}
});
/**
*
* @param {string} path
* @returns {boolean}
*/
export function hasPerm(path = '') {
if (!isPathExists(path) && path !== '/') {
uni.redirectTo({
url: ERROR404_PATH,
});
return false;
}
// 在白名单中或有token直接放行
const hasPermission
= whiteList.includes(removeQueryString(path)) || isLogin();
if (!hasPermission) {
// 将用户的目标路径传递过去,这样可以实现用户登录之后,直接跳转到目标页面
uni.redirectTo({
url: `${LOGIN_PATH}?redirect=${encodeURIComponent(path)}`,
});
}
return hasPermission;
}
function setupPermission() {
// 注意拦截uni.switchTab本身没有问题。但是在微信小程序端点击tabbar的底层逻辑并不是触发uni.switchTab。
// 所以误认为拦截无效此类场景的解决方案是在tabbar页面的页面生命周期onShow中处理。
['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].forEach((item) => {
// https://uniapp.dcloud.net.cn/api/interceptor.html
uni.addInterceptor(item, {
// 页面跳转前进行拦截, invoke根据返回值进行判断是否继续执行跳转
invoke(args) {
// args为所拦截api中的参数比如拦截的是uni.redirectTo(OBJECT)则args对应的是OBJECT参数
return hasPerm(args.url);
},
});
});
}
export default setupPermission;

28
src/plugins/ui.ts Normal file
View File

@ -0,0 +1,28 @@
import type { App } from 'vue';
import uviewPlus, { setConfig } from 'uview-plus';
function setupUI(app: App) {
// 下面的在特殊场景下才需要配置通常不用配置即可直接使用uview-plus框架。
// 调用setConfig方法方法内部会进行对象属性深度合并可以放心嵌套配置
// 需要在app.use(uview-plus)之后执行
setConfig({
// 修改$u.config对象的属性
config: {
// 修改默认单位为rpx相当于执行 uni.$u.config.unit = 'rpx'
unit: 'px',
},
// 修改$u.props对象的属性
props: {
// 修改radio组件的size参数的默认值相当于执行 uni.$u.props.radio.size = 30
radio: {
// size: 20
},
// 其他组件属性配置
// ......
},
});
app.use(uviewPlus);
}
export default setupUI;

89
src/router/index.ts Normal file
View File

@ -0,0 +1,89 @@
import pagesJson from '@/pages.json';
// 路径常量
export const HOME_PATH = '/pages/tab/home/index';
export const LOGIN_PATH = '/pages/common/login/index';
export const ERROR404_PATH = '/pages/common/404/index';
/**
*
* @param {object} pagesJson
* @returns [{"path": "/pages/tab/home/index","needLogin": false},...]
*/
function parseRoutes(pagesJson = {} as any) {
if (!pagesJson.pages) {
pagesJson.pages = [];
}
if (!pagesJson.subPackages) {
pagesJson.subPackages = [];
}
function parsePages(pages = [] as any, rootPath = '') {
const routes = [];
for (let i = 0; i < pages.length; i++) {
routes.push({
path: rootPath ? `/${rootPath}/${pages[i].path}` : `/${pages[i].path}`,
needLogin: pages[i].needLogin === true,
});
}
return routes;
}
function parseSubPackages(subPackages = [] as any) {
const routes = [];
for (let i = 0; i < subPackages.length; i++) {
routes.push(...parsePages(subPackages[i].pages, subPackages[i].root));
}
return routes;
}
return [
...parsePages(pagesJson.pages),
...parseSubPackages(pagesJson.subPackages),
];
}
export const routes = parseRoutes(pagesJson);
/**
*
* @returns {string}
*/
export function currentRoute() {
// getCurrentPages() 至少有1个元素所以不再额外判断
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1] as any;
return currentPage?.$page?.fullPath || currentPage.route;
}
/**
*
* @param {string} path
* @returns {string}
*/
export function removeQueryString(path = '') {
return path.split('?')[0];
}
/**
*
* @param {string} path
* @returns {boolean}
*/
export function isPathExists(path = '') {
const cleanPath = removeQueryString(path);
return routes.some(item => item.path === cleanPath);
}
/**
* tabbar页面路径
* @param {string} path
* @returns {boolean} tabbar页面
*/
export function isTabBarPath(path = '') {
const cleanPath = removeQueryString(path);
return (
pagesJson.tabBar?.list?.some(
item => `/${item.pagePath}` === cleanPath,
) === true
);
}

BIN
src/static/images/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
src/static/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src/static/images/pay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 897 B

View File

@ -0,0 +1,160 @@
page {
font-size: $u-font-base;
color: $u-main-color;
background-color: $u-bg-color;
}
.scroll-list {
@include flex(column);
&__goods-item {
padding: 0px 40rpx;
// margin-right: 20px;
&__image {
border: 2rpx solid rgb(177, 177, 177);
width: 80rpx;
height: 80rpx;
border-radius: 80rpx;
}
&__text {
width: 80rpx;
// color: #f56c6c;
display: inline-block;
text-align: center;
font-size: 26rpx;
// margin-top: 5px;
}
}
&__goods-item-column {
width: 200rpx;
padding: 20rpx 0rpx;
.scroll-list__goods-item__text {
width: 200rpx;
}
}
.border-row-active {
border: 2rpx solid #59cb56;
}
.row-active {
color: #59cb56;
}
.column-active {
width: 200rpx;
// border-left: 10rpx solid #59cb56;
color: #59cb56;
background-color: #FFFFFF;
.scroll-list__goods-item__text {
width: 190rpx;
border-left: 10rpx solid #59cb56;
}
}
&__goods-item:hover {
color: #59cb56;
}
}
.popup-main {
position: fixed;
bottom: 0;
width: 100%;
left: 0;
background-color: #fff;
z-index: 280;
border-radius: 40rpx 40rpx 0 0;
transform: translate3d(0, 100%, 0);
transform: translateY(100%);
transition: all .3s cubic-bezier(.25, .5, .5, .9);
&.on {
transform: translate3d(0, 0, 0);
transform: translateY(0);
}
>.title {
font-size: 32rpx;
text-align: center;
position: relative;
}
}
.detail-count {
border-radius: 44rpx 44rpx 0 0;
background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 54%, rgba(255, 255, 255, 0) 100%);
}
.swiper-bg {
border-radius: 44rpx 44rpx 0 0;
background: #F5f5f5;
top: -2px;
}
.flex {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex-sub {
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100%;
/* height: 100%; */
bottom: 100rpx;
position: fixed;
border: 1px solid #59cb56;
border-radius: 40rpx;
background-color: #ffffffdc;
}
.flex-column {
position: static;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
margin: 0px;
color: rgb(51, 51, 51);
background: rgb(255, 255, 255);
border-radius: 0px;
border-color: rgb(255, 255, 255);
border-width: 0px;
border-style: solid;
}
.flex-row {
position: static;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
margin: 0px;
color: rgb(51, 51, 51);
border-radius: 0px;
border-color: rgb(255, 255, 255);
border-width: 0px;
border-style: solid;
}
.address_box {
border: 2rpx solid #dfdfdf;
margin: 10PX;
border-radius: 10rpx;
background-color: #ffffff;
}

27
src/store/index.ts Normal file
View File

@ -0,0 +1,27 @@
import type { App } from 'vue';
import { createPinia } from 'pinia';
// 数据持久化
import { createPersistedState } from 'pinia-plugin-persistedstate';
// 导入子模块
import useAppStore from './modules/app';
import useUserStore from './modules/user';
// 安装pinia状态管理插件
function setupStore(app: App) {
const store = createPinia();
const piniaPersist = createPersistedState({
storage: {
getItem: uni.getStorageSync,
setItem: uni.setStorageSync,
},
});
store.use(piniaPersist);
app.use(store);
}
// 导出模块
export { useAppStore, useUserStore };
export default setupStore;

View File

@ -0,0 +1,58 @@
import type { AppState } from './types';
import { defineStore } from 'pinia';
const useAppStore = defineStore('app', {
state: (): AppState => ({
systemInfo: {} as UniApp.GetSystemInfoResult,
}),
getters: {
getSystemInfo(): UniApp.GetSystemInfoResult {
return this.systemInfo;
},
},
actions: {
setSystemInfo(info: UniApp.GetSystemInfoResult) {
this.systemInfo = info;
},
initSystemInfo() {
uni.getSystemInfo({
success: (res: UniApp.GetSystemInfoResult) => {
this.setSystemInfo(res);
},
fail: (err: any) => {
console.error(err);
},
});
},
checkUpdate() {
const updateManager = uni.getUpdateManager();
updateManager.onCheckForUpdate((res: UniApp.OnCheckForUpdateResult) => {
// 请求完新版本信息的回调
console.log(res.hasUpdate);
});
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
},
});
});
updateManager.onUpdateFailed((res: any) => {
console.error(res);
// 新的版本下载失败
uni.showToast({
title: '更新失败',
icon: 'error',
});
});
},
},
});
export default useAppStore;

View File

@ -0,0 +1,3 @@
export interface AppState {
systemInfo: UniApp.GetSystemInfoResult;
}

View File

@ -0,0 +1,85 @@
import type { LoginReq } from '@/api/user/types';
import type { providerType, UserState } from './types';
import { UserApi } from '@/api';
import { clearToken, setToken } from '@/utils/auth';
import { defineStore } from 'pinia';
const useUserStore = defineStore('user', {
state: (): UserState => ({
user_id: '0',
user_name: '游客登录',
avatar: '',
token: '',
}),
getters: {
userInfo(state: UserState): UserState {
return { ...state };
},
},
actions: {
// 设置用户的信息
setInfo(partial: Partial<UserState>) {
this.$patch(partial);
},
// 重置用户信息
resetInfo() {
this.$reset();
},
// 获取用户信息
async info() {
const result = await UserApi.profile();
this.setInfo(result);
},
// 异步登录并存储token
login(loginForm: LoginReq) {
return new Promise((resolve, reject) => {
UserApi.login(loginForm).then((res) => {
const token = res.token;
if (token) {
setToken(token);
}
resolve(res);
}).catch((error) => {
reject(error);
});
});
},
// Logout
async logout() {
await UserApi.logout();
this.resetInfo();
clearToken();
},
// 小程序授权登录
authLogin(provider: providerType = 'weixin') {
return new Promise((resolve, reject) => {
uni.login({
provider,
success: async (result: UniApp.LoginRes) => {
if (result.code) {
const res = await UserApi.loginByCode({ code: result.code });
resolve(res);
}
else {
reject(new Error(result.errMsg));
}
},
fail: (err: any) => {
console.error(`login error: ${err}`);
reject(err);
},
});
});
},
// 获取菜单
async goodsMenu() {
await UserApi.goodsMenu();
},
},
persist: true,
});
export default useUserStore;

View File

@ -0,0 +1,16 @@
export type RoleType = '' | '*' | 'user';
export interface UserState {
user_id?: string;
user_name?: string;
avatar?: string;
token?: string;
}
export type providerType =
| 'weixin'
| 'qq'
| 'sinaweibo'
| 'xiaomi'
| 'apple'
| 'univerify'
| undefined;

54
src/uni.scss Normal file
View File

@ -0,0 +1,54 @@
@import 'uview-plus/theme.scss';
/* 颜色变量 */
/* 行为相关颜色 */
$u-primary: #59CB56;
$u-primary-dark: #76a3fd;
$u-success: #3ed268;
$u-warning: #fe9831;
$u-error: #fa4e62;
/* 文字基本颜色 */
$u-main-color: #1b233b;
$u-content-color: #60687e;
$u-tips-color: #7e869a;
$u-light-color: #bdc3d2;
$u-disabled-color: #dce0eb;
/* 背景颜色 */
$u-bg-color: #fbfbfb;
/* 边框颜色 */
$u-border-color: #f2f7f7;
/* 尺寸变量 */
/* 文字尺寸 */
$u-font-sm: 24rpx;
$u-font-base: 28rpx;
$u-font-lg: 32rpx;
/* 图片尺寸 */
$u-img-sm: 40rpx;
$u-img-base: 52rpx;
$u-img-lg: 80rpx;
/* Border Radius */
$u-border-radius-sm: 4rpx;
$u-border-radius-base: 6rpx;
$u-border-radius-lg: 12rpx;
$u-border-radius-circle: 50%;
/* 水平间距 */
$u-spacing-row-sm: 10rpx;
$u-spacing-row-base: 20rpx;
$u-spacing-row-lg: 30rpx;
/* 垂直间距 */
$u-spacing-col-sm: 8rpx;
$u-spacing-col-base: 16rpx;
$u-spacing-col-lg: 24px;
/* 透明度 */
$u-opacity-disabled: 0.3;

View 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.

View 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>
[![stars](https://img.shields.io/github/stars/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
[![forks](https://img.shields.io/github/forks/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus)
[![issues](https://img.shields.io/github/issues/ijry/uview-plus?style=flat-square&logo=GitHub)](https://github.com/ijry/uview-plus/issues)
[![release](https://img.shields.io/github/v/release/ijry/uview-plus?style=flat-square)](https://gitee.com/jry/uview-plus/releases)
[![license](https://img.shields.io/github/license/ijry/uview-plus?style=flat-square)](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应用到您的产品中。

View File

@ -0,0 +1,921 @@
## 3.4.522025-07-16
fix: 修复底部安全区域组件兼容性
## 3.4.512025-07-14
fix: 修复u-slider在click后没有触发change事件
## 3.4.502025-07-13
feat: subsection分段器添加禁用参数
## 3.4.492025-07-11
feat: picker支持bgColor、round、duration和overlayOpacity属性
## 3.4.482025-07-10
fix: 官方文档Card示例组件多行显示省略号样式异常
feat: album组件支持自定义preview事件
## 3.4.472025-07-09
fix: 修复datetime-picker打开时数值可能出现对不上的问题
feat: Subsection 分段器添加支持从 list中读取激活文字颜色和未激活文字颜色
fix: 修复modal定义confirmButton
fix: safe-bottom底部安全距离在小程序优先用JS计算
feat: 新增tree树形组件
## 3.4.462025-07-08
feat: 上传组件预览视频支持videoPreviewObjectFit参数
feat: td增加多个样式props
fix: 修复noticeBar字号增大时文字遮挡
feat: 项目工程增加pinia
## 3.4.452025-07-01
fix: 修复picker-data组件缺少name
fix: 优化picker高度单位
## 3.4.442025-06-30
fix: 修复indexList中stikcy属性写死的问题(always true)
feat: 搜索框添加新的右侧插槽
fix: 修复indexList中丢失的select event
fix: 解决因为层级问题导致点击picker选择器无法正常弹出
## 3.4.432025-06-16
feat: table2支持header插槽
## 3.4.422025-06-12
fix: 修复qrcode中默认id问题及canvas2时App无法绘制二维码 感谢@jiaruiyan
## 3.4.412025-06-11
feat: qrcode支持 新参数useRootHeightAndWidth 是否使用根节点的宽高 感谢@YJR
feat: toast支持设置zIndex层级
## 3.4.402025-06-06
fix: 升级二维码 canvas -> canvas2 感谢@yjr
## 3.4.392025-05-31
fix: 修改步骤条微信小程序下的布局 感谢@jiaruiyan
fix: u-tabs在屏幕尺寸发生变化时滑块位置没有发生变化 感谢@aqzhft
fix: 鸿蒙平台不支持plus.runtime.openWeb 感谢@aqzhft
## 3.4.382025-05-30
fix: 修复picker-data快捷组件缺少index
fix: 修复picker组件双向绑定初始化及取消后复原再次打开后的当前项目
## 3.4.372025-05-29
feat: modal支持设置动画时间
fix: DatetimePicker v-model 绑定异步设置无效 (#803)
## 3.4.362025-05-28
fix: lazy-load图片为空时显示错误
## 3.4.352025-05-28
feat: 进度条支持从右往左加载
## 3.4.342025-05-28
feat: table2支持自定义标题和单元格样式
## 3.4.332025-05-27
fix: 修复小程序cate-tab第一次切换时没反应 感谢@jiaruiyan
fix: 修复datetimepicker传入空字符串时导致组件崩溃 感谢@jiaruiyan
fix: 修复album带单位的字符串参与计算导致的计算数据错误 感谢@jiaruiyan
## 3.4.322025-05-26
feat: 增加状态栏独立颜色配置支持支付宝小程序状态栏对背景色识别的不友好的情况
fix: 抖音二维码兼容修复
feat: cate-tab组件增加rightTop插槽 #715
fix: 修改 test.promise(res) 预期结果不一致
## 3.4.312025-05-17
fix: 修复parse富文本组件导致鸿蒙运行白屏
fix: 去除演示项目中uni.$u用法便于兼容鸿蒙
feat: modal新增popupBottom插槽适用类似关闭按钮与内容区域分离的场景
## 3.4.302025-05-16
feat: 新增pagination分页器组件
feat: popup新增bottom插槽适用类似关闭按钮与内容区域分离的场景
## 3.4.292025-05-15
fix: 修复table2横向滚动样式
fix: 修复table2组件宽度兼容
fix: 修复image显示png图片时默认背景色问题
feat: cate-tab新增height参数便于设置组件高度
feat: 在index.js种导出digit.js便于使用
fix: 修复tag组件缺失iconColor属性
fix: 优化index-list的setValueForTouch方法逻辑 #708
feat: number-box支持change事件返回变动是点击了增加还是减少按钮
fix: 修复table2在小程序下部分情形不显示表格
## 3.4.282025-05-12
feat: 新增table表格组件
feat: 新增element-plus风格的table2组件
## 3.4.272025-05-06
fix: 修复card组件props
## 3.4.262025-05-06
fix: 修复test工具引入
feat: card组件支持全局设置props默认值
fix: 修复image在加载错误情况下高度和宽度不正确问题
fix: 修复picker-data快捷组件默认picker选中
fix: 修复日历month子组件缺失emits定义
## 3.4.252025-04-27
fix: up-form编译在微信小程序里样式缺失 #640
fix: number-box输入为空时自动设为最小值
feat: picker与datetimepicke组件hasInput模式支持inputProps属性
## 3.4.242025-04-25
fix: 修复upload上传逻辑(感谢@semdy)
## 3.4.232025-04-24
chore: 补全chooseFile TS类型(感谢@semdy)
feat: u-search组件的图标支持显示在右边(感谢@semdy)
chore: 修正chooseFile返回的数据TS类型(感谢@semdy)
fix: PR导致缺失name影响uplad自动上传扩展名
## 3.4.222025-04-22
fix: 修复自动上传偶发的success被覆盖为uploading
fix: float-button缺少key #677
fix: upload组件完善优化(感谢@semdy)
fix: toolbar组件confirmColor属性默认改为空以便默认使用主题色、标题字体加粗(感谢@semdy)
## 3.4.212025-04-21
feat: subsection分段器支持双向绑定current
feat: select组件支持maxHeight属性
feat: datetime-picker支持inputBorder属性
## 3.4.202025-04-17
fix: 修复navbar-mini提示border不存在
feat: status-bar支持对外暴露状态栏高度值
feat: upload支持自定义自动上传后处理逻辑便于对接不同规范后端
feat: 优化tag组件插槽
## 3.4.192025-04-14
fix: 修复model组件增加contentStyle带来的语法问题
## 3.4.182025-04-14
fix: upload组件支持所有文件类型的onClickPreview事件
## 3.4.172025-04-11
feat: select组件text插槽增加scope传递currentLabel
## 3.4.162025-04-10
fix: 修复安卓新加载字体方式导致Cannot read property '$page' of undefined
## 3.4.152025-04-10
improvment: 优化移步加载数据时swiper组件displayMultipleItems报错
feat: modal增加contentStyle属性
fix: 修复下拉菜单收起动画缺失
fix: 修复sticky的offset属性值为响应式数据时失效 #237
## 3.4.142025-04-09
feat: 支持自托管内置图标及扩展自定义图标
## 3.4.132025-04-08
fix: tabs点击当前tab触发change事件
## 3.4.122025-04-02
fix: dropdown关闭后遮挡页面内容 #653
fix u-sticky.vue Uncaught TypeError: e.querySelector is not a function at uni-app-view.umd.js
## 3.4.112025-03-31
fix: 优化upload组件预览视频的弹窗占位
## 3.4.102025-03-28
feat: select组件新增多个props属性及优化
fix: 修复cate-tab报错index is not defined #661
## 3.4.92025-03-27
fix: 修复upload组件split报错
fix: 修复float-button缺少flex样式
## 3.4.82025-03-27
fix: 修复upload组件split报错
fix: 移除mapState
## 3.4.72025-03-26
fix: 修复action-sheet-data和picker-data数据回显
fix: 优化upload组件视频封面兼容
## 3.4.62025-03-25
feat: checkbox触发change时携带name参数
feat: upload组件支持服务器本机和阿里云OSS自动上传功能及上传进度条
feat: upload组件支持视频预览及oss上传时获取视频封面图
feat: 新增up-action-sheet-data快捷组件
feat: 新增up-picker-data快捷组件
## 3.4.52025-03-24
feat: tag组件新增textSize/height/padding/borderRadius属性
feat: 新增genLightColor自动计算浅色方法及tag组件支持autoBgColor自动计算背景色
## 3.4.42025-03-13
feat: modal增加异步操作进行中点击取消弹出提示特性防止操作被中断
fix: 修复toast组件show方法类型声明
## 3.4.32025-03-12
fix: 修复textarea自动增高时在输入时高度异常
## 3.4.22025-03-11
feat: step组件增加title插槽及增加辅助class便于自定义样式
## 3.4.12025-03-11
feat: 新机制确保setConfig与http在nvue等环境下生效
## 3.3.742025-03-06
fix: CateTab语法问题
## 3.3.732025-03-06
feat: CateTab新增v-model:current属性
## 3.3.722025-02-28
feat: tabs组件支持icon图标及插槽
## 3.3.712025-02-27
feat: 折叠面板collapse增加titileStyle/iconStyle/rightIconStyle属性
feat: 折叠面板组件新增cellCustomStyle/cellCustomClass属性
fix: select组件盒模型
## 3.3.702025-02-24
fix: 修改u-checkbox-group组件changes事件发生位置
## 3.3.692025-02-19
picker允许传递禁用颜色props
slider组件isRange状态下增加min max插槽分开显示内容
feat: 新增经典下拉框组件up-select
## 3.3.682025-02-12
fix: 修复weekText类型
feat: 日历增加单选与多选指定禁止选中的日期功能
fix: NumberBox删除数字时取值有误 #613
## 3.3.672025-02-11
feat: navbar支持返回全局拦截器配置
feat: 表单-校验-支持无提示-得到校验结果
feat: picker传递hasInput属性时候可以禁用输入框点击
## 3.3.662025-02-09
feat: steps-item增加content插槽
## 3.3.652025-02-05
feat: number-box组件新增按钮圆角/按钮宽度/数据框背景色/迷你模式
## 3.3.642025-01-18
feat: 日历组件支持自定义星期文案
## 3.3.632025-01-13
fix: cate-tab支持支付宝小程序
fix: textarea 修复 placeholder-style
fix: 修复在图片加载及加载失败时容器宽度
fix: waterfall组件报错Maximum recursive updates
## 3.3.622025-01-10
feat: sleder滑动选择器双滑块增加外层触发值的变动功能
fix: picker支持hasInput优化
## 3.3.612024-12-31
fix: 修复微信getSystemInfoSync接口废弃警告
fix: 'u-status-bar' symbol missing
## 3.3.602024-12-30
feat: 日期组件支持禁用
fix: ts定义修复 #600
feat: Tabs组件选中时增加一个active的class #595
## 3.3.592024-12-30
fix: Property "isH5" was accessed during render
## 3.3.582024-12-26
fix: slider组件change事件传参
## 3.3.572024-12-23
fix: slider组件change事件传参
feat: 更新u-picker组件增加当前选中class类名
## 3.3.562024-12-18
feat: 在u-alert组件中添加关闭事件
## 3.3.552024-12-17
add: swiper增加双向绑定
## 3.3.542024-12-11
add: qrcode支持props控制是否开启点击预览
add: 新增cate-tab垂直分类组件
## 3.3.532024-12-10
fix: 修复popup居中模式点击内容区域触发关闭
## 3.3.522024-12-09
add: notice-bar支持justifyContent属性
## 3.3.512024-12-09
add: radio增加label插槽
## 3.3.502024-12-05
fix: 优化popup等对禁止背景滚动机制
add: slider在弹窗使用示例
fix: card组件类名问题
## 3.3.492024-12-02
fix: 去除album多余的$u引用
fix: 优化图片组件兼容性
add: picker组件增加zIndex属性
add: text增加是否占满剩余空间属性
add: input颜色示例
## 3.3.482024-11-29
add: 文本行数限制样式提高到10行
del: 去除不跨端的inputmode
## 3.3.472024-11-28
fix: 时间选择器在hasInput模式下部分机型键盘弹出
## 3.3.462024-11-26
fix: 修复text传递事件参数
## 3.3.452024-11-24
add: navbar组件支持配置标题颜色
fix: 边框按钮警告类型下颜色变量使用错误
## 3.3.432024-11-18
fix: 支持瀑布流组件v-model置为[]
add: 新增字符串路径访问工具方法getValueByPath
add: 新增float-button悬浮按钮组件
## 3.3.422024-11-15
add: button组件支持stop参数阻止冒泡
## 3.3.412024-11-13
fix: u-radio-group invalid import
improvement: 优化图片组件宽高及修复事件event传递
## 3.3.402024-11-11
add: 组件radioGroup增加gap属性用于设置item间隔
fix: 修复H5全局导入
## 3.3.392024-11-04
fix: 修复相册组件
## 3.3.382024-11-04
fix: 修复视频预览报错 #510
add: album组件增加stop参数支持阻止事件冒泡
## 3.3.372024-10-21
fix: 修复因为修改组件名称前缀导致h5打包后$parent方法内找不到父组件的问题
fix: 修复datetime-picker选择2000年以前日期出错
## 3.3.362024-10-09
fix: toast 自动关闭
feat: 增加微信小程序用户昵称审核完毕回调及修改 ts 定义文件
## 3.3.352024-10-08
feat: modal和picker支持v-model:show双向绑定
feat: 支持checkbox使用slot自定义label后自带点击事件 #522
feat: swipe-action支持自动关闭特性及初始化打开状态
## 3.3.342024-09-23
feat: 支持toast设置duration值为-1时不自动关闭
## 3.3.332024-09-18
fix: 修复test.date('008')等验证结果不准确
## 3.3.322024-09-09
fix: u-keyboard名称冲突warning
## 3.3.312024-08-31
feat: qrcode初步支持nvue
## 3.3.302024-08-30
fix: slider兼容step为字符串类型
## 3.3.292024-08-30
fix: 修复tabs组件current参数为字符串处理逻辑
## 3.3.282024-08-26
fix: list组件滑动偏移量不一样取绝对值导致iOS下拉偏移量计算错误
## 3.3.272024-08-22
fix: 修复up-datetime-picker组件toolbarRightSlot定义缺失
fix: 修复FormItem的rules更新错误的问题
## 3.3.262024-08-22
fix: 批量注册全局组件优化
## 3.3.252024-08-21
fix: 修复slider在app-vue下样式问题
## 3.3.242024-08-19
fix: 修复时间选择器hasInput模式小程序不生效
feat: 支持H5导入所有组件
## 3.3.232024-08-17
feat: swipe-action增加closeAll方法
fix: 兼容tabs在某些场景下index小于0时自动设置为0
add: 通用mixin新增navTo页面跳转方法
## 3.3.212024-08-15
improvement: 优化二维码组件loading及支持预览与长按事件 #351
fix: 修复swipe-action自动关闭其它功能及组件卸载自动关闭
## 3.3.202024-08-15
refactor: props默认值文件移至组件文件夹内便于查找
## 3.3.192024-08-14
fix: 修复2被rpx兼容处理只在数字值生效
add: 增加swiper自定义插槽示例
## 3.3.182024-08-13
feat: 新增支持datetime-picker工具栏插槽及picker插槽支持修复
## 3.3.172024-08-12
feat: swiper组件增加默认slot便于自定义
feat: grid新增间隔参数
feat: picker新增toolbar-right和toolbar-bottom插槽
## 3.3.162024-08-12
fix: 解决swiper中title换行后多余的内容未被遮挡问题
fix: 修复迷你导航适配异形屏
## 3.3.152024-08-09
fix: 修复默认单位设置为rpx时一些组件高度间距异常
fix: 修复日历在rpx单位下布局异常
feat: code-input支持App端展示输入光标
## 3.3.142024-08-09
add: 增加box组件
add: 增加card卡片组件
## 3.3.132024-08-08
feat: input支持调用原生组件的focus和blur方法
improvement: grid-item条件编译优化
add: 新增迷你导航组件
## 3.3.122024-08-06
improvement: $u挂载时机调整便于打包分离chunk
fix: steps新增itemStyle属性名称冲突
## 3.3.112024-08-05
feat: 新增支持upload组件的deletable/maxCount/accept变更监听 #333
feat: 新增支持tabs在swiper中使用
feat: 新增FormItem支持独立设置验证规则rules
fix: 修复index-list未设置$slots.header时索引高亮失效
## 3.3.102024-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.92024-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.82024-07-31
feat: slider支持进度条任意位置触发按钮拖动
fix: 修复app-vue下modal标题不居中
fix: #459 TS setConfig 声明异常
feat: tabs组件增加longPress长按事件
feat: 新增showRight属性控制collapse右侧图标显隐
fix: 优化nvue下css警告
## 3.3.72024-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.62024-07-23
feat: u-album组件添加radius,shape参数定义参考当前u-image参数
fix: 修复了calendar组件title和日期title未垂直居中的问题
fix: update:modelValue缺失emit定义
## 3.3.52024-07-10
picker组件支持hasInput模式
## 3.3.42024-07-07
fix: input组件双向绑定问题 #419
lazy-load完善emit
优化通用小程序分享
## 3.3.22024-06-27
fix: 在Nvue环境中编译出现大量警告 #406
## 3.3.12024-06-27
u-button组件报错找不到button mixins #407
## 3.3.02024-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.242024-06-11
fix: 修复时间选择器confirm事件触发时机导致2次才会触发v-model更新
## 3.2.232024-05-30
fix: #378 H5 u-input 在表单中初始值为空也会触发一次 formValidate(this,"change")事件导致进入页面直接校验了一次
fix: #373 搜索组件up-search的@clear事件无效
fix: #372 ActionSheet 组件的取消按钮触发区域太小
## 3.2.222024-05-13
上传组件支持微信小程序预览视频
修复折叠面板右侧箭头不显示
修复uxp2px
## 3.2.212024-05-10
fix: loading-icon修复flex布局
## 3.2.202024-05-10
修复瀑布流大小写#355
## 3.2.192024-05-10
去除意外的文件引入
## 3.2.182024-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.172024-05-08
fix: 支付宝小程序二维码渲染
## 3.2.162024-05-06
修复tabs中当前激活样式的undefined bug
fix: #341u-code 倒计时没结束前退出再次进入结束后退出界面再次进入重新开始倒计时bug
受到uni-app内置text样式影响修复
## 3.2.152024-04-28
优化时间选择器hasInput模式初始化值
## 3.2.142024-04-24
去除pleaseSetTranspileDependencies
http采用useStore
## 3.2.132024-04-22
修复modal标题样式
优化日期选择器hasInput模式宽度
## 3.2.122024-04-22
修复color应用
## 3.2.112024-04-18
修复import化带来的问题
## 3.2.102024-04-17
完善input清空事件App端失效的兼容性
修复日历组件二次打开后当前月份显示不正确
## 3.2.92024-04-16
组件内uni.$u用法改为import引入
规范化及兼容性增强
## 3.2.82024-04-15
修复up-tag语法错
## 3.2.72024-04-15
修复下拉菜单背景色在支付宝小程序无效
setConfig改为浅拷贝解决无法用import导入代替uni.$u.props设置
## 3.2.62024-04-14
修复某些情况下滑动单元格默认右侧按钮是展开的问题
## 3.2.52024-04-13
调整分段器尺寸及修复窗口大小改变时重新计算尺寸
多个组件支持cursor-pointer增强PC端体验
## 3.2.42024-04-12
初步支持typescript
## 3.2.32024-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.22024-04-11
修复换行符问题
## 3.2.12024-04-11
修复演示H5二维码
fix: #270 ReadMore 展开阅读更多内容变化兼容
fix: #238Calendar组件maxDate修改为不能小于minDate
checkbox支持独立使用
修复popup中在微信小程序中真机调试滚动失效
## 3.2.02024-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.522024-04-07
工具类方法调用import化改造
新增up-copy复制组件
## 3.1.512024-04-07
优化时间选择器自带输入框格式化显示
防止按钮文字换行
修复订单列表模板滑动
增加u-qrcode二维码组件
## 3.1.492024-03-27
日期时间组件支持自带输入框
fix: popup弹窗滚动穿透问题
fix: 修复小程序numberbox bug
## 3.1.482024-03-18
fix:[plugin:uni:pre-css] Unbalanced delimiter found in string
## 3.1.472024-03-18
fix: setConfig设置组件默认参数无效问题
fix: 修复自定义图标无效问题
feat: 增加u-form-item单独设置规则变量
fix#293小程序是自定义导航栏的时候即传了customNavHeight的时候会出现跳转偏移的情况
## 3.1.462024-01-29
beforeUnmount
## 3.1.452024-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.422024-01-15
修复u-number-box默认值0时在小程序不显示值
优化u-code的timer判断
优化支付宝小程序下textarea字数统计兼容
优化u-calendar
## 3.1.412023-11-18
#215优化u-cell图标容器间距问题
## 3.1.402023-11-16
修复u-slider双向绑定
## 3.1.392023-11-10
修复头条小程序不支持env(safe-area-inset-bottom)
优化#201u-grid 指定列数导致闪烁
#193IndexList 索引列表 高度错误
其他优化
## 3.1.382023-10-08
修复u-slider
## 3.1.372023-09-13
完善emits定义及修复code-input双向数据绑定
## 3.1.362023-08-08
修复富文本事件名称大小写
## 3.1.352023-08-02
修复编译到支付宝小程序u-form报错
## 3.1.342023-07-27
修复App打包uni.$u.mpMixin方式sdk暂时不支持导致报错
## 3.1.332023-07-13
修复弹窗进入动画、模板页面样式等
## 3.1.312023-07-11
修复dayjs引用
## 3.0.82022-07-12
修复u-tag默认宽度撑满容器
## 3.0.72022-07-12
修复u-navbar自定义插槽演示示例
## 3.0.62022-07-11
修复u-image缺少emits申明
## 3.0.52022-07-11
修复u-upload缺少emits申明
## 3.0.42022-07-10
修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定
## 3.0.32022-07-09
启用自建演示二维码
## 3.0.22022-07-09
修复dayjs/clipboard等导致打包报错
## 3.0.12022-07-09
增加插件市场地址
## 3.0.02022-07-09
# uview-plus(vue3)初步发布

View File

@ -0,0 +1,85 @@
<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)
},
/**
* 校验全部数据
* @param {Object} options
* @param {Boolean} options.showErrorMsg -是否显示校验信息
*/
validate(options) {
/**
* 在微信小程序中通过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(options)
},
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>

View 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>

View 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>

View 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>

View File

@ -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>

View File

@ -0,0 +1,109 @@
<template>
<view class="u-action-sheet-data">
<view class="u-action-sheet-data__trigger">
<slot name="trigger"></slot>
<up-input
v-if="!$slots['trigger']"
:modelValue="current"
disabled
disabledColor="#ffffff"
:placeholder="title"
border="none"
></up-input>
<view @click="show = true"
class="u-action-sheet-data__trigger__cover"></view>
</view>
<up-action-sheet
:show="show"
:actions="options"
:title="title"
safeAreaInsetBottom
:description="description"
@close="show = false"
@select="select"
>
</up-action-sheet>
</view>
</template>
<script>
export default {
props: {
modelValue: {
type: [String, Number],
default: ''
},
title: {
type: String,
default: ''
},
description: {
type: String,
default: ''
},
options: {
type: Array,
default: () => {
return []
}
},
valueKey: {
type: String,
default: 'value'
},
labelKey: {
type: String,
default: 'name'
}
},
data() {
return {
show: false,
current: '',
}
},
created() {
if (this.modelValue) {
this.options.forEach((ele) => {
if (ele[this.valueKey] == this.modelValue) {
this.current = ele[this.labelKey]
}
})
}
},
emits: ['update:modelValue'],
watch: {
modelValue() {
this.options.forEach((ele) => {
if (ele[this.valueKey] == this.modelValue) {
this.current = ele[this.labelKey]
}
})
}
},
methods: {
hideKeyboard() {
uni.hideKeyboard()
},
select(e) {
this.$emit('update:modelValue', e[this.valueKey])
this.current = e[this.labelKey]
},
}
}
</script>
<style lang="scss" scoped>
.u-action-sheet-data {
&__trigger {
position: relative;
&__cover {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
}
</style>

View File

@ -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'
}
}

View 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
},
}
})

View File

@ -0,0 +1,282 @@
<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",
// propsmethodsmixin
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>
$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>

View 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,
}
}

View 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
}
}
})

View File

@ -0,0 +1,300 @@
<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="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
// weexKPIdom
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
)
},
// computedurls
//
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
}
},
emits: ['preview', 'albumWidth'],
methods: {
addUnit,
//
onPreviewTap(e, url) {
const urls = this.urls.map((item) => {
return this.getSrc(item)
})
if (this.previewFullImage) {
uni.previewImage({
current: url,
urls
})
//
this.stop && this.preventEvent(e)
} else {
this.$emit('preview', {
urls,
currentIndex: urls.indexOf(url)
})
}
},
//
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) => {
let singleSize = this.singleSize;
//
let unit = '';
if (Number.isNaN(Number(this.singleSize))) {
//
unit = this.singleSize.replace(/\d+/g, ''); //
singleSize = Number(this.singleSize.replace(/\D+/g, ''), 10); //
}
//
const isHorizotal = res.width >= res.height
this.singleWidth = isHorizotal
? singleSize
: (res.width / res.height) * singleSize
this.singleHeight = !isHorizotal
? singleSize
: (res.height / res.width) * this.singleWidth
//
if(unit != null && unit !== ''){
this.singleWidth = this.singleWidth + unit
this.singleHeight = this.singleHeight + unit
}
},
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"forthis.$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>
.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>

Some files were not shown because too many files have changed in this diff Show More