22
.editorconfig
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 1.1,
|
||||
"atDirectives": [
|
||||
{
|
||||
"name": "@apply"
|
||||
},
|
||||
{
|
||||
"name": "@screen"
|
||||
}
|
||||
]
|
||||
}
|
48
.vscode/vue3.code-snippets
vendored
Normal 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
@ -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
@ -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
@ -1,3 +1,327 @@
|
||||
# caipu_nui
|
||||
# uniapp 团队协作开发实践模板(Vue3)
|
||||
|
||||
菜谱小程序
|
||||
[](https://github.com/oyjt/uniapp-vue3-template)
|
||||
[](https://github.com/oyjt/uniapp-vue3-template)
|
||||
[](https://github.com/oyjt/uniapp-vue3-template)
|
||||
[](https://github.com/oyjt/uniapp-vue3-template)
|
||||
[](https://github.com/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
@ -0,0 +1 @@
|
||||
export * from './proxy';
|
17
build/config/proxy.ts
Normal 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;
|
||||
};
|
13
build/plugins/autoImport.ts
Normal 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,
|
||||
});
|
||||
};
|
12
build/plugins/cleanImage.ts
Normal 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/**'],
|
||||
});
|
||||
};
|
11
build/plugins/component.ts
Normal 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
@ -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;
|
||||
}
|
13
build/plugins/replaceUrl.ts
Normal 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
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @name ConfigUnoCSSPlugin
|
||||
* @description UnoCSS相关配置
|
||||
*/
|
||||
import UnoCSS from 'unocss/vite';
|
||||
|
||||
export const ConfigUnoCSSPlugin = () => {
|
||||
return UnoCSS();
|
||||
};
|
13
build/plugins/visualizer.ts
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
36
scripts/post-upgrade.js
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
214
src/components/agree-privacy/index.vue
Normal 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 必填项不填写时授权按钮id必须为agree-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>
|
51
src/components/lang-select/index.vue
Normal 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
@ -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 };
|
33
src/hooks/use-clipboard/index.ts
Normal 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,
|
||||
};
|
||||
}
|
24
src/hooks/use-loading/index.ts
Normal 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,
|
||||
};
|
||||
}
|
329
src/hooks/use-location/index.ts
Normal 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,
|
||||
};
|
||||
}
|
30
src/hooks/use-location/types.ts
Normal 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;
|
||||
}
|
24
src/hooks/use-modal/index.ts
Normal 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,
|
||||
};
|
||||
}
|
10
src/hooks/use-permission/index.ts
Normal 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());
|
||||
}
|
48
src/hooks/use-share/index.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { ShareOptions } from './types';
|
||||
|
||||
/**
|
||||
* 小程序分享
|
||||
* @param {object} options
|
||||
* @example
|
||||
* // 必须要调用onShareAppMessage,onShareTimeline才能正常分享
|
||||
* // 因为小程序平台,必须在注册页面时,主动配置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
|
||||
}
|
6
src/hooks/use-share/types.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface ShareOptions {
|
||||
title?: string;
|
||||
path?: string;
|
||||
query?: string;
|
||||
imageUrl?: string;
|
||||
}
|
21
src/locales/index.ts
Normal 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
@ -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',
|
||||
},
|
||||
};
|
11
src/locales/langs/zh-Hans.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export default {
|
||||
locale: {
|
||||
'auto': '系统',
|
||||
'en': '英语',
|
||||
'zh-hans': '中文',
|
||||
},
|
||||
home: {
|
||||
'intro': '欢迎来到uni-app演示',
|
||||
'toggle-langs': '切换语言',
|
||||
},
|
||||
};
|
14
src/main.ts
Normal 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
@ -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
@ -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"
|
||||
}
|
||||
}
|
35
src/pages/common/404/index.vue
Normal 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>
|
240
src/pages/common/goods/address.vue
Normal 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 CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | 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 */
|
||||
// 由于uView是基于nvue环境进行开发的,此环境中普通元素默认为flex-direction: column;
|
||||
// 所以在非nvue中,需要对元素进行重置为flex-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>
|
183
src/pages/common/goods/detail.vue
Normal 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 应该是一个视频或图片的URL,但这里却给了一个图片URL
|
||||
// 如果这是一个视频对象,你需要确保 url 是正确的视频文件URL
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.uviewui.com/uview/swiper/swiper3.png',
|
||||
title: '谁念西风独自凉,萧萧黄叶闭疏窗,沉思往事立残阳',
|
||||
// 同样,这里看起来 url 应该是一个视频或图片的URL,但给的是一个图片URL
|
||||
// 需要根据实际需求修正这个值
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
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>
|
507
src/pages/common/goods/order copy.vue
Normal 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);
|
||||
// // 这里的pageNo和pageSize会自动计算好,直接传给服务器即可
|
||||
// // 这里的请求只是演示,请替换成自己的项目的网络请求,并在网络请求回调中通过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 => {
|
||||
// // // 请勿在网络请求回调中给dataList赋值!!只需要调用complete就可以了
|
||||
// // pagingRef.value.complete(res.data.list);
|
||||
// // })
|
||||
// // .catch(res => {
|
||||
// // // 如果请求失败写pagingRef.value.complete(false),会自动展示错误页面
|
||||
// // // 注意,每次都需要在catch中写这句话很麻烦,z-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;
|
||||
// // 这里的pageNo和pageSize会自动计算好,直接传给服务器即可
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
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>
|
309
src/pages/common/goods/order.vue
Normal 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>
|
180
src/pages/common/login/index.vue
Normal 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>
|
12
src/pages/common/webview/index.vue
Normal 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>
|
835
src/pages/tab/home/index.vue
Normal 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
83
src/pages/tab/user/index.vue
Normal 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);
|
||||
}
|
||||
// 登录鉴权,微信小程序端点击tabbar的底层逻辑不触发uni.switchTab,需要在页面onShow生命周期中校验权限
|
||||
onShow(async () => {
|
||||
const hasPermission = await usePermission();
|
||||
console.log(hasPermission ? '已登录' : '未登录,拦截跳转');
|
||||
});
|
||||
</script>
|
21
src/plugins/index.ts
Normal 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
@ -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
@ -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
@ -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
After Width: | Height: | Size: 8.3 KiB |
BIN
src/static/images/logo.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
src/static/images/pay.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
src/static/images/tabbar/icon_home.png
Normal file
After Width: | Height: | Size: 693 B |
BIN
src/static/images/tabbar/icon_home_selected.png
Normal file
After Width: | Height: | Size: 689 B |
BIN
src/static/images/tabbar/icon_list.png
Normal file
After Width: | Height: | Size: 604 B |
BIN
src/static/images/tabbar/icon_list_selected.png
Normal file
After Width: | Height: | Size: 600 B |
BIN
src/static/images/tabbar/icon_me.png
Normal file
After Width: | Height: | Size: 909 B |
BIN
src/static/images/tabbar/icon_me_selected.png
Normal file
After Width: | Height: | Size: 897 B |
160
src/static/styles/common.scss
Normal 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
@ -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;
|
58
src/store/modules/app/index.ts
Normal 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;
|
3
src/store/modules/app/types.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface AppState {
|
||||
systemInfo: UniApp.GetSystemInfoResult;
|
||||
}
|
85
src/store/modules/user/index.ts
Normal 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;
|
16
src/store/modules/user/types.ts
Normal 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
@ -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;
|
21
src/uni_modules/uview-plus/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 https://uiadmin.net/uview-plus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
74
src/uni_modules/uview-plus/README.md
Normal file
@ -0,0 +1,74 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://uiadmin.net/uview-plus/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
|
||||
</p>
|
||||
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uview-plus 3.0</h3>
|
||||
<h3 align="center">多平台快速开发的UI框架</h3>
|
||||
|
||||
[](https://github.com/ijry/uview-plus)
|
||||
[](https://github.com/ijry/uview-plus)
|
||||
[](https://github.com/ijry/uview-plus/issues)
|
||||
[](https://gitee.com/jry/uview-plus/releases)
|
||||
[](https://en.wikipedia.org/wiki/MIT_License)
|
||||
|
||||
## 说明
|
||||
|
||||
uview-plus,是uni-app全面兼容vue3/nvue/鸿蒙/uni-app-x(即将发布)的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水。uview-plus是基于uView2.x移植的支持vue3的版本,感谢uView。
|
||||
|
||||
## 可视化设计
|
||||
|
||||
uview-plus现已推出免费可视化设计,可以方便的进行页面可视化设计,导出源码即可使用。极大提高前端页面开发效率;如产品经理设计师直接使用更可作为高保真高可用原型制作工具,让设计稿即代码,无需传统的设计稿开发还原步骤。
|
||||
|
||||
<img src="https://s3.bmp.ovh/imgs/2024/11/24/fd58d00071e6e5df.png" width="900" height="auto" >
|
||||
<img src="https://s3.bmp.ovh/imgs/2024/11/24/8e85a519fe627fb1.png" width="900" height="auto" >
|
||||
|
||||
|
||||
## 文档
|
||||
[官方文档:https://uview-plus.jiangruyi.com](https://uview-plus.jiangruyi.com)
|
||||
[备用文档:https://uiadmin.net/uview-plus](https://uiadmin.net/uview-plus)
|
||||
|
||||
|
||||
## 预览
|
||||
|
||||
您可以通过**微信**扫码,查看最佳的演示效果。
|
||||
<br>
|
||||
<br>
|
||||
<img src="https://uview-plus.jiangruyi.com/common/h5_qrcode.png" width="220" height="220" >
|
||||
|
||||
## 链接
|
||||
|
||||
- [官方文档](https://uview-plus.jiangruyi.com)
|
||||
- [更新日志](https://uview-plus.jiangruyi.com/components/changelog.html)
|
||||
- [升级指南](https://uview-plus.jiangruyi.com/components/changeGuide.html)
|
||||
- [关于我们](https://uview-plus.jiangruyi.com/cooperation/about.html)
|
||||
|
||||
## 交流反馈
|
||||
|
||||
欢迎加入我们的QQ群交流反馈:[点此跳转](https://uview-plus.jiangruyi.com/components/addQQGroup.html)
|
||||
|
||||
## 关于PR
|
||||
|
||||
> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uview-plus是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
|
||||
> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
|
||||
|
||||
## 安装
|
||||
|
||||
#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?name=uview-plus](https://ext.dcloud.net.cn/plugin?name=uview-plus)
|
||||
|
||||
请通过[官网安装文档](https://uview-plus.jiangruyi.com/components/install.html)了解更详细的内容
|
||||
|
||||
## 快速上手
|
||||
|
||||
请通过[快速上手](https://uview-plus.jiangruyi.com/components/quickstart.html)了解更详细的内容
|
||||
|
||||
## 使用方法
|
||||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
|
||||
|
||||
```html
|
||||
<template>
|
||||
<u-button text="按钮"></u-button>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 版权信息
|
||||
uview-plus遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uview-plus应用到您的产品中。
|
||||
|
921
src/uni_modules/uview-plus/changelog.md
Normal file
@ -0,0 +1,921 @@
|
||||
## 3.4.52(2025-07-16)
|
||||
fix: 修复底部安全区域组件兼容性
|
||||
|
||||
## 3.4.51(2025-07-14)
|
||||
fix: 修复u-slider在click后没有触发change事件
|
||||
|
||||
## 3.4.50(2025-07-13)
|
||||
feat: subsection分段器添加禁用参数
|
||||
|
||||
## 3.4.49(2025-07-11)
|
||||
feat: picker支持bgColor、round、duration和overlayOpacity属性
|
||||
|
||||
## 3.4.48(2025-07-10)
|
||||
fix: 官方文档Card示例组件多行显示省略号样式异常
|
||||
|
||||
feat: album组件支持自定义preview事件
|
||||
|
||||
## 3.4.47(2025-07-09)
|
||||
fix: 修复datetime-picker打开时,数值可能出现对不上的问题
|
||||
|
||||
feat: Subsection 分段器添加支持从 list中读取激活文字颜色和未激活文字颜色
|
||||
|
||||
fix: 修复modal定义confirmButton
|
||||
|
||||
fix: safe-bottom底部安全距离在小程序优先用JS计算
|
||||
|
||||
feat: 新增tree树形组件
|
||||
|
||||
## 3.4.46(2025-07-08)
|
||||
feat: 上传组件预览视频支持videoPreviewObjectFit参数
|
||||
|
||||
feat: td增加多个样式props
|
||||
|
||||
fix: 修复noticeBar字号增大时文字遮挡
|
||||
|
||||
feat: 项目工程增加pinia
|
||||
|
||||
## 3.4.45(2025-07-01)
|
||||
fix: 修复picker-data组件缺少name
|
||||
|
||||
fix: 优化picker高度单位
|
||||
|
||||
## 3.4.44(2025-06-30)
|
||||
fix: 修复indexList中stikcy属性写死的问题(always true)
|
||||
|
||||
feat: 搜索框添加新的右侧插槽
|
||||
|
||||
fix: 修复indexList中丢失的select event
|
||||
|
||||
fix: 解决因为层级问题导致点击picker选择器无法正常弹出
|
||||
|
||||
## 3.4.43(2025-06-16)
|
||||
feat: table2支持header插槽
|
||||
|
||||
## 3.4.42(2025-06-12)
|
||||
fix: 修复qrcode中默认id问题及canvas2时App无法绘制二维码 感谢@jiaruiyan
|
||||
|
||||
## 3.4.41(2025-06-11)
|
||||
feat: qrcode支持 新参数useRootHeightAndWidth 是否使用根节点的宽高 感谢@YJR
|
||||
|
||||
feat: toast支持设置zIndex层级
|
||||
|
||||
|
||||
|
||||
## 3.4.40(2025-06-06)
|
||||
fix: 升级二维码 canvas -> canvas2 感谢@yjr
|
||||
|
||||
## 3.4.39(2025-05-31)
|
||||
fix: 修改步骤条微信小程序下的布局 感谢@jiaruiyan
|
||||
|
||||
fix: u-tabs在屏幕尺寸发生变化时滑块位置没有发生变化 感谢@aqzhft
|
||||
|
||||
fix: 鸿蒙平台不支持plus.runtime.openWeb 感谢@aqzhft
|
||||
|
||||
## 3.4.38(2025-05-30)
|
||||
fix: 修复picker-data快捷组件缺少index
|
||||
|
||||
fix: 修复picker组件双向绑定初始化及取消后复原再次打开后的当前项目
|
||||
|
||||
## 3.4.37(2025-05-29)
|
||||
feat: modal支持设置动画时间
|
||||
|
||||
fix: DatetimePicker v-model 绑定异步设置无效 (#803)
|
||||
|
||||
## 3.4.36(2025-05-28)
|
||||
fix: lazy-load图片为空时显示错误
|
||||
|
||||
## 3.4.35(2025-05-28)
|
||||
feat: 进度条支持从右往左加载
|
||||
|
||||
## 3.4.34(2025-05-28)
|
||||
feat: table2支持自定义标题和单元格样式
|
||||
|
||||
## 3.4.33(2025-05-27)
|
||||
fix: 修复小程序cate-tab第一次切换时没反应 感谢@jiaruiyan
|
||||
|
||||
fix: 修复datetimepicker传入空字符串时导致组件崩溃 感谢@jiaruiyan
|
||||
|
||||
fix: 修复album带单位的字符串参与计算导致的计算数据错误 感谢@jiaruiyan
|
||||
|
||||
## 3.4.32(2025-05-26)
|
||||
feat: 增加状态栏独立颜色配置支持支付宝小程序状态栏对背景色识别的不友好的情况
|
||||
|
||||
fix: 抖音二维码兼容修复
|
||||
|
||||
feat: cate-tab组件增加rightTop插槽 #715
|
||||
|
||||
fix: 修改 test.promise(res) 预期结果不一致
|
||||
|
||||
## 3.4.31(2025-05-17)
|
||||
fix: 修复parse富文本组件导致鸿蒙运行白屏
|
||||
|
||||
fix: 去除演示项目中uni.$u用法便于兼容鸿蒙
|
||||
|
||||
feat: modal新增popupBottom插槽适用类似关闭按钮与内容区域分离的场景
|
||||
|
||||
## 3.4.30(2025-05-16)
|
||||
feat: 新增pagination分页器组件
|
||||
|
||||
feat: popup新增bottom插槽适用类似关闭按钮与内容区域分离的场景
|
||||
|
||||
## 3.4.29(2025-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.28(2025-05-12)
|
||||
feat: 新增table表格组件
|
||||
|
||||
feat: 新增element-plus风格的table2组件
|
||||
|
||||
## 3.4.27(2025-05-06)
|
||||
fix: 修复card组件props
|
||||
|
||||
## 3.4.26(2025-05-06)
|
||||
fix: 修复test工具引入
|
||||
|
||||
feat: card组件支持全局设置props默认值
|
||||
|
||||
fix: 修复image在加载错误情况下高度和宽度不正确问题
|
||||
|
||||
fix: 修复picker-data快捷组件默认picker选中
|
||||
|
||||
fix: 修复日历month子组件缺失emits定义
|
||||
|
||||
## 3.4.25(2025-04-27)
|
||||
fix: up-form编译在微信小程序里样式缺失 #640
|
||||
|
||||
fix: number-box输入为空时自动设为最小值
|
||||
|
||||
feat: picker与datetimepicke组件hasInput模式支持inputProps属性
|
||||
|
||||
## 3.4.24(2025-04-25)
|
||||
fix: 修复upload上传逻辑(感谢@semdy)
|
||||
|
||||
## 3.4.23(2025-04-24)
|
||||
chore: 补全chooseFile TS类型(感谢@semdy)
|
||||
|
||||
feat: u-search组件的图标支持显示在右边(感谢@semdy)
|
||||
|
||||
chore: 修正chooseFile返回的数据TS类型(感谢@semdy)
|
||||
|
||||
fix: PR导致缺失name影响uplad自动上传扩展名
|
||||
|
||||
|
||||
## 3.4.22(2025-04-22)
|
||||
fix: 修复自动上传偶发的success被覆盖为uploading
|
||||
|
||||
fix: float-button缺少key #677
|
||||
|
||||
fix: upload组件完善优化(感谢@semdy)
|
||||
|
||||
fix: toolbar组件confirmColor属性默认改为空,以便默认使用主题色、标题字体加粗(感谢@semdy)
|
||||
|
||||
## 3.4.21(2025-04-21)
|
||||
feat: subsection分段器支持双向绑定current
|
||||
|
||||
feat: select组件支持maxHeight属性
|
||||
|
||||
feat: datetime-picker支持inputBorder属性
|
||||
|
||||
## 3.4.20(2025-04-17)
|
||||
fix: 修复navbar-mini提示border不存在
|
||||
|
||||
feat: status-bar支持对外暴露状态栏高度值
|
||||
|
||||
feat: upload支持自定义自动上传后处理逻辑便于对接不同规范后端
|
||||
|
||||
feat: 优化tag组件插槽
|
||||
|
||||
|
||||
## 3.4.19(2025-04-14)
|
||||
fix: 修复model组件增加contentStyle带来的语法问题
|
||||
|
||||
## 3.4.18(2025-04-14)
|
||||
fix: upload组件支持所有文件类型的onClickPreview事件
|
||||
|
||||
## 3.4.17(2025-04-11)
|
||||
feat: select组件text插槽增加scope传递currentLabel
|
||||
|
||||
## 3.4.16(2025-04-10)
|
||||
fix: 修复安卓新加载字体方式导致Cannot read property '$page' of undefined
|
||||
|
||||
## 3.4.15(2025-04-10)
|
||||
improvment: 优化移步加载数据时swiper组件displayMultipleItems报错
|
||||
|
||||
feat: modal增加contentStyle属性
|
||||
|
||||
fix: 修复下拉菜单收起动画缺失
|
||||
|
||||
fix: 修复sticky的offset属性值为响应式数据时失效 #237
|
||||
|
||||
|
||||
## 3.4.14(2025-04-09)
|
||||
feat: 支持自托管内置图标及扩展自定义图标
|
||||
|
||||
## 3.4.13(2025-04-08)
|
||||
fix: tabs点击当前tab触发change事件
|
||||
|
||||
## 3.4.12(2025-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.11(2025-03-31)
|
||||
fix: 优化upload组件预览视频的弹窗占位
|
||||
|
||||
## 3.4.10(2025-03-28)
|
||||
feat: select组件新增多个props属性及优化
|
||||
|
||||
fix: 修复cate-tab报错index is not defined #661
|
||||
|
||||
|
||||
## 3.4.9(2025-03-27)
|
||||
fix: 修复upload组件split报错
|
||||
|
||||
fix: 修复float-button缺少flex样式
|
||||
|
||||
## 3.4.8(2025-03-27)
|
||||
fix: 修复upload组件split报错
|
||||
|
||||
fix: 移除mapState
|
||||
|
||||
## 3.4.7(2025-03-26)
|
||||
fix: 修复action-sheet-data和picker-data数据回显
|
||||
|
||||
fix: 优化upload组件视频封面兼容
|
||||
|
||||
## 3.4.6(2025-03-25)
|
||||
feat: checkbox触发change时携带name参数
|
||||
|
||||
feat: upload组件支持服务器本机和阿里云OSS自动上传功能及上传进度条
|
||||
|
||||
feat: upload组件支持视频预览及oss上传时获取视频封面图
|
||||
|
||||
feat: 新增up-action-sheet-data快捷组件
|
||||
|
||||
feat: 新增up-picker-data快捷组件
|
||||
|
||||
## 3.4.5(2025-03-24)
|
||||
feat: tag组件新增textSize/height/padding/borderRadius属性
|
||||
|
||||
feat: 新增genLightColor自动计算浅色方法及tag组件支持autoBgColor自动计算背景色
|
||||
|
||||
## 3.4.4(2025-03-13)
|
||||
feat: modal增加异步操作进行中点击取消弹出提示特性防止操作被中断
|
||||
|
||||
fix: 修复toast组件show方法类型声明
|
||||
|
||||
## 3.4.3(2025-03-12)
|
||||
fix: 修复textarea自动增高时在输入时高度异常
|
||||
|
||||
## 3.4.2(2025-03-11)
|
||||
feat: step组件增加title插槽及增加辅助class便于自定义样式
|
||||
|
||||
## 3.4.1(2025-03-11)
|
||||
feat: 新机制确保setConfig与http在nvue等环境下生效
|
||||
|
||||
## 3.3.74(2025-03-06)
|
||||
fix: CateTab语法问题
|
||||
|
||||
## 3.3.73(2025-03-06)
|
||||
feat: CateTab新增v-model:current属性
|
||||
|
||||
## 3.3.72(2025-02-28)
|
||||
feat: tabs组件支持icon图标及插槽
|
||||
|
||||
## 3.3.71(2025-02-27)
|
||||
feat: 折叠面板collapse增加titileStyle/iconStyle/rightIconStyle属性
|
||||
|
||||
feat: 折叠面板组件新增cellCustomStyle/cellCustomClass属性
|
||||
|
||||
fix: select组件盒模型
|
||||
|
||||
## 3.3.70(2025-02-24)
|
||||
fix: 修改u-checkbox-group组件changes事件发生位置
|
||||
|
||||
## 3.3.69(2025-02-19)
|
||||
picker允许传递禁用颜色props
|
||||
|
||||
slider组件isRange状态下增加min max插槽分开显示内容
|
||||
|
||||
feat: 新增经典下拉框组件up-select
|
||||
|
||||
## 3.3.68(2025-02-12)
|
||||
fix: 修复weekText类型
|
||||
|
||||
feat: 日历增加单选与多选指定禁止选中的日期功能
|
||||
|
||||
fix: NumberBox删除数字时取值有误 #613
|
||||
|
||||
## 3.3.67(2025-02-11)
|
||||
feat: navbar支持返回全局拦截器配置
|
||||
|
||||
feat: 表单-校验-支持无提示-得到校验结果
|
||||
|
||||
feat: picker传递hasInput属性时候,可以禁用输入框点击
|
||||
|
||||
## 3.3.66(2025-02-09)
|
||||
feat: steps-item增加content插槽
|
||||
|
||||
## 3.3.65(2025-02-05)
|
||||
feat: number-box组件新增按钮圆角/按钮宽度/数据框背景色/迷你模式
|
||||
## 3.3.64(2025-01-18)
|
||||
feat: 日历组件支持自定义星期文案
|
||||
|
||||
## 3.3.63(2025-01-13)
|
||||
fix: cate-tab支持支付宝小程序
|
||||
|
||||
fix: textarea 修复 placeholder-style
|
||||
|
||||
fix: 修复在图片加载及加载失败时容器宽度
|
||||
|
||||
fix: waterfall组件报错Maximum recursive updates
|
||||
|
||||
## 3.3.62(2025-01-10)
|
||||
feat: sleder滑动选择器双滑块增加外层触发值的变动功能
|
||||
|
||||
fix: picker支持hasInput优化
|
||||
|
||||
## 3.3.61(2024-12-31)
|
||||
fix: 修复微信getSystemInfoSync接口废弃警告
|
||||
|
||||
fix: 'u-status-bar' symbol missing
|
||||
|
||||
## 3.3.60(2024-12-30)
|
||||
feat: 日期组件支持禁用
|
||||
|
||||
fix: ts定义修复 #600
|
||||
|
||||
feat: Tabs组件选中时增加一个active的class #595
|
||||
|
||||
## 3.3.59(2024-12-30)
|
||||
fix: Property "isH5" was accessed during render
|
||||
|
||||
## 3.3.58(2024-12-26)
|
||||
fix: slider组件change事件传参
|
||||
|
||||
## 3.3.57(2024-12-23)
|
||||
fix: slider组件change事件传参
|
||||
|
||||
feat: 更新u-picker组件增加当前选中class类名
|
||||
|
||||
## 3.3.56(2024-12-18)
|
||||
feat: 在u-alert组件中添加关闭事件
|
||||
|
||||
## 3.3.55(2024-12-17)
|
||||
add: swiper增加双向绑定
|
||||
|
||||
## 3.3.54(2024-12-11)
|
||||
add: qrcode支持props控制是否开启点击预览
|
||||
|
||||
add: 新增cate-tab垂直分类组件
|
||||
|
||||
## 3.3.53(2024-12-10)
|
||||
fix: 修复popup居中模式点击内容区域触发关闭
|
||||
|
||||
## 3.3.52(2024-12-09)
|
||||
add: notice-bar支持justifyContent属性
|
||||
|
||||
## 3.3.51(2024-12-09)
|
||||
add: radio增加label插槽
|
||||
|
||||
## 3.3.50(2024-12-05)
|
||||
fix: 优化popup等对禁止背景滚动机制
|
||||
|
||||
add: slider在弹窗使用示例
|
||||
|
||||
fix: card组件类名问题
|
||||
|
||||
## 3.3.49(2024-12-02)
|
||||
fix: 去除album多余的$u引用
|
||||
|
||||
fix: 优化图片组件兼容性
|
||||
|
||||
add: picker组件增加zIndex属性
|
||||
|
||||
add: text增加是否占满剩余空间属性
|
||||
|
||||
add: input颜色示例
|
||||
|
||||
## 3.3.48(2024-11-29)
|
||||
add: 文本行数限制样式提高到10行
|
||||
|
||||
del: 去除不跨端的inputmode
|
||||
## 3.3.47(2024-11-28)
|
||||
fix: 时间选择器在hasInput模式下部分机型键盘弹出
|
||||
|
||||
## 3.3.46(2024-11-26)
|
||||
fix: 修复text传递事件参数
|
||||
|
||||
## 3.3.45(2024-11-24)
|
||||
add: navbar组件支持配置标题颜色
|
||||
|
||||
fix: 边框按钮警告类型下颜色变量使用错误
|
||||
|
||||
## 3.3.43(2024-11-18)
|
||||
fix: 支持瀑布流组件v-model置为[]
|
||||
|
||||
add: 新增字符串路径访问工具方法getValueByPath
|
||||
|
||||
add: 新增float-button悬浮按钮组件
|
||||
|
||||
## 3.3.42(2024-11-15)
|
||||
add: button组件支持stop参数阻止冒泡
|
||||
|
||||
## 3.3.41(2024-11-13)
|
||||
fix: u-radio-group invalid import
|
||||
|
||||
improvement: 优化图片组件宽高及修复事件event传递
|
||||
|
||||
## 3.3.40(2024-11-11)
|
||||
add: 组件radioGroup增加gap属性用于设置item间隔
|
||||
|
||||
fix: 修复H5全局导入
|
||||
|
||||
## 3.3.39(2024-11-04)
|
||||
fix: 修复相册组件
|
||||
|
||||
## 3.3.38(2024-11-04)
|
||||
fix: 修复视频预览报错 #510
|
||||
|
||||
add: album组件增加stop参数支持阻止事件冒泡
|
||||
|
||||
## 3.3.37(2024-10-21)
|
||||
fix: 修复因为修改组件名称前缀,导致h5打包后$parent方法内找不到父组件的问题
|
||||
|
||||
fix: 修复datetime-picker选择2000年以前日期出错
|
||||
|
||||
## 3.3.36(2024-10-09)
|
||||
fix: toast 自动关闭
|
||||
|
||||
feat: 增加微信小程序用户昵称审核完毕回调及修改 ts 定义文件
|
||||
|
||||
## 3.3.35(2024-10-08)
|
||||
feat: modal和picker支持v-model:show双向绑定
|
||||
|
||||
feat: 支持checkbox使用slot自定义label后自带点击事件 #522
|
||||
|
||||
feat: swipe-action支持自动关闭特性及初始化打开状态
|
||||
|
||||
## 3.3.34(2024-09-23)
|
||||
feat: 支持toast设置duration值为-1时不自动关闭
|
||||
|
||||
## 3.3.33(2024-09-18)
|
||||
fix: 修复test.date('008')等验证结果不准确
|
||||
|
||||
## 3.3.32(2024-09-09)
|
||||
fix: u-keyboard名称冲突warning
|
||||
|
||||
## 3.3.31(2024-08-31)
|
||||
feat: qrcode初步支持nvue
|
||||
|
||||
## 3.3.30(2024-08-30)
|
||||
fix: slider兼容step为字符串类型
|
||||
|
||||
## 3.3.29(2024-08-30)
|
||||
fix: 修复tabs组件current参数为字符串处理逻辑
|
||||
|
||||
## 3.3.28(2024-08-26)
|
||||
fix: list组件滑动偏移量不一样取绝对值导致iOS下拉偏移量计算错误
|
||||
|
||||
## 3.3.27(2024-08-22)
|
||||
fix: 修复up-datetime-picker组件toolbarRightSlot定义缺失
|
||||
|
||||
fix: 修复FormItem的rules更新错误的问题
|
||||
|
||||
## 3.3.26(2024-08-22)
|
||||
fix: 批量注册全局组件优化
|
||||
|
||||
## 3.3.25(2024-08-21)
|
||||
fix: 修复slider在app-vue下样式问题
|
||||
|
||||
## 3.3.24(2024-08-19)
|
||||
fix: 修复时间选择器hasInput模式小程序不生效
|
||||
|
||||
feat: 支持H5导入所有组件
|
||||
|
||||
## 3.3.23(2024-08-17)
|
||||
feat: swipe-action增加closeAll方法
|
||||
|
||||
fix: 兼容tabs在某些场景下index小于0时自动设置为0
|
||||
|
||||
add: 通用mixin新增navTo页面跳转方法
|
||||
|
||||
## 3.3.21(2024-08-15)
|
||||
improvement: 优化二维码组件loading及支持预览与长按事件 #351
|
||||
|
||||
fix: 修复swipe-action自动关闭其它功能及组件卸载自动关闭
|
||||
|
||||
## 3.3.20(2024-08-15)
|
||||
refactor: props默认值文件移至组件文件夹内便于查找
|
||||
## 3.3.19(2024-08-14)
|
||||
fix: 修复2被rpx兼容处理只在数字值生效
|
||||
|
||||
add: 增加swiper自定义插槽示例
|
||||
|
||||
## 3.3.18(2024-08-13)
|
||||
feat: 新增支持datetime-picker工具栏插槽及picker插槽支持修复
|
||||
## 3.3.17(2024-08-12)
|
||||
feat: swiper组件增加默认slot便于自定义
|
||||
|
||||
feat: grid新增间隔参数
|
||||
|
||||
feat: picker新增toolbar-right和toolbar-bottom插槽
|
||||
|
||||
## 3.3.16(2024-08-12)
|
||||
fix: 解决swiper中title换行后多余的内容未被遮挡问题
|
||||
|
||||
fix: 修复迷你导航适配异形屏
|
||||
|
||||
## 3.3.15(2024-08-09)
|
||||
fix: 修复默认单位设置为rpx时一些组件高度间距异常
|
||||
|
||||
fix: 修复日历在rpx单位下布局异常
|
||||
|
||||
feat: code-input支持App端展示输入光标
|
||||
|
||||
## 3.3.14(2024-08-09)
|
||||
add: 增加box组件
|
||||
|
||||
add: 增加card卡片组件
|
||||
|
||||
|
||||
## 3.3.13(2024-08-08)
|
||||
feat: input支持调用原生组件的focus和blur方法
|
||||
|
||||
improvement: grid-item条件编译优化
|
||||
|
||||
add: 新增迷你导航组件
|
||||
|
||||
## 3.3.12(2024-08-06)
|
||||
improvement: $u挂载时机调整便于打包分离chunk
|
||||
|
||||
fix: steps新增itemStyle属性名称冲突
|
||||
|
||||
## 3.3.11(2024-08-05)
|
||||
feat: 新增支持upload组件的deletable/maxCount/accept变更监听 #333
|
||||
|
||||
feat: 新增支持tabs在swiper中使用
|
||||
|
||||
feat: 新增FormItem支持独立设置验证规则rules
|
||||
|
||||
fix: 修复index-list未设置$slots.header时索引高亮失效
|
||||
|
||||
## 3.3.10(2024-08-02)
|
||||
fix: 修复index-list偶发的滑动最后一个索引报错top不存在
|
||||
|
||||
fix: 修复gird在QQ、抖音小程序下布局
|
||||
|
||||
feat: 优化step支持自定义样式prop
|
||||
|
||||
feat: action-sheet组件支持v-model:show双向绑定
|
||||
|
||||
fix: 小程序下steps和grid都统一采用grid布局
|
||||
|
||||
fix: 修复支付宝小程序下input类型为数字时双向绑定失效
|
||||
|
||||
feat : form 表单 validate 校验不通过后 error增加字段prop信息 #304
|
||||
|
||||
fix: form组件异步校异常验问题 #393
|
||||
|
||||
## 3.3.9(2024-08-01)
|
||||
fix: 优化获取nvue元素
|
||||
|
||||
feat: modal新增contentTextAlign设置文案对齐方式
|
||||
|
||||
fix: 修复NVUE下tabbar文字不显示 #458
|
||||
|
||||
feat: loading-page增加zIndex属性
|
||||
|
||||
fix: 相册在宽度较小时换行问题
|
||||
|
||||
feat: album相册增加自适应自动换行模式
|
||||
|
||||
feat: album相册增加图片尺寸单位prop
|
||||
|
||||
fix: 修复calendar日历月份居中
|
||||
|
||||
## 3.3.8(2024-07-31)
|
||||
feat: slider支持进度条任意位置触发按钮拖动
|
||||
|
||||
fix: 修复app-vue下modal标题不居中
|
||||
|
||||
fix: #459 TS setConfig 声明异常
|
||||
|
||||
feat: tabs组件增加longPress长按事件
|
||||
|
||||
feat: 新增showRight属性控制collapse右侧图标显隐
|
||||
|
||||
fix: 优化nvue下css警告
|
||||
|
||||
## 3.3.7(2024-07-29)
|
||||
feat: 支持IndexList组件支持在弹窗等场景下使用及联动优化
|
||||
|
||||
feat: popup组件支持v-model:show双向绑定
|
||||
|
||||
feat: 优化tabs的current双向绑定
|
||||
|
||||
fix: checkbox独立使用时checked赋初始值可以,但是手动切换时值没有做双向绑定! #455
|
||||
|
||||
feat: slider组件支持区间双滑块
|
||||
|
||||
fix: toast 支持自定义图标?可传入了决对路径的 icon也没有用 #409
|
||||
|
||||
feat: form-item校验失败时 增加class方便自定义显示错误的展示方式 #394
|
||||
|
||||
fix: up-cell的required配置不生效 #395
|
||||
|
||||
fix: 横向滚动组件,微信小程序编译后会有警告 #415
|
||||
|
||||
fix: u-picker内部对默认值defaultIndex的监听 #425
|
||||
|
||||
feat: toast 组件支持遮掩层穿透 #417
|
||||
|
||||
fix: 兼容vue的slot编译bug #423
|
||||
|
||||
fix: upload 微信小程序 点击预览视频报错 #424
|
||||
|
||||
fix: u-number-box 组件修改【integer, decimalLength, min, max 】props时没有触发绑定值更新 #429
|
||||
|
||||
feat: Tabs组件能否支持自定义插槽 #439
|
||||
|
||||
feat: ActionSheet 可以配置最大高度吗, 我当做select使用了。 #445
|
||||
|
||||
fix: cursor-pointer优化
|
||||
|
||||
feat: 新版slider组件兼容NVUE改造
|
||||
|
||||
feat: 新增slider组件手动实现以支持样式自定义
|
||||
|
||||
perf:补充TS声明提示信息
|
||||
|
||||
修复:ActionSheet 操作菜单cancelText属性为空DOM节点还存在并且可以点击问题
|
||||
|
||||
fix: 去除预留的beforeDestroy兼容容易在某些sdk下不识别条件编译
|
||||
|
||||
## 3.3.6(2024-07-23)
|
||||
feat: u-album组件添加radius,shape参数,定义参考当前u-image参数
|
||||
|
||||
fix: 修复了calendar组件title和日期title未垂直居中的问题
|
||||
|
||||
fix: update:modelValue缺失emit定义
|
||||
|
||||
## 3.3.5(2024-07-10)
|
||||
picker组件支持hasInput模式
|
||||
|
||||
## 3.3.4(2024-07-07)
|
||||
fix: input组件双向绑定问题 #419
|
||||
|
||||
lazy-load完善emit
|
||||
|
||||
优化通用小程序分享
|
||||
|
||||
## 3.3.2(2024-06-27)
|
||||
fix: 在Nvue环境中编译,出现大量警告 #406
|
||||
## 3.3.1(2024-06-27)
|
||||
u-button组件报错,找不到button mixins #407
|
||||
## 3.3.0(2024-06-27)
|
||||
feat: checkbox支持label设置slot
|
||||
|
||||
feat: modal增加customClass
|
||||
|
||||
feat: navbar、popup、tabs、text支持customClass
|
||||
|
||||
fix: cell组建缺少flex布局
|
||||
|
||||
fix: 修复微信小程序真机调试时快速输入出现文本回退问题
|
||||
|
||||
feat: tag增加默认slot
|
||||
|
||||
公共mixin改造为按需导入语法
|
||||
|
||||
refactor: 组件props混入mixin改造为按需导入语法
|
||||
|
||||
fix: u-tabbar 安卓手机点击按钮变蓝问题 #396
|
||||
|
||||
feat: upload组建增加extension属性
|
||||
|
||||
fix: upload组件参数mode添加left
|
||||
|
||||
fix: 修复阴影在非nvue时白色背景色不显示
|
||||
|
||||
## 3.2.24(2024-06-11)
|
||||
fix: 修复时间选择器confirm事件触发时机导致2次才会触发v-model更新
|
||||
## 3.2.23(2024-05-30)
|
||||
fix: #378 H5 u-input 在表单中初始值为空也会触发一次 formValidate(this,"change")事件导致进入页面直接校验了一次
|
||||
|
||||
fix: #373 搜索组件up-search的@clear事件无效
|
||||
|
||||
fix: #372 ActionSheet 组件的取消按钮触发区域太小
|
||||
|
||||
## 3.2.22(2024-05-13)
|
||||
上传组件支持微信小程序预览视频
|
||||
|
||||
修复折叠面板右侧箭头不显示
|
||||
|
||||
修复uxp2px
|
||||
|
||||
## 3.2.21(2024-05-10)
|
||||
fix: loading-icon修复flex布局
|
||||
## 3.2.20(2024-05-10)
|
||||
修复瀑布流大小写#355
|
||||
## 3.2.19(2024-05-10)
|
||||
去除意外的文件引入
|
||||
## 3.2.18(2024-05-09)
|
||||
fix: 349 popup 组件设置 zIndex 属性后,组件渲染异常#
|
||||
feat: 搜索框增加adjustPosition属性
|
||||
fix: #331增加u-action-sheet__cancel
|
||||
优化mixin兼容性
|
||||
feat: #326 up-list增加下拉刷新功能
|
||||
fix: #319 优化up-tabs参数与定义匹配
|
||||
fix: index-list组件微信小程序端使用自定义导航栏异常
|
||||
fix: #285 pickerimmediateChange 写死为true
|
||||
fix: #111 u-scroll-list组件,隐藏指示器后报错, 提示找不到ref
|
||||
list增加微信小程序防抖配置
|
||||
|
||||
## 3.2.17(2024-05-08)
|
||||
fix: 支付宝小程序二维码渲染
|
||||
## 3.2.16(2024-05-06)
|
||||
修复tabs中,当前激活样式的undefined bug
|
||||
|
||||
fix: #341u-code 倒计时没结束前退出,再次进入结束后退出界面,再次进入重新开始倒计时bug
|
||||
|
||||
受到uni-app内置text样式影响修复
|
||||
|
||||
## 3.2.15(2024-04-28)
|
||||
优化时间选择器hasInput模式初始化值
|
||||
## 3.2.14(2024-04-24)
|
||||
去除pleaseSetTranspileDependencies
|
||||
|
||||
http采用useStore
|
||||
|
||||
## 3.2.13(2024-04-22)
|
||||
修复modal标题样式
|
||||
|
||||
优化日期选择器hasInput模式宽度
|
||||
|
||||
## 3.2.12(2024-04-22)
|
||||
修复color应用
|
||||
## 3.2.11(2024-04-18)
|
||||
修复import化带来的问题
|
||||
## 3.2.10(2024-04-17)
|
||||
完善input清空事件App端失效的兼容性
|
||||
|
||||
修复日历组件二次打开后当前月份显示不正确
|
||||
|
||||
## 3.2.9(2024-04-16)
|
||||
组件内uni.$u用法改为import引入
|
||||
|
||||
规范化及兼容性增强
|
||||
|
||||
## 3.2.8(2024-04-15)
|
||||
修复up-tag语法错
|
||||
## 3.2.7(2024-04-15)
|
||||
修复下拉菜单背景色在支付宝小程序无效
|
||||
|
||||
setConfig改为浅拷贝解决无法用import导入代替uni.$u.props设置
|
||||
|
||||
## 3.2.6(2024-04-14)
|
||||
修复某些情况下滑动单元格默认右侧按钮是展开的问题
|
||||
## 3.2.5(2024-04-13)
|
||||
调整分段器尺寸及修复窗口大小改变时重新计算尺寸
|
||||
|
||||
多个组件支持cursor-pointer增强PC端体验
|
||||
|
||||
## 3.2.4(2024-04-12)
|
||||
初步支持typescript
|
||||
## 3.2.3(2024-04-12)
|
||||
fix: 修复square属性在小程序下无效问题
|
||||
|
||||
fix:修复lastIndex异常导致的column异常问题
|
||||
|
||||
fix: alipayapp picker style
|
||||
|
||||
feat(button): 添加用户同意隐私协议事件回调
|
||||
|
||||
fix: input switch password
|
||||
|
||||
fix: 修复u-code组件keepRuning失效问题
|
||||
|
||||
feat: form-item添加labelPosition属性
|
||||
|
||||
新增dropdown组件
|
||||
|
||||
分段器支持内部current值
|
||||
|
||||
优化cell和action-sheet视觉大小
|
||||
|
||||
修复tabs文字换行
|
||||
|
||||
## 3.2.2(2024-04-11)
|
||||
修复换行符问题
|
||||
## 3.2.1(2024-04-11)
|
||||
修复演示H5二维码
|
||||
|
||||
fix: #270 ReadMore 展开阅读更多内容变化兼容
|
||||
|
||||
fix: #238Calendar组件maxDate修改为不能小于minDate
|
||||
|
||||
checkbox支持独立使用
|
||||
|
||||
修复popup中在微信小程序中真机调试滚动失效
|
||||
|
||||
## 3.2.0(2024-04-10)
|
||||
修复轮播图在nvue显示
|
||||
修复疑似u-slider名称被占用导致slider在App下不显示
|
||||
解决微信小程序提示 Some selectors are not allowed in component wxss
|
||||
示例中u-前缀统一为up-
|
||||
增加瀑布流与图片懒加载组件
|
||||
fix: #308修复tag组件缺失iconColor参数
|
||||
fix: #297使用grid布局解决目前编译为抖音小程序无法开启virtualHost
|
||||
## 3.1.52(2024-04-07)
|
||||
工具类方法调用import化改造
|
||||
新增up-copy复制组件
|
||||
## 3.1.51(2024-04-07)
|
||||
优化时间选择器自带输入框格式化显示
|
||||
防止按钮文字换行
|
||||
修复订单列表模板滑动
|
||||
增加u-qrcode二维码组件
|
||||
## 3.1.49(2024-03-27)
|
||||
日期时间组件支持自带输入框
|
||||
fix: popup弹窗滚动穿透问题
|
||||
fix: 修复小程序numberbox bug
|
||||
## 3.1.48(2024-03-18)
|
||||
fix:[plugin:uni:pre-css] Unbalanced delimiter found in string
|
||||
## 3.1.47(2024-03-18)
|
||||
fix: setConfig设置组件默认参数无效问题
|
||||
fix: 修复自定义图标无效问题
|
||||
feat: 增加u-form-item单独设置规则变量
|
||||
fix:#293小程序是自定义导航栏的时候即传了customNavHeight的时候会出现跳转偏移的情况
|
||||
|
||||
## 3.1.46(2024-01-29)
|
||||
beforeUnmount
|
||||
## 3.1.45(2024-01-24)
|
||||
fix: #262ext组件为超链接的情况下size属性不生效
|
||||
fix: #263最新版本3.1.42中微信小程序u-swipe-action-item报错
|
||||
fix: #224最新版本3.1.42中微信小程序u-swipe-action-item报错
|
||||
fix: #263支持支付宝小程序
|
||||
fix: #261u-input在直接修改v-model的绑定值时,每隔一次会无法出发change事件
|
||||
优化折叠面板兼容微信小程序
|
||||
## 3.1.42(2024-01-15)
|
||||
修复u-number-box默认值0时在小程序不显示值
|
||||
优化u-code的timer判断
|
||||
优化支付宝小程序下textarea字数统计兼容
|
||||
优化u-calendar
|
||||
## 3.1.41(2023-11-18)
|
||||
#215优化u-cell图标容器间距问题
|
||||
## 3.1.40(2023-11-16)
|
||||
修复u-slider双向绑定
|
||||
## 3.1.39(2023-11-10)
|
||||
修复头条小程序不支持env(safe-area-inset-bottom)
|
||||
优化#201u-grid 指定列数导致闪烁
|
||||
#193IndexList 索引列表 高度错误
|
||||
其他优化
|
||||
## 3.1.38(2023-10-08)
|
||||
修复u-slider
|
||||
## 3.1.37(2023-09-13)
|
||||
完善emits定义及修复code-input双向数据绑定
|
||||
## 3.1.36(2023-08-08)
|
||||
修复富文本事件名称大小写
|
||||
## 3.1.35(2023-08-02)
|
||||
修复编译到支付宝小程序u-form报错
|
||||
## 3.1.34(2023-07-27)
|
||||
修复App打包uni.$u.mpMixin方式sdk暂时不支持导致报错
|
||||
## 3.1.33(2023-07-13)
|
||||
修复弹窗进入动画、模板页面样式等
|
||||
## 3.1.31(2023-07-11)
|
||||
修复dayjs引用
|
||||
## 3.0.8(2022-07-12)
|
||||
修复u-tag默认宽度撑满容器
|
||||
## 3.0.7(2022-07-12)
|
||||
修复u-navbar自定义插槽演示示例
|
||||
## 3.0.6(2022-07-11)
|
||||
修复u-image缺少emits申明
|
||||
## 3.0.5(2022-07-11)
|
||||
修复u-upload缺少emits申明
|
||||
## 3.0.4(2022-07-10)
|
||||
修复u-textarea/u-input/u-datetime-picker/u-number-box/u-radio-group/u-switch/u-rate在vue3下数据绑定
|
||||
## 3.0.3(2022-07-09)
|
||||
启用自建演示二维码
|
||||
## 3.0.2(2022-07-09)
|
||||
修复dayjs/clipboard等导致打包报错
|
||||
## 3.0.1(2022-07-09)
|
||||
增加插件市场地址
|
||||
## 3.0.0(2022-07-09)
|
||||
# uview-plus(vue3)初步发布
|
85
src/uni_modules/uview-plus/components/u--form/u--form.vue
Normal 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>
|
50
src/uni_modules/uview-plus/components/u--image/u--image.vue
Normal file
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<uvImage
|
||||
:src="src"
|
||||
:mode="mode"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:shape="shape"
|
||||
:radius="radius"
|
||||
:lazyLoad="lazyLoad"
|
||||
:showMenuByLongpress="showMenuByLongpress"
|
||||
:loadingIcon="loadingIcon"
|
||||
:errorIcon="errorIcon"
|
||||
:showLoading="showLoading"
|
||||
:showError="showError"
|
||||
:fade="fade"
|
||||
:webp="webp"
|
||||
:duration="duration"
|
||||
:bgColor="bgColor"
|
||||
:customStyle="customStyle"
|
||||
@click="$emit('click')"
|
||||
@error="$emit('error')"
|
||||
@load="$emit('load')"
|
||||
>
|
||||
<template v-slot:loading>
|
||||
<slot name="loading"></slot>
|
||||
</template>
|
||||
<template v-slot:error>
|
||||
<slot name="error"></slot>
|
||||
</template>
|
||||
</uvImage>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
|
||||
* 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
|
||||
*/
|
||||
import uvImage from '../u-image/u-image.vue';
|
||||
import { props } from '../u-image/props.js';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
export default {
|
||||
name: 'u--image',
|
||||
mixins: [mpMixin, props, mixin],
|
||||
components: {
|
||||
uvImage
|
||||
},
|
||||
emits: ['click', 'error', 'load']
|
||||
}
|
||||
</script>
|
74
src/uni_modules/uview-plus/components/u--input/u--input.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<uvInput
|
||||
<!-- #ifdef VUE2 -->
|
||||
:value="value"
|
||||
@input="e => $emit('input', e)"
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef VUE3 -->
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="e => $emit('update:modelValue', e)"
|
||||
<!-- #endif -->
|
||||
:type="type"
|
||||
:fixed="fixed"
|
||||
:disabled="disabled"
|
||||
:disabledColor="disabledColor"
|
||||
:clearable="clearable"
|
||||
:password="password"
|
||||
:maxlength="maxlength"
|
||||
:placeholder="placeholder"
|
||||
:placeholderClass="placeholderClass"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:showWordLimit="showWordLimit"
|
||||
:confirmType="confirmType"
|
||||
:confirmHold="confirmHold"
|
||||
:holdKeyboard="holdKeyboard"
|
||||
:focus="focus"
|
||||
:autoBlur="autoBlur"
|
||||
:disableDefaultPadding="disableDefaultPadding"
|
||||
:cursor="cursor"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:selectionStart="selectionStart"
|
||||
:selectionEnd="selectionEnd"
|
||||
:adjustPosition="adjustPosition"
|
||||
:inputAlign="inputAlign"
|
||||
:fontSize="fontSize"
|
||||
:color="color"
|
||||
:prefixIcon="prefixIcon"
|
||||
:suffixIcon="suffixIcon"
|
||||
:suffixIconStyle="suffixIconStyle"
|
||||
:prefixIconStyle="prefixIconStyle"
|
||||
:border="border"
|
||||
:readonly="readonly"
|
||||
:shape="shape"
|
||||
:customStyle="customStyle"
|
||||
:formatter="formatter"
|
||||
:ignoreCompositionEvent="ignoreCompositionEvent"
|
||||
>
|
||||
<!-- #ifdef MP -->
|
||||
<slot name="prefix"></slot>
|
||||
<slot name="suffix"></slot>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP -->
|
||||
<slot name="prefix" slot="prefix"></slot>
|
||||
<slot name="suffix" slot="suffix"></slot>
|
||||
<!-- #endif -->
|
||||
</uvInput>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
|
||||
* 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
|
||||
*/
|
||||
import uvInput from '../u-input/u-input.vue';
|
||||
import { props } from '../u-input/props.js';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
export default {
|
||||
name: 'u--input',
|
||||
mixins: [mpMixin, props, mixin],
|
||||
components: {
|
||||
uvInput
|
||||
},
|
||||
}
|
||||
</script>
|
45
src/uni_modules/uview-plus/components/u--text/u--text.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<uvText
|
||||
:type="type"
|
||||
:show="show"
|
||||
:text="text"
|
||||
:prefixIcon="prefixIcon"
|
||||
:suffixIcon="suffixIcon"
|
||||
:mode="mode"
|
||||
:href="href"
|
||||
:format="format"
|
||||
:call="call"
|
||||
:openType="openType"
|
||||
:bold="bold"
|
||||
:block="block"
|
||||
:lines="lines"
|
||||
:color="color"
|
||||
:decoration="decoration"
|
||||
:size="size"
|
||||
:iconStyle="iconStyle"
|
||||
:margin="margin"
|
||||
:lineHeight="lineHeight"
|
||||
:align="align"
|
||||
:wordWrap="wordWrap"
|
||||
:customStyle="customStyle"
|
||||
></uvText>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
|
||||
* 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
|
||||
* 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
|
||||
*/
|
||||
import uvText from "../u-text/u-text.vue";
|
||||
import { props } from "../u-text/props.js";
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin.js'
|
||||
import { mixin } from '../../libs/mixin/mixin.js'
|
||||
export default {
|
||||
name: "u--text",
|
||||
mixins: [mpMixin, mixin, props,],
|
||||
components: {
|
||||
uvText,
|
||||
},
|
||||
};
|
||||
</script>
|
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<uvTextarea
|
||||
:value="value"
|
||||
:modelValue="modelValue"
|
||||
:placeholder="placeholder"
|
||||
:height="height"
|
||||
:confirmType="confirmType"
|
||||
:disabled="disabled"
|
||||
:count="count"
|
||||
:focus="focus"
|
||||
:autoHeight="autoHeight"
|
||||
:fixed="fixed"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:cursor="cursor"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
:selectionStart="selectionStart"
|
||||
:selectionEnd="selectionEnd"
|
||||
:adjustPosition="adjustPosition"
|
||||
:disableDefaultPadding="disableDefaultPadding"
|
||||
:holdKeyboard="holdKeyboard"
|
||||
:maxlength="maxlength"
|
||||
:border="border"
|
||||
:customStyle="customStyle"
|
||||
:formatter="formatter"
|
||||
:ignoreCompositionEvent="ignoreCompositionEvent"
|
||||
@input="e => $emit('input', e)"
|
||||
@update:modelValue="e => $emit('update:modelValue', e)"
|
||||
></uvTextarea>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
|
||||
* 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
|
||||
*/
|
||||
import uvTextarea from '../u-textarea/u-textarea.vue';
|
||||
import { props } from '../u-textarea/props.js';
|
||||
import { mpMixin } from '../../libs/mixin/mpMixin';
|
||||
import { mixin } from '../../libs/mixin/mixin';
|
||||
export default {
|
||||
name: 'u--textarea',
|
||||
mixins: [mpMixin, props, mixin],
|
||||
components: {
|
||||
uvTextarea
|
||||
},
|
||||
}
|
||||
</script>
|
@ -0,0 +1,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>
|
@ -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'
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 操作菜单是否展示 (默认false)
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.show
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.title
|
||||
},
|
||||
// 选项上方的描述信息
|
||||
description: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.description
|
||||
},
|
||||
// 数据
|
||||
actions: {
|
||||
type: Array,
|
||||
default: () => defProps.actionSheet.actions
|
||||
},
|
||||
// 取消按钮的文字,不为空时显示按钮
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.cancelText
|
||||
},
|
||||
// 点击某个菜单项时是否关闭弹窗
|
||||
closeOnClickAction: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.closeOnClickAction
|
||||
},
|
||||
// 处理底部安全区(默认true)
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.safeAreaInsetBottom
|
||||
},
|
||||
// 小程序的打开方式
|
||||
openType: {
|
||||
type: String,
|
||||
default: () => defProps.actionSheet.openType
|
||||
},
|
||||
// 点击遮罩是否允许关闭 (默认true)
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: () => defProps.actionSheet.closeOnClickOverlay
|
||||
},
|
||||
// 圆角值
|
||||
round: {
|
||||
type: [Boolean, String, Number],
|
||||
default: () => defProps.actionSheet.round
|
||||
},
|
||||
// 选项区域最大高度
|
||||
wrapMaxHeight: {
|
||||
type: [String],
|
||||
default: () => defProps.actionSheet.wrapMaxHeight
|
||||
},
|
||||
}
|
||||
})
|
@ -0,0 +1,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",
|
||||
// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
|
||||
mixins: [openType, buttonMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {};
|
||||
if (this.actions[index].color) style.color = this.actions[index].color
|
||||
if (this.actions[index].fontSize) style.fontSize = addUnit(this.actions[index].fontSize)
|
||||
// 选项被禁用的样式
|
||||
if (this.actions[index].disabled) style.color = '#c0c4cc'
|
||||
return style;
|
||||
}
|
||||
},
|
||||
},
|
||||
emits: ["close", "select", "update:show"],
|
||||
methods: {
|
||||
closeHandler() {
|
||||
// 允许点击遮罩关闭时,才发出close事件
|
||||
if(this.closeOnClickOverlay) {
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
// 点击取消按钮
|
||||
cancel() {
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
},
|
||||
selectHandler(index) {
|
||||
const item = this.actions[index]
|
||||
if (item && !item.disabled && !item.loading) {
|
||||
this.$emit('select', item)
|
||||
if (this.closeOnClickAction) {
|
||||
this.$emit('update:show', false)
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$u-action-sheet-reset-button-width:100% !default;
|
||||
$u-action-sheet-title-font-size: 16px !default;
|
||||
$u-action-sheet-title-padding: 12px 30px !default;
|
||||
$u-action-sheet-title-color: $u-main-color !default;
|
||||
$u-action-sheet-header-icon-wrap-right:15px !default;
|
||||
$u-action-sheet-header-icon-wrap-top:15px !default;
|
||||
$u-action-sheet-description-font-size:13px !default;
|
||||
$u-action-sheet-description-color:14px !default;
|
||||
$u-action-sheet-description-margin: 18px 15px !default;
|
||||
$u-action-sheet-item-wrap-item-padding:17px !default;
|
||||
$u-action-sheet-item-wrap-name-font-size:16px !default;
|
||||
$u-action-sheet-item-wrap-subname-font-size:13px !default;
|
||||
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
|
||||
$u-action-sheet-item-wrap-subname-margin-top:10px !default;
|
||||
$u-action-sheet-cancel-text-font-size:16px !default;
|
||||
$u-action-sheet-cancel-text-color:$u-content-color !default;
|
||||
$u-action-sheet-cancel-text-font-size:15px !default;
|
||||
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
|
||||
|
||||
.u-reset-button {
|
||||
width: $u-action-sheet-reset-button-width;
|
||||
}
|
||||
|
||||
.u-action-sheet {
|
||||
text-align: center;
|
||||
&__header {
|
||||
position: relative;
|
||||
padding: $u-action-sheet-title-padding;
|
||||
&__title {
|
||||
font-size: $u-action-sheet-title-font-size;
|
||||
color: $u-action-sheet-title-color;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
position: absolute;
|
||||
right: $u-action-sheet-header-icon-wrap-right;
|
||||
top: $u-action-sheet-header-icon-wrap-top;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
font-size: $u-action-sheet-description-font-size;
|
||||
color: $u-tips-color;
|
||||
margin: $u-action-sheet-description-margin;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__item-wrap {
|
||||
|
||||
&__item {
|
||||
padding: $u-action-sheet-item-wrap-item-padding;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
&__name {
|
||||
font-size: $u-action-sheet-item-wrap-name-font-size;
|
||||
color: $u-main-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__subname {
|
||||
font-size: $u-action-sheet-item-wrap-subname-font-size;
|
||||
color: $u-action-sheet-item-wrap-subname-color;
|
||||
margin-top: $u-action-sheet-item-wrap-subname-margin-top;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel-text {
|
||||
font-size: $u-action-sheet-cancel-text-font-size;
|
||||
color: $u-action-sheet-cancel-text-color;
|
||||
text-align: center;
|
||||
// padding: $u-action-sheet-cancel-text-font-size;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: $u-action-sheet-cancel-text-hover-background-color;
|
||||
}
|
||||
}
|
||||
</style>
|
28
src/uni_modules/uview-plus/components/u-album/album.js
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @Date : 2021-08-20 16:44:21
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-20 16:47:24
|
||||
* @FilePath : /u-view2.0/uview-ui/libs/config/props/album.js
|
||||
*/
|
||||
export default {
|
||||
// album 组件
|
||||
album: {
|
||||
urls: [],
|
||||
keyName: '',
|
||||
singleSize: 180,
|
||||
multipleSize: 70,
|
||||
space: 6,
|
||||
singleMode: 'scaleToFill',
|
||||
multipleMode: 'aspectFill',
|
||||
maxCount: 9,
|
||||
previewFullImage: true,
|
||||
rowCount: 3,
|
||||
showMore: true,
|
||||
autoWrap: false,
|
||||
unit: 'px',
|
||||
stop: true,
|
||||
}
|
||||
}
|
86
src/uni_modules/uview-plus/components/u-album/props.js
Normal file
@ -0,0 +1,86 @@
|
||||
import { defineMixin } from '../../libs/vue'
|
||||
import defProps from '../../libs/config/props.js'
|
||||
export const props = defineMixin({
|
||||
props: {
|
||||
// 图片地址,Array<String>|Array<Object>形式
|
||||
urls: {
|
||||
type: Array,
|
||||
default: () => defProps.album.urls
|
||||
},
|
||||
// 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
keyName: {
|
||||
type: String,
|
||||
default: () => defProps.album.keyName
|
||||
},
|
||||
// 单图时,图片长边的长度
|
||||
singleSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.singleSize
|
||||
},
|
||||
// 多图时,图片边长
|
||||
multipleSize: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.multipleSize
|
||||
},
|
||||
// 多图时,图片水平和垂直之间的间隔
|
||||
space: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.space
|
||||
},
|
||||
// 单图时,图片缩放裁剪的模式
|
||||
singleMode: {
|
||||
type: String,
|
||||
default: () => defProps.album.singleMode
|
||||
},
|
||||
// 多图时,图片缩放裁剪的模式
|
||||
multipleMode: {
|
||||
type: String,
|
||||
default: () => defProps.album.multipleMode
|
||||
},
|
||||
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
|
||||
maxCount: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.maxCount
|
||||
},
|
||||
// 是否可以预览图片
|
||||
previewFullImage: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.previewFullImage
|
||||
},
|
||||
// 每行展示图片数量,如设置,singleSize和multipleSize将会无效
|
||||
rowCount: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.album.rowCount
|
||||
},
|
||||
// 超出maxCount时是否显示查看更多的提示
|
||||
showMore: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.showMore
|
||||
},
|
||||
// 图片形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: () => defProps.image.shape
|
||||
},
|
||||
// 圆角,单位任意
|
||||
radius: {
|
||||
type: [String, Number],
|
||||
default: () => defProps.image.radius
|
||||
},
|
||||
// 自适应换行
|
||||
autoWrap: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.autoWrap
|
||||
},
|
||||
// 单位
|
||||
unit: {
|
||||
type: [String],
|
||||
default: () => defProps.album.unit
|
||||
},
|
||||
// 阻止点击冒泡
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: () => defProps.album.stop
|
||||
}
|
||||
}
|
||||
})
|
300
src/uni_modules/uview-plus/components/u-album/u-album.vue
Normal 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
|
||||
// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Album 相册
|
||||
* @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
|
||||
* @tutorial https://ijry.github.io/uview-plus/components/album.html
|
||||
*
|
||||
* @property {Array} urls 图片地址列表 Array<String>|Array<Object>形式
|
||||
* @property {String} keyName 指定从数组的对象元素中读取哪个属性作为图片地址
|
||||
* @property {String | Number} singleSize 单图时,图片长边的长度 (默认 180 )
|
||||
* @property {String | Number} multipleSize 多图时,图片边长 (默认 70 )
|
||||
* @property {String | Number} space 多图时,图片水平和垂直之间的间隔 (默认 6 )
|
||||
* @property {String} singleMode 单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
|
||||
* @property {String} multipleMode 多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
|
||||
* @property {String | Number} maxCount 取消按钮的提示文字 (默认 9 )
|
||||
* @property {Boolean} previewFullImage 是否可以预览图片 (默认 true )
|
||||
* @property {String | Number} rowCount 每行展示图片数量,如设置,singleSize和multipleSize将会无效 (默认 3 )
|
||||
* @property {Boolean} showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
|
||||
* @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
|
||||
* @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
|
||||
* @property {Boolean} autoWrap 自适应换行模式,不受rowCount限制,图片会自动换行 (默认 false )
|
||||
* @property {String} unit 图片单位 (默认 px )
|
||||
* @event {Function} albumWidth 某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送 (回调参数 width )
|
||||
* @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-album',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 单图的宽度
|
||||
singleWidth: 0,
|
||||
// 单图的高度
|
||||
singleHeight: 0,
|
||||
// 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
|
||||
singlePercent: 0.6
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
urls: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal.length === 1) {
|
||||
this.getImageRect()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
emits: ["albumWidth"],
|
||||
computed: {
|
||||
imageStyle() {
|
||||
return (index1, index2) => {
|
||||
const { space, rowCount, multipleSize, urls } = this,
|
||||
rowLen = this.showUrls.length,
|
||||
allLen = this.urls.length
|
||||
const style = {
|
||||
marginRight: addUnit(space),
|
||||
marginBottom: addUnit(space)
|
||||
}
|
||||
// 如果为最后一行,则每个图片都无需下边框
|
||||
if (index1 === rowLen && !this.autoWrap) style.marginBottom = 0
|
||||
// 每行的最右边一张和总长度的最后一张无需右边框
|
||||
if (!this.autoWrap) {
|
||||
if (
|
||||
index2 === rowCount ||
|
||||
(index1 === rowLen &&
|
||||
index2 === this.showUrls[index1 - 1].length)
|
||||
)
|
||||
style.marginRight = 0
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 将数组划分为二维数组
|
||||
showUrls() {
|
||||
if (this.autoWrap) {
|
||||
return [ this.urls.slice(0, this.maxCount) ];
|
||||
} else {
|
||||
const arr = []
|
||||
this.urls.map((item, index) => {
|
||||
// 限制最大展示数量
|
||||
if (index + 1 <= this.maxCount) {
|
||||
// 计算该元素为第几个素组内
|
||||
const itemIndex = Math.floor(index / this.rowCount)
|
||||
// 判断对应的索引是否存在
|
||||
if (!arr[itemIndex]) {
|
||||
arr[itemIndex] = []
|
||||
}
|
||||
arr[itemIndex].push(item)
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}
|
||||
},
|
||||
imageWidth() {
|
||||
return addUnit(
|
||||
this.urls.length === 1 ? this.singleWidth : this.multipleSize, this.unit
|
||||
)
|
||||
},
|
||||
imageHeight() {
|
||||
return addUnit(
|
||||
this.urls.length === 1 ? this.singleHeight : this.multipleSize, this.unit
|
||||
)
|
||||
},
|
||||
// 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
|
||||
// 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
|
||||
albumWidth() {
|
||||
let width = 0
|
||||
if (this.urls.length === 1) {
|
||||
width = this.singleWidth
|
||||
} else {
|
||||
width =
|
||||
this.showUrls[0].length * this.multipleSize +
|
||||
this.space * (this.showUrls[0].length - 1)
|
||||
}
|
||||
this.$emit('albumWidth', width)
|
||||
return width
|
||||
}
|
||||
},
|
||||
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"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
|
||||
const ref = this.$refs['u-album__row'][0]
|
||||
ref &&
|
||||
dom.getComponentRect(ref, (res) => {
|
||||
this.singleWidth = res.size.width * this.singlePercent
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.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>
|