Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6534723050 | |||
| f031501286 | |||
| 525f94cb80 | |||
| 127834e8dc | |||
| e7d4e6cd51 | |||
| 6e4c831870 | |||
| ff544b9766 | |||
| 9c966dde26 | |||
| f2a20a66cb | |||
| 09f1d8a4c1 | |||
| e2f75434c6 | |||
| bf658e1d3b | |||
| 5f74ece29e | |||
| 437764c9d2 | |||
| a85117cca7 | |||
| 7a95facf30 | |||
| 4b1d3445da | |||
| 91c75bfbff | |||
| 55ffcdb9d3 | |||
| 4ed63675a4 | |||
| 8a4cba7769 |
@@ -0,0 +1,138 @@
|
||||
name: Uni-app H5 Deploy (Prod + Staging)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
###############################################
|
||||
# 0. 检查发布类型和分支权限
|
||||
###############################################
|
||||
- name: Check Release Type and Branch Permission
|
||||
id: detect
|
||||
run: |
|
||||
BRANCH="${{ github.event.release.target_commitish }}"
|
||||
IS_PRERELEASE="${{ github.event.release.prerelease }}"
|
||||
|
||||
echo "当前分支: $BRANCH"
|
||||
echo "是否为预发布: $IS_PRERELEASE"
|
||||
echo "发布标签: ${{ github.event.release.tag_name }}"
|
||||
|
||||
# 如果勾选了"标记为预发布"
|
||||
if [ "$IS_PRERELEASE" = "true" ]; then
|
||||
echo "检测到预发布版本"
|
||||
if [ "$BRANCH" != "develop" ]; then
|
||||
echo "错误: 预发布只能从 develop 分支发布"
|
||||
echo "当前分支: $BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
echo "env=staging" >> "$GITHUB_OUTPUT"
|
||||
echo "target=${{ vars.STG_WEB_HOST }}" >> "$GITHUB_OUTPUT"
|
||||
echo "部署环境: 预发布环境"
|
||||
echo "目标服务器: ${{ vars.STG_WEB_HOST }}"
|
||||
else
|
||||
# 未勾选"标记为预发布",为正式发布
|
||||
echo "检测到正式发布版本"
|
||||
if [ "$BRANCH" != "main" ]; then
|
||||
echo "错误: 正式发布只能从 main 分支发布"
|
||||
echo "当前分支: $BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
echo "env=production" >> "$GITHUB_OUTPUT"
|
||||
echo "target=${{ vars.PRD_WEB_HOST }}" >> "$GITHUB_OUTPUT"
|
||||
echo "部署环境: 生产环境"
|
||||
echo "目标服务器: ${{ vars.PRD_WEB_HOST }}"
|
||||
fi
|
||||
|
||||
###############################################
|
||||
# 1. 设置 SSH 密钥 & 主机信任
|
||||
###############################################
|
||||
- name: Setup SSH Key
|
||||
run: |
|
||||
# 1. 确保 .ssh 目录存在且权限正确
|
||||
mkdir -p ~/.ssh
|
||||
chmod 700 ~/.ssh
|
||||
|
||||
# 2. 将 Gitea 密钥里的私钥写入文件
|
||||
echo "${{ secrets.PRD_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
# 3. 生成并写入 SSH Config 配置文件
|
||||
cat > ~/.ssh/config << EOF
|
||||
Host git-host
|
||||
HostName 101.200.86.98
|
||||
User kk
|
||||
StrictHostKeyChecking no
|
||||
UserKnownHostsFile /dev/null
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
EOF
|
||||
chmod 600 ~/.ssh/config
|
||||
|
||||
echo "SSH 配置完成"
|
||||
|
||||
###############################################
|
||||
# 2. Checkout 代码
|
||||
###############################################
|
||||
- name: Checkout Code via SSH
|
||||
env:
|
||||
CLONE_SSH: ${{ vars.CLONE_SSH }}
|
||||
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
# 直接使用服务器上的 Gitea 仓库物理路径
|
||||
LOCAL_REPO_PATH: /usr/local/bin/data/gitea-repositories/kellan/consumer-front.git
|
||||
run: |
|
||||
set -e
|
||||
# 使用 --no-hardlinks 在本机通过本地路径克隆,完全避开 SSH 和所有权检查
|
||||
git clone --depth=1 --branch "$TAG_NAME" --no-hardlinks "$LOCAL_REPO_PATH" .
|
||||
|
||||
###############################################
|
||||
# 3. 安装依赖 & 构建 Uni-app (H5)
|
||||
###############################################
|
||||
- name: Install dependencies and Build H5
|
||||
run: |
|
||||
set -e
|
||||
# pnpm真实路径
|
||||
PNPM_PATH="/root/.nvm/versions/node/v20.20.2/bin/pnpm"
|
||||
|
||||
# 1. 配置镜像并安装依赖
|
||||
$PNPM_PATH config set registry https://registry.npmmirror.com
|
||||
$PNPM_PATH install --frozen-lockfile
|
||||
|
||||
# 2. 执行构建
|
||||
$PNPM_PATH run build:h5
|
||||
|
||||
###############################################
|
||||
# 4. 上传 dist 到目标服务器
|
||||
###############################################
|
||||
- name: Upload dist to server
|
||||
run: |
|
||||
TARGET="${{ steps.detect.outputs.target }}"
|
||||
# 请根据你的实际构建输出路径调整
|
||||
H5_SOURCE_DIR="dist/build/h5"
|
||||
WEB_ROOT="${{ vars.PROJECT_DIR }}"
|
||||
|
||||
echo "部署到: $TARGET"
|
||||
echo "源目录: $H5_SOURCE_DIR"
|
||||
echo "目标目录: $WEB_ROOT"
|
||||
|
||||
# 检查构建目录是否存在
|
||||
if [ ! -d "$H5_SOURCE_DIR" ]; then
|
||||
echo "错误: 构建目录 $H5_SOURCE_DIR 不存在,请检查构建命令是否正确"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 确保构建产物可读
|
||||
chmod -R a+r "$H5_SOURCE_DIR"
|
||||
|
||||
# 使用 rsync 同步文件
|
||||
rsync -rltvz --delete \
|
||||
--no-perms --no-times --no-owner --no-group \
|
||||
-e "ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no" \
|
||||
"$H5_SOURCE_DIR/" \
|
||||
"root@${TARGET}:${WEB_ROOT}/"
|
||||
|
||||
echo "H5 部署完成"
|
||||
echo "${{ steps.detect.outputs.env }} 环境部署成功!"
|
||||
@@ -0,0 +1,33 @@
|
||||
# --- 构建产物 ---
|
||||
unpackage/
|
||||
dist/
|
||||
build/
|
||||
|
||||
# --- 依赖目录 ---
|
||||
node_modules/
|
||||
|
||||
# --- uni-app 插件模块 (仅忽略根目录,src/uni_modules 是项目代码) ---
|
||||
/uni_modules/
|
||||
|
||||
# --- 编辑器配置与缓存 ---
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# --- 系统文件 ---
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# --- 环境与敏感配置 ---
|
||||
.env*
|
||||
|
||||
# --- 日志文件 ---
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# --- 依赖锁文件 (根据项目使用的包管理器,保留一个即可) ---
|
||||
# package-lock.json
|
||||
# yarn.lock
|
||||
# pnpm-lock.yaml
|
||||
/CLAUDE.md
|
||||
@@ -1,32 +0,0 @@
|
||||
<script>
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.warn('当前组件仅支持 uni_modules 目录结构 ,请升级 HBuilderX 到 3.1.0 版本以上!')
|
||||
console.log('App Launch')
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/*每个页面公共css */
|
||||
@import '@/uni_modules/uni-scss/index.scss';
|
||||
/* #ifndef APP-NVUE */
|
||||
@import '@/static/customicons.css';
|
||||
// 设置整个项目的背景色
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.example-info {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -15,6 +15,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "consumer-front",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"vconsole": "^3.15.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/copy-text-to-clipboard": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz",
|
||||
"integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.49.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz",
|
||||
"integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/mutation-observer": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/mutation-observer/-/mutation-observer-1.0.3.tgz",
|
||||
"integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
|
||||
},
|
||||
"node_modules/vconsole": {
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/vconsole/-/vconsole-3.15.1.tgz",
|
||||
"integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"copy-text-to-clipboard": "^3.0.1",
|
||||
"core-js": "^3.11.0",
|
||||
"mutation-observer": "^1.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "consumer-front",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev:h5": "uni",
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"build:h5": "uni build",
|
||||
"build:mp-weixin": "uni build -p mp-weixin"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-components": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-h5": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-4060620250520001",
|
||||
"vue": "^3.4.21",
|
||||
"vconsole": "^3.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.8",
|
||||
"@dcloudio/uni-automator": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-4060620250520001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-4060620250520001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-4060620250520001",
|
||||
"postcss": "^8.4.38",
|
||||
"sass-embedded": "^1.77.0",
|
||||
"vite": "^5.2.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"postcss": "^8.4.38"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{"pages": [{"path": "pages/index/index", "style": {"navigationBarTitleText": "智能货柜", "navigationStyle": "custom"}}], "globalStyle": {"navigationBarTextStyle": "black", "navigationBarTitleText": "智能货柜", "navigationBarBackgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8"}}
|
||||
@@ -1,201 +0,0 @@
|
||||
<template>
|
||||
<view class="app-container">
|
||||
<!-- 商品展示区域 -->
|
||||
<scroll-view class="product-area" scroll-y>
|
||||
<!-- 标题 -->
|
||||
<view class="section-title">热销商品</view>
|
||||
|
||||
<!-- 商品列表 - 三列 -->
|
||||
<view class="product-grid">
|
||||
<view
|
||||
v-for="(item, index) in productList"
|
||||
:key="index"
|
||||
class="product-card"
|
||||
>
|
||||
<image class="product-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="product-name">{{ item.name }}</view>
|
||||
<view class="product-price">{{ item.price }}元</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item" :class="{ active: currentTab === 'home' }" @click="switchTab('home')">
|
||||
<text class="tab-text">首页</text>
|
||||
</view>
|
||||
|
||||
<!-- 开门按钮 - 圆形凸起 -->
|
||||
<view class="door-btn-wrapper" @click="handleOpenDoor">
|
||||
<view class="door-btn">开门</view>
|
||||
</view>
|
||||
|
||||
<view class="tab-item" :class="{ active: currentTab === 'mine' }" @click="switchTab('mine')">
|
||||
<text class="tab-text">我的</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentTab: 'home',
|
||||
productList: [
|
||||
{ name: '君乐宝450ml悦鲜活', price: '0.01', image: '/static/c1.png' },
|
||||
{ name: '白红(0.0.0.0-9.9.9)', price: '0.06', image: '/static/c2.png' },
|
||||
{ name: '白桔(0.0.0.0-5.5.5)', price: '0.05', image: '/static/c3.png' },
|
||||
{ name: '可口可乐330ml', price: '3.00', image: '/static/c4.png' },
|
||||
{ name: '农夫山泉550ml', price: '2.00', image: '/static/c5.png' },
|
||||
{ name: '乐事薯片原味75g', price: '7.50', image: '/static/c6.png' }
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
switchTab(tab) {
|
||||
this.currentTab = tab;
|
||||
},
|
||||
handleOpenDoor() {
|
||||
uni.showModal({
|
||||
title: '开门',
|
||||
content: '确认打开柜门?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '柜门已打开', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* ========== 商品区域 ========== */
|
||||
.product-area {
|
||||
flex: 1;
|
||||
padding-bottom: 160rpx;
|
||||
}
|
||||
|
||||
/* 热销商品标题 */
|
||||
.section-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
padding: 24rpx 28rpx 16rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 三列网格 */
|
||||
.product-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 12rpx;
|
||||
gap: 12rpx;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 每列占1/3 */
|
||||
.product-card {
|
||||
width: calc((100% - 24rpx) / 3);
|
||||
background: transparent;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
text-align: center; /* 居中 */
|
||||
}
|
||||
|
||||
.product-img {
|
||||
width: 100%;
|
||||
height: 220rpx;
|
||||
display: block;
|
||||
border-radius: 8rpx;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 商品名称:居中 + 单行超出省略 */
|
||||
.product-name {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
padding: 10rpx 8rpx 6rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 价格:红色加粗 + 居中 */
|
||||
.product-price {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #e4393c;
|
||||
padding: 0 8rpx 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== 底部导航栏 ========== */
|
||||
.tab-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
z-index: 999;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
font-size: 28rpx; /* 变小一点 */
|
||||
color: #999;
|
||||
font-weight: normal; /* 不加粗 */
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
color: #2979ff;
|
||||
}
|
||||
|
||||
/* ========== 开门按钮 - 更大、更高 ========== */
|
||||
.door-btn-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -80rpx; /* 更往上凸起 */
|
||||
}
|
||||
|
||||
.door-btn {
|
||||
width: 160rpx; /* 更大的圆 */
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #2979ff, #1e60e0);
|
||||
color: #fff;
|
||||
font-size: 38rpx; /* 更大的字 */
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 36rpx rgba(41, 121, 255, 0.55);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
70011ed2d63c3cfa7094f5cb2a9aafea
|
||||
@@ -0,0 +1 @@
|
||||
sfIpUAfcT78BnMBv
|
||||
@@ -0,0 +1,84 @@
|
||||
<script>
|
||||
import { initEnv } from '@/utils/env.js';
|
||||
|
||||
export default {
|
||||
onLaunch: function(options) {
|
||||
console.log('App Launch')
|
||||
|
||||
// 1. 环境检测 + 缓存 app_no/platform
|
||||
initEnv();
|
||||
|
||||
// #ifdef H5
|
||||
// 2. 解析 URL 路径中的设备编号(如 /A1036)
|
||||
let deviceId = '';
|
||||
const path = window.location.pathname;
|
||||
const segments = path.split('/').filter(Boolean);
|
||||
if (segments.length > 0) {
|
||||
const last = segments[segments.length - 1];
|
||||
if (/^[A-Z][A-Z0-9]+$/.test(last)) {
|
||||
deviceId = last;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 检查 URL 中是否有授权回调的 code
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get('code');
|
||||
|
||||
if (code) {
|
||||
// 有 code → 进入授权 Loading 页处理
|
||||
const loadingUrl = deviceId
|
||||
? `/pages/auth/loading?device_id=${deviceId}`
|
||||
: '/pages/auth/loading';
|
||||
uni.redirectTo({ url: loadingUrl });
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 检查 JWT
|
||||
const token = uni.getStorageSync('token');
|
||||
|
||||
if (token) {
|
||||
// 有 JWT → 直接进入首页
|
||||
const homeUrl = deviceId
|
||||
? `/pages/index/index?device_id=${deviceId}`
|
||||
: '/pages/index/scan';
|
||||
uni.redirectTo({ url: homeUrl });
|
||||
} else {
|
||||
// 无 JWT → 进入授权 Loading 页
|
||||
const loadingUrl = deviceId
|
||||
? `/pages/auth/loading?device_id=${deviceId}`
|
||||
: '/pages/auth/loading';
|
||||
uni.redirectTo({ url: loadingUrl });
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 小程序环境:直接进入扫码页
|
||||
uni.redirectTo({ url: '/pages/index/scan' });
|
||||
// #endif
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/*每个页面公共css */
|
||||
@import '@/uni_modules/uni-scss/index.scss';
|
||||
/* #ifndef APP-NVUE */
|
||||
@import '@/static/customicons.css';
|
||||
// 设置整个项目的背景色
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.example-info {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<view class="float-menu-card">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="menu-item"
|
||||
:class="{ 'last-item': index === list.length - 1 }"
|
||||
@click="handleItemClick(item, index)"
|
||||
>
|
||||
<text class="menu-text">{{ item.title }}</text>
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FloatMenuCard',
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleItemClick(item, index) {
|
||||
// 发出事件,让父组件处理跳转逻辑
|
||||
this.$emit('item-click', { item, index });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.float-menu-card {
|
||||
position: fixed;
|
||||
bottom: 40rpx;
|
||||
left: 28rpx;
|
||||
right: 28rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 28rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.menu-item:last-child,
|
||||
.menu-item.last-item {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<view class="page-nav-bar">
|
||||
<view class="nav-btn left" @click="handlePrev">
|
||||
<text class="arrow-icon">‹</text>
|
||||
</view>
|
||||
<view class="nav-btn right" @click="handleNext">
|
||||
<text class="arrow-icon">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageNavBar',
|
||||
props: {
|
||||
hasPrev: { type: Boolean, default: true },
|
||||
hasNext: { type: Boolean, default: true }
|
||||
},
|
||||
methods: {
|
||||
handlePrev() {
|
||||
if (!this.hasPrev) return;
|
||||
this.$emit('prev');
|
||||
},
|
||||
handleNext() {
|
||||
if (!this.hasNext) return;
|
||||
this.$emit('next');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-nav-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 110rpx;
|
||||
background: #fff;
|
||||
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 160rpx;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
font-size: 72rpx;
|
||||
color: #333;
|
||||
font-weight: 300;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.nav-btn.right .arrow-icon {
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<view class="page-nav">
|
||||
<!-- 首页按钮 -->
|
||||
<view class="nav-item" @click="handleNav('home')">
|
||||
<text class="nav-icon">🏠</text>
|
||||
<text class="nav-text">首页</text>
|
||||
</view>
|
||||
|
||||
<!-- 开门按钮 - 圆形凸起 -->
|
||||
<view class="door-btn-wrapper" @click="handleOpenDoor">
|
||||
<view class="door-btn">开门</view>
|
||||
</view>
|
||||
|
||||
<!-- 我的按钮 -->
|
||||
<view class="nav-item" @click="handleNav('mine')">
|
||||
<text class="nav-icon">👤</text>
|
||||
<text class="nav-text">我的</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageNav',
|
||||
props: {
|
||||
// 当前高亮的tab: 'home' | 'mine'
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: 'home'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleNav(tab) {
|
||||
// 发出事件,由父组件处理导航
|
||||
this.$emit('change', tab);
|
||||
},
|
||||
handleOpenDoor() {
|
||||
uni.showModal({
|
||||
title: '开门',
|
||||
content: '确认打开柜门?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '柜门已打开', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
this.$emit('openDoor');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-nav {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 120rpx;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.08);
|
||||
z-index: 999;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 40rpx;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.nav-item:active .nav-text,
|
||||
.nav-item.active .nav-text {
|
||||
color: #2979ff;
|
||||
}
|
||||
|
||||
.door-btn-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -80rpx;
|
||||
}
|
||||
|
||||
.door-btn {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #2979ff, #1e60e0);
|
||||
color: #fff;
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10rpx 36rpx rgba(41, 121, 255, 0.55);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 开发环境配置
|
||||
*/
|
||||
const dev = {
|
||||
// 业务 API 地址(vms-api,端口 4000)
|
||||
apiBaseUrl: 'http://192.168.0.23:4000',
|
||||
|
||||
// 网关地址(vms-gateway,端口 4001)— 用户授权、短信等接口走网关
|
||||
apiGatewayUrl: 'https://gateway.arklinksmart.cn',
|
||||
|
||||
// 授权回调地址(微信 OAuth redirect_uri,必须 HTTPS,指向前端页面)
|
||||
authRedirectUri: 'https://m.arklinksmart.cn/pages/auth/loading',
|
||||
|
||||
// 开发阶段硬编码 Token(生产环境应从登录流程获取)
|
||||
bearerToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImRlZXB2ZW5kaW5nIiwic3ViIjoiMTY0MDI0MTEyNiIsImZyb20iOiJwb3MiLCJyb2xlIjoicG9zIiwibWFpbmJvYXJkX2lkIjoiVDEwMDIiLCJwb3Nfc24iOiIxNjQwMjQxMTI2IiwiaWF0IjoxNzc4NDkxOTA3LCJleHAiOjE3NzkwOTY3MDd9.DjscZtrTCr30XOngToUifxoel4q2EGES_uqGQernkvg',
|
||||
|
||||
// 是否开启请求日志
|
||||
enableRequestLog: true,
|
||||
};
|
||||
|
||||
export default dev;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 环境配置入口
|
||||
* 根据编译模式自动选择 dev / prod 配置
|
||||
*/
|
||||
import common from './index.js';
|
||||
import dev from './dev.js';
|
||||
import prod from './prod.js';
|
||||
|
||||
// uni-app 编译时通过 define 注译 __UNI_PLATFORM__
|
||||
// H5 开发模式下 process.env.NODE_ENV === 'development'
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
const envConfig = isProd ? prod : dev;
|
||||
|
||||
const config = {
|
||||
...common,
|
||||
...envConfig,
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 公共配置(开发/生产环境共享)
|
||||
*/
|
||||
const common = {
|
||||
// 应用信息
|
||||
appName: '悟空智能',
|
||||
appVersion: '1.0.0',
|
||||
|
||||
// 默认设备ID(开发调试用)
|
||||
defaultDeviceId: '',
|
||||
|
||||
// 客服电话
|
||||
servicePhone: '13264706088',
|
||||
|
||||
// 请求超时时间(ms)
|
||||
requestTimeout: 10000,
|
||||
};
|
||||
|
||||
export default common;
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* 生产环境配置
|
||||
*/
|
||||
const prod = {
|
||||
// 业务 API 地址(vms-api)
|
||||
apiBaseUrl: 'http://101.200.86.98:4000',
|
||||
|
||||
// 网关地址(vms-gateway)— 用户授权、短信等接口走网关
|
||||
apiGatewayUrl: 'https://gateway.arklinksmart.cn',
|
||||
|
||||
// 授权回调地址(微信 OAuth redirect_uri,必须 HTTPS,指向前端页面)
|
||||
authRedirectUri: 'https://m.arklinksmart.cn/pages/auth/loading',
|
||||
|
||||
// 生产环境 Token 从登录流程获取,此处为空
|
||||
bearerToken: '',
|
||||
|
||||
enableRequestLog: false,
|
||||
};
|
||||
|
||||
export default prod;
|
||||
@@ -2,6 +2,7 @@
|
||||
// #ifndef VUE3
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import { initVConsole } from '@/utils/vconsole'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
@@ -16,6 +17,7 @@ app.$mount()
|
||||
// #ifdef VUE3
|
||||
import { createSSRApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { initVConsole } from '@/utils/vconsole'
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
return {
|
||||
@@ -23,3 +25,6 @@ export function createApp() {
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 初始化 vConsole(开发环境)
|
||||
initVConsole()
|
||||
@@ -56,5 +56,10 @@
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"h5" : {
|
||||
"router" : {
|
||||
"mode" : "history"
|
||||
}
|
||||
},
|
||||
"vueVersion" : "3"
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"pages": [
|
||||
{
|
||||
"path": "pages/index/scan",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/loading",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/register/register",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mine/mine",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/faq/faq",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order/order",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "智能货柜",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<view class="loading-page">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">{{ statusText }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/config/env.js';
|
||||
import { gatewayGet, gatewayPost } from '@/utils/request.js';
|
||||
import { detectPlatform, getAppNo } from '@/utils/env.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
statusText: '正在登录...',
|
||||
deviceId: '',
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.deviceId = options.device_id || '';
|
||||
|
||||
// #ifdef H5
|
||||
// 检查 URL 中是否有授权回调的 code
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const code = urlParams.get('code');
|
||||
|
||||
if (code) {
|
||||
// 有 code → 调用后端登录
|
||||
this.handleCallback(code);
|
||||
} else {
|
||||
// 无 code → 发起 OAuth 授权
|
||||
this.startAuth();
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 小程序环境暂不支持,跳回扫码页
|
||||
uni.redirectTo({ url: '/pages/index/scan' });
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 发起 OAuth 授权
|
||||
*/
|
||||
async startAuth() {
|
||||
const platform = detectPlatform();
|
||||
const appNo = getAppNo(platform);
|
||||
|
||||
if (!appNo) {
|
||||
// 非微信环境,跳转注册页(手机号注册)
|
||||
this.goToRegister();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.statusText = '正在获取授权...';
|
||||
|
||||
// 回调地址必须是 HTTPS,使用配置文件中的地址
|
||||
const redirectUri = config.authRedirectUri || (
|
||||
window.location.origin.replace(/^http:/, 'https:') + window.location.pathname
|
||||
);
|
||||
|
||||
const res = await gatewayGet('/api/v1/user/auth/url', {
|
||||
app_no: appNo,
|
||||
scopes: 'snsapi_userinfo',
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
const data = res.data || {};
|
||||
if (data.code === 0 && data.data && data.data.auth_url) {
|
||||
// 跳转微信授权页
|
||||
window.location.href = data.data.auth_url;
|
||||
} else {
|
||||
uni.showToast({ title: data.message || '获取授权失败', icon: 'none' });
|
||||
setTimeout(() => this.goToScan(), 1500);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Auth] startAuth error:', e);
|
||||
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
setTimeout(() => this.goToScan(), 1500);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理授权回调(从 URL 中获取 code 后调用后端 /login)
|
||||
*/
|
||||
async handleCallback(code) {
|
||||
const appNo = uni.getStorageSync('app_no') || getAppNo(detectPlatform());
|
||||
|
||||
if (!appNo) {
|
||||
uni.showToast({ title: '无法获取应用信息', icon: 'none' });
|
||||
this.goToScan();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.statusText = '正在验证身份...';
|
||||
|
||||
const res = await gatewayPost('/api/v1/user/login', {
|
||||
app_no: appNo,
|
||||
code: code,
|
||||
});
|
||||
|
||||
const data = res.data || {};
|
||||
if (data.code === 0 && data.data) {
|
||||
const result = data.data;
|
||||
|
||||
// 清除 URL 中的 code 参数,避免刷新重复处理
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete('code');
|
||||
url.searchParams.delete('state');
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
|
||||
if (result.register) {
|
||||
// 已注册 → 缓存 JWT → 跳转首页
|
||||
uni.setStorageSync('token', result.token);
|
||||
uni.setStorageSync('user_info', result.user_info);
|
||||
this.goToHome();
|
||||
} else {
|
||||
// 未注册 → 缓存临时数据 → 跳转注册页
|
||||
uni.setStorageSync('temp_token', result.temp_token);
|
||||
uni.setStorageSync('auth_info', result.auth_info);
|
||||
this.goToRegister();
|
||||
}
|
||||
} else {
|
||||
uni.showToast({ title: data.message || '登录失败', icon: 'none' });
|
||||
setTimeout(() => this.goToScan(), 1500);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Auth] login error:', e);
|
||||
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
setTimeout(() => this.goToScan(), 1500);
|
||||
}
|
||||
},
|
||||
|
||||
goToHome() {
|
||||
const url = this.deviceId
|
||||
? `/pages/index/index?device_id=${this.deviceId}`
|
||||
: '/pages/index/index';
|
||||
uni.redirectTo({ url });
|
||||
},
|
||||
|
||||
goToRegister() {
|
||||
const url = this.deviceId
|
||||
? `/pages/register/register?device_id=${this.deviceId}`
|
||||
: '/pages/register/register';
|
||||
uni.redirectTo({ url });
|
||||
},
|
||||
|
||||
goToScan() {
|
||||
uni.redirectTo({ url: '/pages/index/scan' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page {
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.loading-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border: 6rpx solid #e5e5e5;
|
||||
border-top-color: #2979ff;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<view class="faq-page">
|
||||
<!-- 问题列表 -->
|
||||
<scroll-view class="faq-scroll" scroll-y>
|
||||
<view class="faq-list">
|
||||
<view
|
||||
class="faq-item"
|
||||
v-for="(item, index) in faqList"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 问题标题(点击展开/收起) -->
|
||||
<view class="faq-question" @click="toggleItem(index)">
|
||||
<text class="q-text">{{ item.question }}</text>
|
||||
<text class="q-arrow" :class="{ expanded: item.expanded }">›</text>
|
||||
</view>
|
||||
|
||||
<!-- 答案内容(展开时显示) -->
|
||||
<view class="faq-answer" v-if="item.expanded">
|
||||
<text class="a-text">{{ item.answer }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bottom-spacer"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部左右箭头导航条 -->
|
||||
<page-nav-bar
|
||||
:hasPrev="true"
|
||||
:hasNext="false"
|
||||
@prev="handleNavPrev"
|
||||
@next="handleNavNext"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageNavBar from '@/components/page-nav-bar/page-nav-bar.vue';
|
||||
|
||||
export default {
|
||||
components: { PageNavBar },
|
||||
data() {
|
||||
return {
|
||||
faqList: [
|
||||
{
|
||||
question: '如何购买商品?',
|
||||
answer: '在首页浏览商品,点击商品卡片选择商品,确认后即可完成购买。支持微信支付和支付宝支付。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何开门取货?',
|
||||
answer: '下单成功后,点击首页底部中间的"开门"按钮,柜门会自动打开,取出商品后关闭柜门即可。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '商品质量问题怎么办?',
|
||||
answer: '如发现商品有质量问题,请在24小时内通过"联系客服"反馈,我们会为您办理退款或换货。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何查看订单?',
|
||||
answer: '进入"我的"页面,点击"我的订单"可查看所有订单记录,包括待付款、已完成、已退款等状态。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '忘记密码怎么办?',
|
||||
answer: '在登录页面点击"忘记密码",输入注册手机号,通过短信验证码即可重置密码。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '如何修改收货地址?',
|
||||
answer: '进入"我的"页面,点击"收货地址",可以新增、编辑或删除收货地址信息。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '支持哪些支付方式?',
|
||||
answer: '目前支持微信支付、支付宝支付两种方式,后续将开通更多支付渠道。',
|
||||
expanded: false
|
||||
},
|
||||
{
|
||||
question: '退款多久到账?',
|
||||
answer: '退款申请审核通过后,微信支付1-3个工作日到账,支付宝1-5个工作日到账,具体以银行处理时间为准。',
|
||||
expanded: false
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleItem(index) {
|
||||
this.faqList[index].expanded = !this.faqList[index].expanded;
|
||||
},
|
||||
handleNavPrev() {
|
||||
uni.navigateBack({ fail: () => { uni.switchTab({ url: '/pages/index/index' }); } });
|
||||
},
|
||||
handleNavNext() {
|
||||
uni.showToast({ title: '暂无下一页', icon: 'none' });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page { height: 100%; background-color: #f5f5f5; }
|
||||
|
||||
.faq-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 滚动区域 */
|
||||
.faq-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 问题列表 */
|
||||
.faq-list {
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.faq-item {
|
||||
background: #fff;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 问题标题 */
|
||||
.faq-question {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 24rpx;
|
||||
}
|
||||
.q-text {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.q-arrow {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
transition: transform 0.25s;
|
||||
flex-shrink: 0;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
.q-arrow.expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* 答案 */
|
||||
.faq-answer {
|
||||
margin: 0 24rpx 24rpx;
|
||||
padding: 24rpx;
|
||||
background: #f8f9fc;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
.a-text {
|
||||
font-size: 27rpx;
|
||||
color: #666;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* 底部占位 */
|
||||
.bottom-spacer {
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,510 @@
|
||||
<template>
|
||||
<view class="app-container">
|
||||
<!-- ========== 首页内容 ========== -->
|
||||
<view v-if="currentTab === 'home'" class="page-content home-page">
|
||||
<scroll-view
|
||||
class="product-area"
|
||||
scroll-y
|
||||
>
|
||||
<!-- 热销商品标题 + 装饰 -->
|
||||
<view class="section-title-wrap">
|
||||
<view class="section-title">热销商品</view>
|
||||
<view class="title-decoration">
|
||||
<view class="deco-dot"></view>
|
||||
<view class="deco-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 分类 Tab 横向滚动 -->
|
||||
<scroll-view class="category-tabs" scroll-x>
|
||||
<view class="tabs-inner">
|
||||
<view class="tab-item" :class="{ active: activeCategory === 'all' }" @click="switchCategory('all')">
|
||||
<text class="tab-text">全部商品</text>
|
||||
<view class="tab-underline"></view>
|
||||
</view>
|
||||
<view
|
||||
v-for="(cat, idx) in categoryList"
|
||||
:key="idx"
|
||||
class="tab-item"
|
||||
:class="{ active: activeCategory === cat.category }"
|
||||
@click="switchCategory(cat.category)"
|
||||
>
|
||||
<text class="tab-text">{{ cat.category }}</text>
|
||||
<view class="tab-underline"></view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 商品网格 -->
|
||||
<view class="product-grid">
|
||||
<view
|
||||
v-for="(item, idx) in displayProducts"
|
||||
:key="idx"
|
||||
class="product-card"
|
||||
>
|
||||
<image class="product-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="product-name">{{ item.name }}</view>
|
||||
<view class="product-price">
|
||||
<text class="price-yen">¥</text>
|
||||
<text class="price-num">{{ getDisplayPrice(item) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 无商品时 -->
|
||||
<view v-if="categoryList.length === 0 && !loading" class="empty-tip">
|
||||
<text>暂无商品</text>
|
||||
</view>
|
||||
|
||||
<!-- loading -->
|
||||
<view v-if="loading" class="loading-tip">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="bottom-spacer"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-bar-row">
|
||||
<view class="tab-item" :class="{ active: currentTab === 'home' }" @click="switchTab('home')">
|
||||
<view class="home-icon">
|
||||
<view class="home-roof"></view>
|
||||
<view class="home-body"></view>
|
||||
</view>
|
||||
<text class="tab-text">首页</text>
|
||||
</view>
|
||||
|
||||
<view class="door-btn-wrapper" @click="handleOpenDoor">
|
||||
<view class="door-btn">开门</view>
|
||||
</view>
|
||||
|
||||
<view class="tab-item" :class="{ active: currentTab === 'mine' }" @click="switchTab('mine')">
|
||||
<view class="mine-icon">
|
||||
<view class="mine-head"></view>
|
||||
<view class="mine-body"></view>
|
||||
</view>
|
||||
<text class="tab-text">我的</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付分信息(仅微信环境显示) -->
|
||||
<view v-if="isWechat" class="payscore-info">
|
||||
<image class="payscore-logo" src="/static/payscore-logo.png" mode="aspectFit" />
|
||||
<view class="payscore-divider"></view>
|
||||
<text class="payscore-text">微信支付分 | 550分及以上优享</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ========== 我的内容 ========== -->
|
||||
<view v-if="currentTab === 'mine'" class="page-content mine-page">
|
||||
<!-- 蓝色个人信息区域 -->
|
||||
<view class="user-header">
|
||||
<view class="user-info">
|
||||
<view class="avatar-wrapper">
|
||||
<image class="avatar" :src="userInfo.avatar || '/static/default-avatar.png'" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="user-detail">
|
||||
<text class="nickname">{{ userInfo.nickname || '未登录' }}</text>
|
||||
<text class="phone">{{ maskedPhone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 菜单列表 -->
|
||||
<view class="content-area">
|
||||
<view class="menu-list">
|
||||
<view class="menu-item" v-for="(item, index) in menuList" :key="index" @click="handleMenuClick(item)">
|
||||
<text class="menu-text">{{ item.title }}</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 开发调试工具(仅开发环境显示) -->
|
||||
<view v-if="isDev" class="dev-tools">
|
||||
<view class="dev-tools-title">开发工具</view>
|
||||
<view class="menu-item">
|
||||
<text class="menu-text">vConsole 调试</text>
|
||||
<switch :checked="vconsoleEnabled" color="#2979ff" @change="onVConsoleChange" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部左右箭头导航条 -->
|
||||
<page-nav-bar
|
||||
:hasPrev="true"
|
||||
:hasNext="false"
|
||||
@prev="handleNavPrev"
|
||||
@next="handleNavNext"
|
||||
/>
|
||||
|
||||
<!-- 公众号二维码弹窗 -->
|
||||
<view v-if="showQrcodePopup" class="qrcode-popup-mask" @click="showQrcodePopup = false">
|
||||
<view class="qrcode-popup-content" @click.stop>
|
||||
<view class="qrcode-popup-title">关注公众号</view>
|
||||
<image class="qrcode-img" src="/static/qrcode.png" mode="aspectFit" />
|
||||
<view class="qrcode-popup-tip">扫码关注获取更多优惠信息</view>
|
||||
<view class="qrcode-popup-close" @click="showQrcodePopup = false">关闭</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageNavBar from '@/components/page-nav-bar/page-nav-bar.vue';
|
||||
import config from '@/config/env.js';
|
||||
import { get } from '@/utils/request.js';
|
||||
import { checkLoginWithPrompt, getCachedUserInfo } from '@/utils/auth-guard.js';
|
||||
import { isVConsoleEnabled, showVConsole, hideVConsole } from '@/utils/vconsole';
|
||||
|
||||
export default {
|
||||
components: { PageNavBar },
|
||||
data() {
|
||||
return {
|
||||
deviceId: '',
|
||||
currentTab: 'home',
|
||||
showQrcodePopup: false,
|
||||
loading: false,
|
||||
categoryList: [],
|
||||
activeCategory: 'all',
|
||||
userInfo: { nickname: '', phone: '', avatar: '' },
|
||||
menuList: [
|
||||
{ title: '我的订单', action: 'order' },
|
||||
{ title: '常见问题', action: 'faq' },
|
||||
{ title: '联系客服', action: 'call' },
|
||||
{ title: '公众号信息', action: 'qrcode' }
|
||||
],
|
||||
servicePhone: config.servicePhone,
|
||||
isWechat: false,
|
||||
isDev: process.env.NODE_ENV === 'development',
|
||||
vconsoleEnabled: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
maskedPhone() {
|
||||
const p = this.userInfo.phone;
|
||||
return p ? p.substring(0,3) + '****' + p.substring(7) : '';
|
||||
},
|
||||
// 全部商品:合并所有分类下的商品
|
||||
allProducts() {
|
||||
let all = [];
|
||||
this.categoryList.forEach(cat => {
|
||||
if (cat.products) all = all.concat(cat.products);
|
||||
});
|
||||
return all;
|
||||
},
|
||||
// 当前显示的商品列表(根据选中的分类 tab)
|
||||
displayProducts() {
|
||||
if (this.activeCategory === 'all') return this.allProducts;
|
||||
const cat = this.categoryList.find(c => c.category === this.activeCategory);
|
||||
return cat ? cat.products || [] : [];
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 检测是否在微信环境中打开
|
||||
// #ifdef MP-WEIXIN
|
||||
this.isWechat = true;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
this.isWechat = /MicroMessenger/i.test(navigator.userAgent);
|
||||
// #endif
|
||||
|
||||
// 支持通过 tab 参数切换到"我的"页面
|
||||
if (options.tab === 'mine') {
|
||||
this.currentTab = 'mine';
|
||||
}
|
||||
|
||||
if (options.device_id) {
|
||||
this.deviceId = options.device_id;
|
||||
this.fetchProducts();
|
||||
} else if (!options.tab) {
|
||||
// 无设备编号且非 tab 跳转,跳回扫码页
|
||||
uni.redirectTo({ url: '/pages/index/scan' });
|
||||
}
|
||||
|
||||
// 加载缓存的用户信息
|
||||
this.loadUserInfo();
|
||||
|
||||
// 初始化 vConsole 状态
|
||||
this.vconsoleEnabled = isVConsoleEnabled();
|
||||
},
|
||||
onShow() {
|
||||
// 每次显示页面时刷新用户信息(授权回调后可能已更新)
|
||||
this.loadUserInfo();
|
||||
},
|
||||
onReady() {
|
||||
// 页面渲染完成后再次检查状态
|
||||
this.vconsoleEnabled = isVConsoleEnabled();
|
||||
},
|
||||
methods: {
|
||||
switchTab(tab) {
|
||||
// 切换到"我的"Tab 时检查登录
|
||||
if (tab === 'mine' && !checkLoginWithPrompt('mine')) return;
|
||||
this.currentTab = tab;
|
||||
},
|
||||
switchCategory(cat) { this.activeCategory = cat; },
|
||||
getDisplayPrice(item) {
|
||||
// 优先显示促销价,没有促销价则显示销售价
|
||||
if (item.promotion_price != null) return item.promotion_price;
|
||||
return item.sale_price;
|
||||
},
|
||||
// 从缓存加载用户信息
|
||||
loadUserInfo() {
|
||||
const cached = getCachedUserInfo();
|
||||
if (cached) {
|
||||
this.userInfo = { ...this.userInfo, ...cached };
|
||||
} else {
|
||||
this.userInfo = { nickname: '', phone: '', avatar: '' };
|
||||
}
|
||||
},
|
||||
async fetchProducts() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const res = await get('/device-product/getProductList', {
|
||||
device_id: this.deviceId,
|
||||
});
|
||||
|
||||
const ret = res.data || {};
|
||||
if (ret.code === 0 && Array.isArray(ret.data)) {
|
||||
this.categoryList = ret.data;
|
||||
} else {
|
||||
uni.showToast({ title: ret.message || '获取商品失败', icon: 'none' });
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
handleOpenDoor() {
|
||||
uni.showModal({
|
||||
title: '开门',
|
||||
content: '确认打开柜门?',
|
||||
success: r => r.confirm && uni.showToast({ title: '柜门已打开', icon: 'success' })
|
||||
});
|
||||
},
|
||||
handleMenuClick(item) {
|
||||
switch (item.action) {
|
||||
case 'order':
|
||||
// 查看订单需要登录
|
||||
if (!checkLoginWithPrompt('order')) return;
|
||||
uni.navigateTo({ url: '/pages/order/order' });
|
||||
break;
|
||||
case 'faq':
|
||||
uni.navigateTo({ url: '/pages/faq/faq' });
|
||||
break;
|
||||
case 'call':
|
||||
uni.makePhoneCall({ phoneNumber: this.servicePhone, fail: () => {} });
|
||||
break;
|
||||
case 'qrcode':
|
||||
this.showQrcodePopup = true;
|
||||
break;
|
||||
}
|
||||
},
|
||||
handleNavPrev() { this.currentTab = 'home'; },
|
||||
handleNavNext() { uni.showToast({ title: '暂无下一页', icon: 'none' }); },
|
||||
onVConsoleChange(e) {
|
||||
const enabled = e.detail.value;
|
||||
if (enabled) {
|
||||
showVConsole();
|
||||
} else {
|
||||
hideVConsole();
|
||||
}
|
||||
this.vconsoleEnabled = enabled;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page { height: 100%; background-color: #fff; }
|
||||
.app-container { display: flex; flex-direction: column; height: 100vh; background-color: #fff; }
|
||||
.page-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; position: relative; }
|
||||
|
||||
/* ====== 首页样式 ====== */
|
||||
.home-page { display: flex; flex-direction: column; height: 100%; }
|
||||
.product-area { flex: 1; overflow-y: auto; height: 0; }
|
||||
|
||||
/* 热销商品标题 + 装饰 */
|
||||
.section-title-wrap { display: flex; align-items: center; padding: 24rpx 28rpx 16rpx; }
|
||||
.section-title { font-size: 36rpx; font-weight: bold; color: #333; }
|
||||
.title-decoration { display: flex; align-items: center; margin-left: 16rpx; }
|
||||
.deco-dot { width: 12rpx; height: 12rpx; background: #ff6b6b; border-radius: 50%; }
|
||||
.deco-line { width: 40rpx; height: 6rpx; background: linear-gradient(90deg, #ff6b6b, transparent); margin-left: 6rpx; border-radius: 3rpx; }
|
||||
|
||||
/* 分类 Tab 横向滚动 */
|
||||
.category-tabs { white-space: nowrap; padding: 0 20rpx; border-bottom: 1rpx solid #f0f0f0; }
|
||||
.tabs-inner { display: inline-flex; padding: 0 8rpx; }
|
||||
.tab-item { display: inline-flex; flex-direction: column; align-items: center; padding: 20rpx 24rpx; position: relative; }
|
||||
.tab-text { font-size: 28rpx; color: #666; transition: color 0.2s; }
|
||||
.tab-item.active .tab-text { color: #2979ff; font-weight: bold; }
|
||||
.tab-underline { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); width: 0; height: 4rpx; background: #2979ff; border-radius: 2rpx; transition: width 0.2s; }
|
||||
.tab-item.active .tab-underline { width: 48rpx; }
|
||||
|
||||
/* 商品网格 */
|
||||
.product-grid { display: flex; flex-wrap: wrap; padding: 12rpx; gap: 12rpx; }
|
||||
.product-card { width: calc((100% - 24rpx) / 3); text-align: center; }
|
||||
.product-img { width: 100%; height: 220rpx; border-radius: 8rpx; background-color: #fff; }
|
||||
.product-name { font-size: 24rpx; color: #333; padding: 10rpx 8rpx 6rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.product-price { font-size: 30rpx; font-weight: bold; color: #e4393c; padding: 0 8rpx 16rpx; }
|
||||
.price-yen { font-size: 22rpx; }
|
||||
.price-num { font-size: 32rpx; }
|
||||
|
||||
/* 底部占位 */
|
||||
.bottom-spacer { height: 200rpx; }
|
||||
|
||||
/* 底部导航栏 */
|
||||
.tab-bar { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; display: flex; flex-direction: column; align-items: center; box-shadow: 0 -2rpx 16rpx rgba(0,0,0,0.08); z-index: 999; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); }
|
||||
.tab-bar-row { width: 100%; height: 120rpx; display: flex; align-items: center; justify-content: space-around; }
|
||||
.tab-bar .tab-item { flex: 1; display: flex; align-items: center; justify-content: center; height: 100%; }
|
||||
.tab-bar .tab-text { font-size: 28rpx; color: #999; font-weight: normal; }
|
||||
.tab-bar .tab-item.active .tab-text { color: #2979ff; }
|
||||
|
||||
/* 首页图标 */
|
||||
.tab-bar .home-icon {
|
||||
width: 40rpx;
|
||||
height: 36rpx;
|
||||
position: relative;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
.tab-bar .home-roof {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 22rpx solid transparent;
|
||||
border-right: 22rpx solid transparent;
|
||||
border-bottom: 18rpx solid #999;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.tab-bar .home-body {
|
||||
width: 30rpx;
|
||||
height: 18rpx;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 0 0 3rpx 3rpx;
|
||||
}
|
||||
.tab-bar .tab-item.active .home-roof {
|
||||
border-bottom-color: #2979ff;
|
||||
}
|
||||
.tab-bar .tab-item.active .home-body {
|
||||
background-color: #2979ff;
|
||||
}
|
||||
|
||||
/* 我的图标(与扫一扫页共用) */
|
||||
.tab-bar .mine-icon {
|
||||
width: 40rpx;
|
||||
height: 36rpx;
|
||||
position: relative;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
.tab-bar .mine-head {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.tab-bar .mine-body {
|
||||
width: 28rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 14rpx 14rpx 0 0;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.tab-bar .tab-item.active .mine-head,
|
||||
.tab-bar .tab-item.active .mine-body {
|
||||
background-color: #2979ff;
|
||||
}
|
||||
.door-btn-wrapper { position: relative; display: flex; align-items: center; justify-content: center; margin-top: -80rpx; }
|
||||
.door-btn { width: 160rpx; height: 160rpx; border-radius: 50%; background: linear-gradient(135deg, #2979ff, #1e60e0); color: #fff; font-size: 38rpx; font-weight: bold; display: flex; align-items: center; justify-content: center; box-shadow: 0 10rpx 36rpx rgba(41,121,255,0.55); }
|
||||
.payscore-info { display: flex; align-items: center; padding-bottom: 16rpx; }
|
||||
.payscore-logo { width: 34rpx; height: 34rpx; }
|
||||
.payscore-divider { width: 2rpx; height: 24rpx; background-color: rgba(102, 102, 102, 0.6); margin: 0 10rpx; }
|
||||
.payscore-text { font-size: 24rpx; color: #666; white-space: nowrap; }
|
||||
|
||||
/* 空状态 / Loading */
|
||||
.empty-tip, .loading-tip { text-align: center; padding: 60rpx; color: #999; font-size: 28rpx; }
|
||||
|
||||
/* ====== 我的页面样式 ====== */
|
||||
.mine-page { background-color: #f5f5f5; position: relative; overflow-y: auto; }
|
||||
.user-header { background: linear-gradient(135deg, #2979ff, #1e60e0); padding: 60rpx 40rpx; }
|
||||
.user-info { display: flex; align-items: center; }
|
||||
.avatar { width: 120rpx; height: 120rpx; border-radius: 50%; border: 4rpx solid rgba(255,255,255,0.8); background-color: #e0e0e0; }
|
||||
.avatar-wrapper { margin-right: 30rpx; }
|
||||
.user-detail { display: flex; flex-direction: column; flex: 1; }
|
||||
.nickname { font-size: 36rpx; font-weight: bold; color: #fff; margin-bottom: 12rpx; }
|
||||
.phone { font-size: 28rpx; color: rgba(255,255,255,0.9); }
|
||||
.content-area { padding: 20rpx; padding-bottom: 140rpx; }
|
||||
.menu-list { background-color: #fff; border-radius: 16rpx; overflow: hidden; }
|
||||
.menu-item { display: flex; justify-content: space-between; padding: 32rpx 28rpx; border-bottom: 1rpx solid #f0f0f0; }
|
||||
.menu-item:last-child { border-bottom: none; }
|
||||
.menu-text { font-size: 30rpx; color: #333; }
|
||||
.menu-arrow { font-size: 28rpx; color: #ccc; }
|
||||
|
||||
/* 开发工具区域 */
|
||||
.dev-tools { margin-top: 20rpx; background-color: #fff; border-radius: 16rpx; overflow: hidden; }
|
||||
.dev-tools-title { padding: 20rpx 28rpx 10rpx; font-size: 24rpx; color: #999; }
|
||||
.dev-tools .menu-item { align-items: center; }
|
||||
.dev-tools switch { transform: scale(0.8); }
|
||||
|
||||
/* ====== 公众号二维码弹窗 ====== */
|
||||
.qrcode-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.qrcode-popup-content {
|
||||
width: 560rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 48rpx 40rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.qrcode-popup-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.qrcode-img {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
.qrcode-popup-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.qrcode-popup-close {
|
||||
font-size: 28rpx;
|
||||
color: #2979ff;
|
||||
padding: 16rpx 60rpx;
|
||||
border: 2rpx solid #2979ff;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<view class="scan-page">
|
||||
<!-- 顶部品牌 -->
|
||||
<view class="brand-area">
|
||||
<text class="brand-name">智联方舟</text>
|
||||
</view>
|
||||
|
||||
<!-- 中间扫一扫按钮 -->
|
||||
<view class="scan-area">
|
||||
<view class="scan-btn" @click="handleScan">
|
||||
<!-- 二维码图标 -->
|
||||
<view class="qr-icon">
|
||||
<view class="qr-row">
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-space"></view>
|
||||
<view class="qr-block"></view>
|
||||
</view>
|
||||
<view class="qr-row">
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-space"></view>
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-space"></view>
|
||||
</view>
|
||||
<view class="qr-row">
|
||||
<view class="qr-space"></view>
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-space"></view>
|
||||
<view class="qr-block"></view>
|
||||
</view>
|
||||
<view class="qr-row">
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-block"></view>
|
||||
<view class="qr-space"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="scan-text">扫一扫</text>
|
||||
</view>
|
||||
<text class="scan-tip">扫描货柜上的二维码开始购物</text>
|
||||
</view>
|
||||
|
||||
<!-- 我的入口 -->
|
||||
<view class="mine-entry" @click="goMine">
|
||||
<view class="mine-icon">
|
||||
<view class="mine-head"></view>
|
||||
<view class="mine-body"></view>
|
||||
</view>
|
||||
<text class="mine-text">我的</text>
|
||||
</view>
|
||||
|
||||
<!-- 底部客服电话 -->
|
||||
<view class="footer">
|
||||
<text class="footer-text">客服电话:</text>
|
||||
<text class="footer-phone" @click="callService">{{ servicePhone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/config/env.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
servicePhone: config.servicePhone,
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
// 1. 检查 JWT(App.vue 已处理首次跳转,这里兜底)
|
||||
const token = uni.getStorageSync('token');
|
||||
if (!token) {
|
||||
// 无 JWT → 跳转授权 Loading 页
|
||||
uni.redirectTo({ url: '/pages/auth/loading' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 有 JWT + query 参数中携带设备编号
|
||||
if (options.device_id) {
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/index?device_id=' + options.device_id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// 3. URL 路径中携带设备编号(如 http://xxx/A1036)
|
||||
const path = window.location.pathname;
|
||||
const segments = path.split('/').filter(Boolean);
|
||||
if (segments.length > 0) {
|
||||
const last = segments[segments.length - 1];
|
||||
if (/^[A-Z][A-Z0-9]+$/.test(last)) {
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/index?device_id=' + last,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
handleScan() {
|
||||
// #ifdef H5
|
||||
uni.showToast({ title: 'H5 环境暂不支持扫码', icon: 'none' });
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
uni.scanCode({
|
||||
onlyFromCamera: false,
|
||||
success: (res) => {
|
||||
this.parseScanResult(res.result);
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '扫码取消', icon: 'none' });
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
parseScanResult(result) {
|
||||
// 解析扫码结果,提取 device_id
|
||||
// 预期格式:https://domain/A1036 或 device_id=A1036
|
||||
let deviceId = '';
|
||||
|
||||
try {
|
||||
const url = new URL(result);
|
||||
// 取路径最后一段作为设备编号
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
if (pathParts.length > 0) {
|
||||
deviceId = pathParts[pathParts.length - 1];
|
||||
}
|
||||
} catch {
|
||||
// 非 URL 格式,尝试直接匹配
|
||||
if (/^[A-Z][A-Z0-9]+$/.test(result)) {
|
||||
deviceId = result;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceId) {
|
||||
uni.redirectTo({
|
||||
url: '/pages/index/index?device_id=' + deviceId,
|
||||
});
|
||||
} else {
|
||||
uni.showToast({ title: '无效的二维码', icon: 'none' });
|
||||
}
|
||||
},
|
||||
goMine() {
|
||||
uni.navigateTo({ url: '/pages/index/index?tab=mine' });
|
||||
},
|
||||
callService() {
|
||||
// #ifdef H5
|
||||
window.location.href = 'tel:' + this.servicePhone;
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.makePhoneCall({ phoneNumber: this.servicePhone, fail: () => {} });
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page {
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.scan-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background-color: #fff;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
/* 品牌区域 */
|
||||
.brand-area {
|
||||
margin-top: 200rpx;
|
||||
margin-bottom: 80rpx;
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-size: 96rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
letter-spacing: 12rpx;
|
||||
}
|
||||
|
||||
/* 扫一扫按钮 */
|
||||
.scan-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 80rpx;
|
||||
}
|
||||
|
||||
.scan-btn {
|
||||
width: 360rpx;
|
||||
height: 360rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #2979ff, #1e60e0);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 20rpx 60rpx rgba(41, 121, 255, 0.4);
|
||||
}
|
||||
|
||||
/* 二维码图标 */
|
||||
.qr-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.qr-row {
|
||||
display: flex;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.qr-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.qr-block {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background-color: #fff;
|
||||
margin-right: 6rpx;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.qr-space {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.scan-text {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.scan-tip {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
/* 我的入口 */
|
||||
.mine-entry {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 140rpx;
|
||||
}
|
||||
|
||||
.mine-icon {
|
||||
width: 60rpx;
|
||||
height: 54rpx;
|
||||
position: relative;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.mine-head {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.mine-body {
|
||||
width: 44rpx;
|
||||
height: 26rpx;
|
||||
border-radius: 22rpx 22rpx 0 0;
|
||||
background-color: #999;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.mine-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 底部客服 */
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 32rpx 0;
|
||||
padding-bottom: calc(32rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.footer-phone {
|
||||
font-size: 26rpx;
|
||||
color: #2979ff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<view class="app-container">
|
||||
<!-- 我的内容 -->
|
||||
<view class="page-content">
|
||||
<!-- 蓝色个人信息区域 -->
|
||||
<view class="user-header">
|
||||
<view class="user-info">
|
||||
<!-- 左侧圆形头像 -->
|
||||
<view class="avatar-wrapper">
|
||||
<image
|
||||
class="avatar"
|
||||
:src="userInfo.avatar || '/static/default-avatar.png'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<!-- 右侧信息 -->
|
||||
<view class="user-detail">
|
||||
<text class="nickname">{{ userInfo.nickname || '未登录' }}</text>
|
||||
<text class="phone">{{ maskedPhone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 菜单列表 -->
|
||||
<view class="content-area">
|
||||
<view class="menu-list">
|
||||
<view
|
||||
class="menu-item"
|
||||
v-for="(item, index) in menuList"
|
||||
:key="index"
|
||||
@click="handleMenuClick(item)"
|
||||
>
|
||||
<text class="menu-text">{{ item.title }}</text>
|
||||
<text class="menu-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航栏 -->
|
||||
<page-nav activeTab="mine" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageNav from '@/components/page-nav/page-nav.vue';
|
||||
import config from '@/config/env.js';
|
||||
import { getCachedUserInfo } from '@/utils/auth-guard.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PageNav
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userInfo: {
|
||||
avatar: '',
|
||||
nickname: '',
|
||||
phone: ''
|
||||
},
|
||||
menuList: [
|
||||
{ title: '我的订单', page: '/pages/order/order' },
|
||||
{ title: '收货地址', page: '/pages/address/address' },
|
||||
{ title: '常见问题', page: '/pages/faq/faq' },
|
||||
{ title: '联系客服', action: 'contact' }
|
||||
]
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
// 每次显示页面时刷新用户信息
|
||||
this.loadUserInfo();
|
||||
},
|
||||
computed: {
|
||||
maskedPhone() {
|
||||
const phone = this.userInfo.phone;
|
||||
if (!phone || phone.length < 11) return phone;
|
||||
return phone.substring(0, 3) + '****' + phone.substring(7);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadUserInfo() {
|
||||
const cached = getCachedUserInfo();
|
||||
if (cached) {
|
||||
this.userInfo = { ...this.userInfo, ...cached };
|
||||
}
|
||||
},
|
||||
handleMenuClick(item) {
|
||||
if (item.page) {
|
||||
uni.navigateTo({ url: item.page });
|
||||
} else if (item.action === 'contact') {
|
||||
uni.showModal({
|
||||
title: '联系客服',
|
||||
content: '客服电话:' + config.servicePhone,
|
||||
showCancel: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page {
|
||||
height: 100%;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
.user-header {
|
||||
background: linear-gradient(135deg, #2979ff, #1e60e0);
|
||||
padding: 80rpx 40rpx 60rpx;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
margin-right: 30rpx;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid rgba(255, 255, 255, 0.8);
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.user-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nickname {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.phone {
|
||||
font-size: 28rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.content-area {
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx 28rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.menu-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,648 @@
|
||||
<template>
|
||||
<view class="order-page">
|
||||
<!-- 顶部Tab切换 -->
|
||||
<view class="tab-header">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'all' }"
|
||||
@click="switchTab('all')"
|
||||
>
|
||||
<text class="tab-text">全部订单</text>
|
||||
<view class="tab-line" v-if="currentTab === 'all'"></view>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'paid' }"
|
||||
@click="switchTab('paid')"
|
||||
>
|
||||
<text class="tab-text">已支付</text>
|
||||
<view class="tab-line" v-if="currentTab === 'paid'"></view>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'unpaid' }"
|
||||
@click="switchTab('unpaid')"
|
||||
>
|
||||
<text class="tab-text">未支付</text>
|
||||
<view class="tab-line" v-if="currentTab === 'unpaid'"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 数量统计和筛选 -->
|
||||
<view class="filter-bar">
|
||||
<text class="total-count">总数量: {{ filteredOrders.length }}</text>
|
||||
<view class="search-btn" @click="showFilterSidebar = true">
|
||||
<text class="search-icon">🔍</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选条件显示 -->
|
||||
<view class="filter-condition" v-if="filterStartTime || filterEndTime">
|
||||
<text class="filter-text">{{ filterStartTime }} - {{ filterEndTime }}</text>
|
||||
<text class="filter-clear" @click="clearFilter">✕</text>
|
||||
</view>
|
||||
|
||||
<!-- 订单列表 -->
|
||||
<scroll-view class="order-scroll" scroll-y>
|
||||
<view class="order-list">
|
||||
<view
|
||||
class="order-card"
|
||||
v-for="(order, index) in filteredOrders"
|
||||
:key="index"
|
||||
>
|
||||
<!-- 第一行: 日期 + 状态 -->
|
||||
<view class="order-header">
|
||||
<text class="order-date">{{ order.create_time }}</text>
|
||||
<text class="order-status" :class="{ paid: order.status === 'paid', unpaid: order.status === 'unpaid' }">
|
||||
{{ order.status === 'paid' ? '已支付' : '未支付' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 商品列表(整体可点击跳转) -->
|
||||
<view class="product-section" @click="goToDetail(order)">
|
||||
<view class="product-list">
|
||||
<view
|
||||
class="product-item"
|
||||
v-for="(product, pIndex) in order.products"
|
||||
:key="pIndex"
|
||||
>
|
||||
<image class="product-img" :src="product.image" mode="aspectFill" />
|
||||
<view class="product-info">
|
||||
<text class="product-name">{{ product.name }}</text>
|
||||
<text class="product-price-qty">¥{{ product.price }} x {{ product.quantity }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="product-arrow">
|
||||
<text class="arrow-icon">›</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 总计 -->
|
||||
<view class="order-total">
|
||||
<text class="total-qty">共{{ order.totalQuantity }}件</text>
|
||||
<view class="total-amount">
|
||||
<text class="total-label">总计:</text>
|
||||
<text class="total-price">¥{{ order.totalAmount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="order-action">
|
||||
<view
|
||||
class="action-btn"
|
||||
:class="{ 'pay-btn': order.status === 'unpaid', 'refund-btn': order.status === 'paid' }"
|
||||
@click="handleAction(order)"
|
||||
>
|
||||
{{ order.status === 'unpaid' ? '立即支付' : '申请退款' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="filteredOrders.length === 0">
|
||||
<text class="empty-text">暂无订单</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom-spacer"></view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 筛选侧边栏 -->
|
||||
<view class="filter-sidebar-mask" v-if="showFilterSidebar" @click="showFilterSidebar = false">
|
||||
<view class="filter-sidebar" @click.stop>
|
||||
<view class="sidebar-header">
|
||||
<text class="sidebar-title">筛选条件</text>
|
||||
<text class="sidebar-close" @click="showFilterSidebar = false">✕</text>
|
||||
</view>
|
||||
<view class="sidebar-content">
|
||||
<view class="date-row">
|
||||
<text class="date-label">开始时间</text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="tempStartTime"
|
||||
@change="onStartTimeChange"
|
||||
>
|
||||
<view class="date-picker">
|
||||
<text class="date-value">{{ tempStartTime || '请选择' }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="date-row">
|
||||
<text class="date-label">结束时间</text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="tempEndTime"
|
||||
@change="onEndTimeChange"
|
||||
>
|
||||
<view class="date-picker">
|
||||
<text class="date-value">{{ tempEndTime || '请选择' }}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
<view class="sidebar-footer">
|
||||
<view class="sidebar-btn reset-btn" @click="resetFilter">
|
||||
<text class="btn-text">重置</text>
|
||||
</view>
|
||||
<view class="sidebar-btn confirm-btn" @click="confirmFilter">
|
||||
<text class="btn-text">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部导航条 -->
|
||||
<page-nav-bar
|
||||
:hasPrev="true"
|
||||
:hasNext="false"
|
||||
@prev="handleNavPrev"
|
||||
@next="handleNavNext"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageNavBar from '@/components/page-nav-bar/page-nav-bar.vue';
|
||||
import { checkLoginWithPrompt } from '@/utils/auth-guard.js';
|
||||
|
||||
export default {
|
||||
components: { PageNavBar },
|
||||
data() {
|
||||
return {
|
||||
currentTab: 'all',
|
||||
showFilterSidebar: false,
|
||||
tempStartTime: '',
|
||||
tempEndTime: '',
|
||||
filterStartTime: '',
|
||||
filterEndTime: '',
|
||||
// 模拟订单数据
|
||||
orderList: [
|
||||
{
|
||||
id: 1,
|
||||
create_time: '2026-04-30 10:25:30',
|
||||
status: 'paid',
|
||||
products: [
|
||||
{ name: '君乐宝450ml悦鲜活', price: 0.01, quantity: 2, image: '/static/c1.png' },
|
||||
{ name: '可口可乐330ml', price: 3.00, quantity: 1, image: '/static/c4.png' }
|
||||
],
|
||||
totalQuantity: 3,
|
||||
totalAmount: '0.04'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
create_time: '2026-04-29 15:12:45',
|
||||
status: 'unpaid',
|
||||
products: [
|
||||
{ name: '农夫山泉550ml', price: 2.00, quantity: 3, image: '/static/c5.png' }
|
||||
],
|
||||
totalQuantity: 3,
|
||||
totalAmount: '6.00'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
create_time: '2026-04-28 09:30:00',
|
||||
status: 'paid',
|
||||
products: [
|
||||
{ name: '乐事薯片原味75g', price: 7.50, quantity: 1, image: '/static/c6.png' },
|
||||
{ name: '白桔(0.0.0.0-5.5.5)', price: 0.05, quantity: 5, image: '/static/c3.png' }
|
||||
],
|
||||
totalQuantity: 6,
|
||||
totalAmount: '1.00'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
create_time: '2026-04-27 18:45:20',
|
||||
status: 'unpaid',
|
||||
products: [
|
||||
{ name: '白红(0.0.0.0-9.9.9)', price: 0.06, quantity: 10, image: '/static/c2.png' }
|
||||
],
|
||||
totalQuantity: 10,
|
||||
totalAmount: '0.60'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
create_time: '2026-04-25 12:00:00',
|
||||
status: 'paid',
|
||||
products: [
|
||||
{ name: '君乐宝450ml悦鲜活', price: 0.01, quantity: 1, image: '/static/c1.png' }
|
||||
],
|
||||
totalQuantity: 1,
|
||||
totalAmount: '0.01'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredOrders() {
|
||||
let list = this.orderList;
|
||||
|
||||
// Tab筛选
|
||||
if (this.currentTab === 'paid') {
|
||||
list = list.filter(o => o.status === 'paid');
|
||||
} else if (this.currentTab === 'unpaid') {
|
||||
list = list.filter(o => o.status === 'unpaid');
|
||||
}
|
||||
|
||||
// 日期筛选
|
||||
if (this.filterStartTime) {
|
||||
list = list.filter(o => o.create_time >= this.filterStartTime);
|
||||
}
|
||||
if (this.filterEndTime) {
|
||||
list = list.filter(o => o.create_time.split(' ')[0] <= this.filterEndTime);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 进入订单页需要登录
|
||||
if (!checkLoginWithPrompt('order')) return;
|
||||
},
|
||||
methods: {
|
||||
switchTab(tab) {
|
||||
this.currentTab = tab;
|
||||
},
|
||||
onStartTimeChange(e) {
|
||||
this.tempStartTime = e.detail.value;
|
||||
},
|
||||
onEndTimeChange(e) {
|
||||
this.tempEndTime = e.detail.value;
|
||||
},
|
||||
resetFilter() {
|
||||
this.tempStartTime = '';
|
||||
this.tempEndTime = '';
|
||||
},
|
||||
confirmFilter() {
|
||||
this.filterStartTime = this.tempStartTime;
|
||||
this.filterEndTime = this.tempEndTime;
|
||||
this.showFilterSidebar = false;
|
||||
},
|
||||
clearFilter() {
|
||||
this.filterStartTime = '';
|
||||
this.filterEndTime = '';
|
||||
},
|
||||
goToDetail(order) {
|
||||
uni.showToast({ title: '订单详情开发中', icon: 'none' });
|
||||
},
|
||||
handleAction(order) {
|
||||
if (order.status === 'unpaid') {
|
||||
// 支付需要登录
|
||||
if (!checkLoginWithPrompt('pay')) return;
|
||||
uni.showModal({
|
||||
title: '立即支付',
|
||||
content: `确认支付¥${order.totalAmount}?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '支付成功', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: '申请退款',
|
||||
content: '确认申请退款?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({ title: '退款申请已提交', icon: 'success' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
handleNavPrev() {
|
||||
uni.navigateBack({ fail: () => { uni.switchTab({ url: '/pages/index/index' }); } });
|
||||
},
|
||||
handleNavNext() {
|
||||
uni.showToast({ title: '暂无下一页', icon: 'none' });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page { height: 100%; background-color: #f5f5f5; }
|
||||
|
||||
.order-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 顶部Tab */
|
||||
.tab-header {
|
||||
display: flex;
|
||||
background: #fff;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 28rpx 0 20rpx;
|
||||
position: relative;
|
||||
}
|
||||
.tab-text {
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
}
|
||||
.tab-item.active .tab-text {
|
||||
color: #2979ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.tab-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 48rpx;
|
||||
height: 6rpx;
|
||||
background: #2979ff;
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
/* 筛选栏 */
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 28rpx;
|
||||
background: #fff;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.total-count {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
.search-btn {
|
||||
padding: 8rpx 16rpx;
|
||||
}
|
||||
.search-icon {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
/* 筛选条件显示 */
|
||||
.filter-condition {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 28rpx;
|
||||
background: #e8f4ff;
|
||||
}
|
||||
.filter-text {
|
||||
font-size: 26rpx;
|
||||
color: #2979ff;
|
||||
flex: 1;
|
||||
}
|
||||
.filter-clear {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
padding: 8rpx;
|
||||
}
|
||||
|
||||
/* 订单列表 */
|
||||
.order-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
.order-list {
|
||||
padding: 20rpx;
|
||||
}
|
||||
.order-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 订单头部 */
|
||||
.order-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.order-date {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
.order-status {
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.order-status.paid {
|
||||
color: #19be6b;
|
||||
}
|
||||
.order-status.unpaid {
|
||||
color: #e4393c;
|
||||
}
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background: #f0f0f0;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
/* 商品列表 */
|
||||
.product-section {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.product-list {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.product-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
.product-img {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.product-info {
|
||||
flex: 1;
|
||||
margin-left: 20rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
.product-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.product-price-qty {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
margin-top: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
.product-arrow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16rpx 0 20rpx;
|
||||
}
|
||||
.arrow-icon {
|
||||
font-size: 52rpx;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
/* 总计 */
|
||||
.order-total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 16rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.total-qty {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
.total-amount {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.total-label {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
.total-price {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #e4393c;
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.order-action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
.action-btn {
|
||||
padding: 16rpx 40rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.pay-btn {
|
||||
background: linear-gradient(135deg, #2979ff, #1e60e0);
|
||||
color: #fff;
|
||||
}
|
||||
.refund-btn {
|
||||
background: #fff;
|
||||
border: 2rpx solid #2979ff;
|
||||
color: #2979ff;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 100rpx 0;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 筛选侧边栏 */
|
||||
.filter-sidebar-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
.filter-sidebar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 600rpx;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.sidebar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 32rpx 28rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.sidebar-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
.sidebar-close {
|
||||
font-size: 36rpx;
|
||||
color: #999;
|
||||
padding: 8rpx;
|
||||
}
|
||||
.sidebar-content {
|
||||
flex: 1;
|
||||
padding: 28rpx;
|
||||
}
|
||||
.date-row {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.date-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
.date-picker {
|
||||
background: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
.date-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
.sidebar-footer {
|
||||
display: flex;
|
||||
padding: 28rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.sidebar-btn {
|
||||
flex: 1;
|
||||
padding: 24rpx 0;
|
||||
text-align: center;
|
||||
border-radius: 8rpx;
|
||||
margin: 0 8rpx;
|
||||
}
|
||||
.reset-btn {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.confirm-btn {
|
||||
background: #2979ff;
|
||||
}
|
||||
.reset-btn .btn-text {
|
||||
color: #666;
|
||||
}
|
||||
.confirm-btn .btn-text {
|
||||
color: #fff;
|
||||
}
|
||||
.btn-text {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
/* 底部占位 */
|
||||
.bottom-spacer {
|
||||
height: 120rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<view class="register-page">
|
||||
<!-- 顶部标题 -->
|
||||
<view class="header">
|
||||
<view class="title">用户注册</view>
|
||||
<view class="subtitle">请输入手机号获取验证码完成注册</view>
|
||||
</view>
|
||||
|
||||
<!-- 用户信息展示(来自微信授权) -->
|
||||
<view v-if="authInfo.nickname" class="auth-info">
|
||||
<image class="auth-avatar" :src="authInfo.avatar || '/static/default-avatar.png'" mode="aspectFill" />
|
||||
<text class="auth-nickname">{{ authInfo.nickname }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-area">
|
||||
<!-- 手机号 -->
|
||||
<view class="input-group">
|
||||
<view class="input-label">手机号</view>
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
class="input-field"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
placeholder="请输入手机号"
|
||||
placeholder-class="placeholder"
|
||||
v-model="phone"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 验证码 -->
|
||||
<view class="input-group">
|
||||
<view class="input-label">验证码</view>
|
||||
<view class="input-wrapper code-wrapper">
|
||||
<input
|
||||
class="input-field"
|
||||
type="number"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
placeholder-class="placeholder"
|
||||
v-model="code"
|
||||
/>
|
||||
<view
|
||||
class="code-btn"
|
||||
:class="{ disabled: countdown > 0 }"
|
||||
@click="handleGetCode"
|
||||
>
|
||||
{{ countdown > 0 ? countdown + 's' : '获取验证码' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 注册按钮 -->
|
||||
<view class="submit-btn" :class="{ active: canSubmit }" @click="handleRegister">
|
||||
注册
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部协议 -->
|
||||
<view class="agreement">
|
||||
<text class="agreement-text">注册表示同意</text>
|
||||
<text class="agreement-link" @click="showAgreement('user')">《用户协议》</text>
|
||||
<text class="agreement-text">和</text>
|
||||
<text class="agreement-link" @click="showAgreement('privacy')">《隐私政策》</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { gatewayPost } from '@/utils/request.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
phone: '',
|
||||
code: '',
|
||||
countdown: 0,
|
||||
timer: null,
|
||||
tempToken: '',
|
||||
authInfo: { nickname: '', avatar: '' },
|
||||
deviceId: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSubmit() {
|
||||
return this.phone.length === 11 && this.code.length >= 4;
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.deviceId = options.device_id || '';
|
||||
|
||||
// 从缓存读取授权信息(loading 页写入)
|
||||
this.tempToken = uni.getStorageSync('temp_token') || '';
|
||||
const cachedAuthInfo = uni.getStorageSync('auth_info');
|
||||
if (cachedAuthInfo) {
|
||||
this.authInfo = cachedAuthInfo;
|
||||
}
|
||||
|
||||
// 如果没有 temp_token,提示用户重新授权
|
||||
if (!this.tempToken) {
|
||||
uni.showToast({ title: '授权信息已过期,请重新登录', icon: 'none' });
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取验证码
|
||||
async handleGetCode() {
|
||||
if (this.countdown > 0) return;
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
|
||||
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.tempToken) {
|
||||
uni.showToast({ title: '授权信息已过期,请重新登录', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await gatewayPost('/api/v1/user/sms/send', {
|
||||
phone: this.phone,
|
||||
temp_token: this.tempToken,
|
||||
purpose: 'register',
|
||||
});
|
||||
|
||||
const ret = res.data || {};
|
||||
if (ret.code === 0) {
|
||||
uni.showToast({ title: '验证码已发送', icon: 'success' });
|
||||
this.startCountdown();
|
||||
} else {
|
||||
uni.showToast({ title: ret.message || '发送失败', icon: 'none' });
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
startCountdown() {
|
||||
this.countdown = 60;
|
||||
this.timer = setInterval(() => {
|
||||
this.countdown--;
|
||||
if (this.countdown <= 0) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
// 注册
|
||||
async handleRegister() {
|
||||
if (!this.canSubmit) return;
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
|
||||
uni.showToast({ title: '请输入正确的手机号', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.code.length < 4) {
|
||||
uni.showToast({ title: '请输入验证码', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.tempToken) {
|
||||
uni.showToast({ title: '授权信息已过期,请重新登录', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '注册中...' });
|
||||
|
||||
try {
|
||||
const res = await gatewayPost('/api/v1/user/register', {
|
||||
temp_token: this.tempToken,
|
||||
phone: this.phone,
|
||||
code: this.code,
|
||||
});
|
||||
|
||||
const ret = res.data || {};
|
||||
if (ret.code === 0 && ret.data) {
|
||||
// 保存 token 和用户信息
|
||||
uni.setStorageSync('token', ret.data.token);
|
||||
uni.setStorageSync('user_info', ret.data.user_info);
|
||||
|
||||
// 清除临时数据
|
||||
uni.removeStorageSync('temp_token');
|
||||
uni.removeStorageSync('auth_info');
|
||||
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '注册成功', icon: 'success' });
|
||||
|
||||
// 跳转首页
|
||||
setTimeout(() => {
|
||||
const url = this.deviceId
|
||||
? `/pages/index/index?device_id=${this.deviceId}`
|
||||
: '/pages/index/index';
|
||||
uni.redirectTo({ url });
|
||||
}, 1500);
|
||||
} else {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: ret.message || '注册失败', icon: 'none' });
|
||||
}
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
showAgreement(type) {
|
||||
const title = type === 'user' ? '用户协议' : '隐私政策';
|
||||
uni.showToast({ title: title + '开发中', icon: 'none' });
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page {
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.register-page {
|
||||
min-height: 100vh;
|
||||
background-color: #fff;
|
||||
padding: 0 60rpx;
|
||||
}
|
||||
|
||||
/* 顶部标题 */
|
||||
.header {
|
||||
padding-top: 180rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 微信授权信息 */
|
||||
.auth-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 0;
|
||||
margin-bottom: 40rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.auth-avatar {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.auth-nickname {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 表单区域 */
|
||||
.form-area {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 2rpx solid #e5e5e5;
|
||||
padding-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #ccc;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 验证码输入框 */
|
||||
.code-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
flex-shrink: 0;
|
||||
font-size: 28rpx;
|
||||
color: #2979ff;
|
||||
padding: 12rpx 24rpx;
|
||||
border: 2rpx solid #2979ff;
|
||||
border-radius: 8rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.code-btn.disabled {
|
||||
color: #999;
|
||||
border-color: #e5e5e5;
|
||||
}
|
||||
|
||||
/* 注册按钮 */
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: #e5e5e5;
|
||||
border-radius: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: #fff;
|
||||
margin-top: 60rpx;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.submit-btn.active {
|
||||
background: linear-gradient(135deg, #2979ff, #1e60e0);
|
||||
box-shadow: 0 8rpx 24rpx rgba(41, 121, 255, 0.4);
|
||||
}
|
||||
|
||||
/* 底部协议 */
|
||||
.agreement {
|
||||
position: absolute;
|
||||
bottom: 80rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.agreement-text {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.agreement-link {
|
||||
font-size: 24rpx;
|
||||
color: #2979ff;
|
||||
}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 470 B After Width: | Height: | Size: 470 B |
|
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 511 B |
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 472 B After Width: | Height: | Size: 472 B |
|
Before Width: | Height: | Size: 545 B After Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 365 B After Width: | Height: | Size: 365 B |
|
Before Width: | Height: | Size: 587 B After Width: | Height: | Size: 587 B |
|
Before Width: | Height: | Size: 565 B After Width: | Height: | Size: 565 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 用户授权工具
|
||||
* 登录检查、授权跳转、退出登录
|
||||
*
|
||||
* 新流程:
|
||||
* - App.vue 检查 JWT,无则跳转 /pages/auth/loading
|
||||
* - loading 页发起 OAuth → 获取 code → 调用 /login
|
||||
* - 未注册用户跳转 /pages/register/register
|
||||
*/
|
||||
|
||||
/**
|
||||
* 检查是否已登录
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLoggedIn() {
|
||||
return !!uni.getStorageSync('token');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查登录状态,未登录时弹窗引导
|
||||
* @param {string} scene - 触发场景:'order' | 'mine' | 'pay'
|
||||
* @returns {boolean} 是否已登录
|
||||
*/
|
||||
export function checkLoginWithPrompt(scene = '') {
|
||||
if (isLoggedIn()) return true;
|
||||
|
||||
const SCENE_TIPS = {
|
||||
order: '登录后即可查看订单',
|
||||
mine: '登录后即可查看个人信息',
|
||||
pay: '登录后即可下单购买',
|
||||
};
|
||||
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: SCENE_TIPS[scene] || '需要登录后操作',
|
||||
confirmText: '去登录',
|
||||
cancelText: '暂不',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 跳转到授权 Loading 页
|
||||
uni.redirectTo({ url: '/pages/auth/loading' });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查登录状态,未登录时静默跳转
|
||||
* @returns {boolean} 是否已登录
|
||||
*/
|
||||
export function requireLogin() {
|
||||
if (isLoggedIn()) return true;
|
||||
uni.redirectTo({ url: '/pages/auth/loading' });
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export function logout() {
|
||||
uni.removeStorageSync('token');
|
||||
uni.removeStorageSync('user_info');
|
||||
uni.removeStorageSync('auth_info');
|
||||
uni.removeStorageSync('temp_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的用户信息
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
export function getCachedUserInfo() {
|
||||
try {
|
||||
return uni.getStorageSync('user_info') || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 环境检测工具
|
||||
* 检测当前运行平台(微信/支付宝/普通浏览器),映射到对应的 app_no
|
||||
*/
|
||||
|
||||
// app_no 编码表:platform + terminalType → app_no
|
||||
const APP_NO_MAP = {
|
||||
'wechat_h5': '60000001102001',
|
||||
'wechat_mp': '600102101',
|
||||
'alipay_h5': '600112001',
|
||||
'alipay_mp': '600112101',
|
||||
};
|
||||
|
||||
/**
|
||||
* 检测当前运行环境
|
||||
* @returns {'wechat' | 'alipay' | 'browser'}
|
||||
*/
|
||||
export function detectPlatform() {
|
||||
// #ifdef H5
|
||||
const ua = navigator.userAgent;
|
||||
|
||||
if (/MicroMessenger/i.test(ua)) {
|
||||
return 'wechat';
|
||||
}
|
||||
if (/AlipayClient/i.test(ua)) {
|
||||
return 'alipay';
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
return 'wechat';
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
return 'alipay';
|
||||
// #endif
|
||||
|
||||
return 'browser';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测当前终端类型
|
||||
* @returns {'h5' | 'mp'}
|
||||
*/
|
||||
export function detectTerminalType() {
|
||||
// #ifdef H5
|
||||
return 'h5';
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN || MP-ALIPAY
|
||||
return 'mp';
|
||||
// #endif
|
||||
|
||||
return 'h5';
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据平台和终端类型获取 app_no
|
||||
* @param {string} platform - 'wechat' | 'alipay' | 'browser'
|
||||
* @param {string} terminalType - 'h5' | 'mp'
|
||||
* @returns {string} app_no
|
||||
*/
|
||||
export function getAppNo(platform, terminalType = 'h5') {
|
||||
if (platform === 'browser') return '';
|
||||
return APP_NO_MAP[`${platform}_${terminalType}`] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化环境检测并缓存到 storage
|
||||
* 应在 App.vue onLaunch 中调用
|
||||
*/
|
||||
export function initEnv() {
|
||||
const platform = detectPlatform();
|
||||
const terminalType = detectTerminalType();
|
||||
const appNo = getAppNo(platform, terminalType);
|
||||
|
||||
uni.setStorageSync('platform', platform);
|
||||
uni.setStorageSync('terminal_type', terminalType);
|
||||
if (appNo) {
|
||||
uni.setStorageSync('app_no', appNo);
|
||||
}
|
||||
|
||||
return { platform, terminalType, appNo };
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 统一请求封装
|
||||
* 自动注入 Authorization header,处理 401
|
||||
* app_no 通过接口参数传递,不在 header 中
|
||||
*/
|
||||
import config from '@/config/env.js';
|
||||
|
||||
/**
|
||||
* 发起请求
|
||||
* @param {Object} options - uni.request 选项
|
||||
* @param {string} options.url - 请求地址(可以是相对路径,会拼接 baseUrl)
|
||||
* @param {string} [options.method='GET'] - 请求方法
|
||||
* @param {Object} [options.data] - 请求参数
|
||||
* @param {Object} [options.header] - 额外请求头
|
||||
* @param {string} [options.baseUrl] - 基础地址,默认 apiBaseUrl;传 'gateway' 使用 apiGatewayUrl
|
||||
* @returns {Promise<Object>} 响应数据
|
||||
*/
|
||||
export function request(options = {}) {
|
||||
const token = uni.getStorageSync('token') || '';
|
||||
|
||||
// 确定 baseUrl
|
||||
let baseUrl = config.apiBaseUrl;
|
||||
if (options.baseUrl === 'gateway') {
|
||||
baseUrl = config.apiGatewayUrl;
|
||||
} else if (options.baseUrl) {
|
||||
baseUrl = options.baseUrl;
|
||||
}
|
||||
|
||||
// 拼接完整 URL
|
||||
let url = options.url || '';
|
||||
if (url && !url.startsWith('http')) {
|
||||
url = baseUrl + url;
|
||||
}
|
||||
|
||||
// 构建 header(app_no 不在 header 中传,通过参数传递)
|
||||
const header = {
|
||||
...options.header,
|
||||
};
|
||||
|
||||
// 公开接口(授权/登录/注册相关)不带 Authorization,避免 CORS 预检和 JWT 拦截
|
||||
const publicPaths = [
|
||||
'/api/v1/user/auth/url',
|
||||
'/api/v1/user/auth/callback',
|
||||
'/api/v1/user/login',
|
||||
'/api/v1/user/register',
|
||||
'/api/v1/user/sms/send',
|
||||
];
|
||||
const isPublic = publicPaths.some((p) => url.includes(p));
|
||||
|
||||
if (token && !isPublic) {
|
||||
header['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
// 开发环境请求日志
|
||||
if (config.enableRequestLog) {
|
||||
console.log(`[API] ${options.method || 'GET'} ${url}`, options.data || '');
|
||||
console.log('[API] Headers:', header);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data,
|
||||
header,
|
||||
timeout: config.requestTimeout,
|
||||
success: (res) => {
|
||||
if (config.enableRequestLog) {
|
||||
console.log('[API] StatusCode:', res.statusCode);
|
||||
console.log('[API] Response Headers:', res.header || res.headers);
|
||||
console.log('[API] Response Body:', res.data);
|
||||
}
|
||||
|
||||
// 401 → 清除登录状态
|
||||
if (res.statusCode === 401) {
|
||||
uni.removeStorageSync('token');
|
||||
uni.removeStorageSync('user_info');
|
||||
uni.removeStorageSync('temp_token');
|
||||
}
|
||||
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
if (config.enableRequestLog) {
|
||||
console.error('[API] Error:', err);
|
||||
}
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求(默认走业务 API)
|
||||
*/
|
||||
export function get(url, data, options = {}) {
|
||||
return request({ url, method: 'GET', data, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求(默认走业务 API)
|
||||
*/
|
||||
export function post(url, data, options = {}) {
|
||||
return request({ url, method: 'POST', data, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求(走网关)
|
||||
*/
|
||||
export function gatewayGet(url, data, options = {}) {
|
||||
return request({ url, method: 'GET', data, baseUrl: 'gateway', ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求(走网关)
|
||||
*/
|
||||
export function gatewayPost(url, data, options = {}) {
|
||||
return request({ url, method: 'POST', data, baseUrl: 'gateway', ...options });
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* vConsole 调试工具管理
|
||||
*
|
||||
* 使用方式:
|
||||
* 1. URL 参数:?vconsole=1 开启,?vconsole=0 关闭
|
||||
* 2. localStorage:设置 'vconsole' 为 '1' 开启,'0' 关闭
|
||||
* 3. 代码调用:import { showVConsole, hideVConsole, toggleVConsole } from '@/utils/vconsole'
|
||||
*/
|
||||
|
||||
let vConsoleInstance = null;
|
||||
|
||||
// 检测是否为开发环境(兼容多种方式)
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
/**
|
||||
* 获取 vConsole 是否开启
|
||||
*/
|
||||
export function isVConsoleEnabled() {
|
||||
if (!isDev) return false;
|
||||
|
||||
// 优先从 URL 参数获取(H5 环境直接读 window.location)
|
||||
// #ifdef H5
|
||||
try {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const vconsoleParam = urlParams.get('vconsole');
|
||||
if (vconsoleParam !== null) {
|
||||
return vconsoleParam === '1' || vconsoleParam === 'true';
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
try {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1];
|
||||
const options = currentPage.$page?.options || currentPage.options || {};
|
||||
if (options.vconsole !== undefined) {
|
||||
return options.vconsole === '1' || options.vconsole === 'true';
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 其次从 localStorage 获取
|
||||
try {
|
||||
return uni.getStorageSync('vconsole') === '1';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启 vConsole
|
||||
*/
|
||||
export function showVConsole() {
|
||||
if (vConsoleInstance) return;
|
||||
|
||||
// 动态加载 vConsole
|
||||
import('vconsole').then((module) => {
|
||||
const VConsole = module.default || module;
|
||||
vConsoleInstance = new VConsole({
|
||||
theme: 'dark',
|
||||
disableLogScrolling: false,
|
||||
buttonSize: 48,
|
||||
});
|
||||
|
||||
// 增强 vConsole 按钮样式,确保不被遮挡
|
||||
setTimeout(() => {
|
||||
const btn = document.querySelector('#__vconsole');
|
||||
if (btn) {
|
||||
btn.style.zIndex = '999999';
|
||||
btn.style.position = 'fixed';
|
||||
btn.style.bottom = '80px';
|
||||
btn.style.right = '10px';
|
||||
}
|
||||
}, 500);
|
||||
|
||||
uni.setStorageSync('vconsole', '1');
|
||||
}).catch((err) => {
|
||||
console.error('[vConsole] 加载失败:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭 vConsole
|
||||
*/
|
||||
export function hideVConsole() {
|
||||
if (vConsoleInstance) {
|
||||
vConsoleInstance.destroy();
|
||||
vConsoleInstance = null;
|
||||
}
|
||||
try {
|
||||
uni.setStorageSync('vconsole', '0');
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换 vConsole 状态
|
||||
*/
|
||||
export function toggleVConsole() {
|
||||
if (vConsoleInstance) {
|
||||
hideVConsole();
|
||||
} else {
|
||||
showVConsole();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置初始化 vConsole
|
||||
*/
|
||||
export function initVConsole() {
|
||||
if (!isDev) return;
|
||||
|
||||
if (isVConsoleEnabled()) {
|
||||
showVConsole();
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
isVConsoleEnabled,
|
||||
showVConsole,
|
||||
hideVConsole,
|
||||
toggleVConsole,
|
||||
initVConsole,
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
## 1.2.2(2023-01-28)
|
||||
- 修复 运行/打包 控制台警告问题
|
||||
## 1.2.1(2022-09-05)
|
||||
- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
|
||||
## 1.2.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
|
||||
## 1.1.7(2021-11-08)
|
||||
- 优化 升级ui
|
||||
- 修改 size 属性默认值调整为 small
|
||||
- 修改 type 属性,默认值调整为 error,info 替换 default
|
||||
## 1.1.6(2021-09-22)
|
||||
- 修复 在字节小程序上样式不生效的 bug
|
||||
## 1.1.5(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.4(2021-07-29)
|
||||
- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
|
||||
## 1.1.3(2021-06-24)
|
||||
- 优化 示例项目
|
||||
## 1.1.1(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.1.0(2021-05-12)
|
||||
- 新增 uni-badge 的 absolute 属性,支持定位
|
||||
- 新增 uni-badge 的 offset 属性,支持定位偏移
|
||||
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
|
||||
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
|
||||
- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
|
||||
## 1.0.7(2021-05-07)
|
||||
- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
|
||||
- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
|
||||
- 新增 uni-badge 属性 custom-style, 支持自定义样式
|
||||
## 1.0.6(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -1,268 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-badge--x">
|
||||
<slot />
|
||||
<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
|
||||
class="uni-badge" @click="onClick()">{{displayValue}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Badge 数字角标
|
||||
* @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
|
||||
* @property {String} text 角标内容
|
||||
* @property {String} size = [normal|small] 角标内容
|
||||
* @property {String} type = [info|primary|success|warning|error] 颜色类型
|
||||
* @value info 灰色
|
||||
* @value primary 蓝色
|
||||
* @value success 绿色
|
||||
* @value warning 黄色
|
||||
* @value error 红色
|
||||
* @property {String} inverted = [true|false] 是否无需背景颜色
|
||||
* @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
|
||||
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
|
||||
* @value rightTop 右上
|
||||
* @value rightBottom 右下
|
||||
* @value leftTop 左上
|
||||
* @value leftBottom 左下
|
||||
* @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px,[10, 10] 表示向 absolute 指定的内偏移 10px
|
||||
* @property {String} isDot = [true|false] 是否显示为一个小点
|
||||
* @event {Function} click 点击 Badge 触发事件
|
||||
* @example <uni-badge text="1"></uni-badge>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniBadge',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'error'
|
||||
},
|
||||
inverted: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isDot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: 99
|
||||
},
|
||||
absolute: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
offset: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [0, 0]
|
||||
}
|
||||
},
|
||||
text: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'small'
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
width() {
|
||||
return String(this.text).length * 8 + 12
|
||||
},
|
||||
classNames() {
|
||||
const {
|
||||
inverted,
|
||||
type,
|
||||
size,
|
||||
absolute
|
||||
} = this
|
||||
return [
|
||||
inverted ? 'uni-badge--' + type + '-inverted' : '',
|
||||
'uni-badge--' + type,
|
||||
'uni-badge--' + size,
|
||||
absolute ? 'uni-badge--absolute' : ''
|
||||
].join(' ')
|
||||
},
|
||||
positionStyle() {
|
||||
if (!this.absolute) return {}
|
||||
let w = this.width / 2,
|
||||
h = 10
|
||||
if (this.isDot) {
|
||||
w = 5
|
||||
h = 5
|
||||
}
|
||||
const x = `${- w + this.offset[0]}px`
|
||||
const y = `${- h + this.offset[1]}px`
|
||||
|
||||
const whiteList = {
|
||||
rightTop: {
|
||||
right: x,
|
||||
top: y
|
||||
},
|
||||
rightBottom: {
|
||||
right: x,
|
||||
bottom: y
|
||||
},
|
||||
leftBottom: {
|
||||
left: x,
|
||||
bottom: y
|
||||
},
|
||||
leftTop: {
|
||||
left: x,
|
||||
top: y
|
||||
}
|
||||
}
|
||||
const match = whiteList[this.absolute]
|
||||
return match ? match : whiteList['rightTop']
|
||||
},
|
||||
dotStyle() {
|
||||
if (!this.isDot) return {}
|
||||
return {
|
||||
width: '10px',
|
||||
minWidth: '0',
|
||||
height: '10px',
|
||||
padding: '0',
|
||||
borderRadius: '10px'
|
||||
}
|
||||
},
|
||||
displayValue() {
|
||||
const {
|
||||
isDot,
|
||||
text,
|
||||
maxNum
|
||||
} = this
|
||||
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" >
|
||||
$uni-primary: #2979ff !default;
|
||||
$uni-success: #4cd964 !default;
|
||||
$uni-warning: #f0ad4e !default;
|
||||
$uni-error: #dd524d !default;
|
||||
$uni-info: #909399 !default;
|
||||
|
||||
|
||||
$bage-size: 12px;
|
||||
$bage-small: scale(0.8);
|
||||
|
||||
.uni-badge--x {
|
||||
/* #ifdef APP-NVUE */
|
||||
// align-self: flex-start;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-badge--absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.uni-badge--small {
|
||||
transform: $bage-small;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.uni-badge {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
font-feature-settings: "tnum";
|
||||
min-width: 20px;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
height: 20px;
|
||||
padding: 0 4px;
|
||||
line-height: 18px;
|
||||
color: #fff;
|
||||
border-radius: 100px;
|
||||
background-color: $uni-info;
|
||||
background-color: transparent;
|
||||
border: 1px solid #fff;
|
||||
text-align: center;
|
||||
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||
font-size: $bage-size;
|
||||
/* #ifdef H5 */
|
||||
z-index: 999;
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
|
||||
&--info {
|
||||
color: #fff;
|
||||
background-color: $uni-info;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
background-color: $uni-success;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: $uni-warning;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: $uni-error;
|
||||
}
|
||||
|
||||
&--inverted {
|
||||
padding: 0 5px 0 0;
|
||||
color: $uni-info;
|
||||
}
|
||||
|
||||
&--info-inverted {
|
||||
color: $uni-info;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--primary-inverted {
|
||||
color: $uni-primary;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--success-inverted {
|
||||
color: $uni-success;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--warning-inverted {
|
||||
color: $uni-warning;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--error-inverted {
|
||||
color: $uni-error;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"id": "uni-badge",
|
||||
"displayName": "uni-badge 数字角标",
|
||||
"version": "1.2.2",
|
||||
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
|
||||
"keywords": [
|
||||
"",
|
||||
"badge",
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"数字角标",
|
||||
"徽章"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-scss"],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "y",
|
||||
"联盟": "y"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
## Badge 数字角标
|
||||
> **组件名:uni-badge**
|
||||
> 代码块: `uBadge`
|
||||
|
||||
数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
## 0.1.2(2022-06-08)
|
||||
- 修复 微信小程序 separator 不显示的Bug
|
||||
## 0.1.1(2022-06-02)
|
||||
- 新增 支持 uni.scss 修改颜色
|
||||
## 0.1.0(2022-04-21)
|
||||
- 初始化
|
||||
@@ -1,126 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-breadcrumb-item">
|
||||
<view :class="{
|
||||
'uni-breadcrumb-item--slot': true,
|
||||
'uni-breadcrumb-item--slot-link': to && currentPage !== to
|
||||
}" @click="navTo">
|
||||
<slot />
|
||||
</view>
|
||||
<i v-if="separatorClass" class="uni-breadcrumb-item--separator" :class="separatorClass" />
|
||||
<text v-else class="uni-breadcrumb-item--separator">{{ separator }}</text>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* BreadcrumbItem 面包屑导航子组件
|
||||
* @property {String/Object} to 路由跳转页面路径/对象
|
||||
* @property {Boolean} replace 在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持)
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentPage: ""
|
||||
}
|
||||
},
|
||||
options: {
|
||||
// #ifdef MP-TOUTIAO
|
||||
virtualHost: false,
|
||||
// #endif
|
||||
// #ifndef MP-TOUTIAO
|
||||
virtualHost: true
|
||||
// #endif
|
||||
},
|
||||
props: {
|
||||
to: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
replace:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
uniBreadcrumb: {
|
||||
from: "uniBreadcrumb",
|
||||
default: null
|
||||
}
|
||||
},
|
||||
created(){
|
||||
const pages = getCurrentPages()
|
||||
const page = pages[pages.length-1]
|
||||
|
||||
if(page){
|
||||
this.currentPage = `/${page.route}`
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
separator() {
|
||||
return this.uniBreadcrumb.separator
|
||||
},
|
||||
separatorClass() {
|
||||
return this.uniBreadcrumb.separatorClass
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navTo() {
|
||||
const { to } = this
|
||||
|
||||
if (!to || this.currentPage === to){
|
||||
return
|
||||
}
|
||||
|
||||
if(this.replace){
|
||||
uni.redirectTo({
|
||||
url:to
|
||||
})
|
||||
}else{
|
||||
uni.navigateTo({
|
||||
url:to
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
$uni-primary: #2979ff !default;
|
||||
$uni-base-color: #6a6a6a !default;
|
||||
$uni-main-color: #3a3a3a !default;
|
||||
.uni-breadcrumb-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
|
||||
&--slot {
|
||||
color: $uni-base-color;
|
||||
padding: 0 10px;
|
||||
|
||||
&-link {
|
||||
color: $uni-main-color;
|
||||
font-weight: bold;
|
||||
/* #ifndef APP-NVUE */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
|
||||
&:hover {
|
||||
color: $uni-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--separator {
|
||||
font-size: 12px;
|
||||
color: $uni-base-color;
|
||||
}
|
||||
|
||||
&:first-child &--slot {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&:last-child &--separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,46 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-breadcrumb">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* Breadcrumb 面包屑导航父组件
|
||||
* @description 显示当前页面的路径,快速返回之前的任意页面
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
|
||||
* @property {String} separator 分隔符,默认为斜杠'/'
|
||||
* @property {String} separatorClass 图标分隔符 class
|
||||
*/
|
||||
export default {
|
||||
options: {
|
||||
// #ifdef MP-TOUTIAO
|
||||
virtualHost: false,
|
||||
// #endif
|
||||
// #ifndef MP-TOUTIAO
|
||||
virtualHost: true
|
||||
// #endif
|
||||
},
|
||||
props: {
|
||||
separator: {
|
||||
type: String,
|
||||
default: '/'
|
||||
},
|
||||
separatorClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
provide() {
|
||||
return {
|
||||
uniBreadcrumb: this
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.uni-breadcrumb {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"id": "uni-breadcrumb",
|
||||
"displayName": "uni-breadcrumb 面包屑",
|
||||
"version": "0.1.2",
|
||||
"description": "Breadcrumb 面包屑",
|
||||
"keywords": [
|
||||
"uni-breadcrumb",
|
||||
"breadcrumb",
|
||||
"uni-ui",
|
||||
"面包屑导航",
|
||||
"面包屑"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
## breadcrumb 面包屑导航
|
||||
> **组件名:uni-breadcrumb**
|
||||
> 代码块: `ubreadcrumb`
|
||||
|
||||
显示当前页面的路径,快速返回之前的任意页面。
|
||||
|
||||
### 安装方式
|
||||
|
||||
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
|
||||
|
||||
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
|
||||
|
||||
### 基本用法
|
||||
|
||||
在 ``template`` 中使用组件
|
||||
|
||||
```html
|
||||
<uni-breadcrumb separator="/">
|
||||
<uni-breadcrumb-item v-for="(route,index) in routes" :key="index" :to="route.to">{{route.name}}</uni-breadcrumb-item>
|
||||
</uni-breadcrumb>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
name: "uni-stat-breadcrumb",
|
||||
data() {
|
||||
return {
|
||||
routes: [{
|
||||
to: '/A',
|
||||
name: 'A页面'
|
||||
}, {
|
||||
to: '/B',
|
||||
name: 'B页面'
|
||||
}, {
|
||||
to: '/C',
|
||||
name: 'C页面'
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Breadcrumb Props
|
||||
|
||||
|属性名 |类型 |默认值 |说明 |
|
||||
|:-: |:-: |:-: |:-: |
|
||||
|separator |String |斜杠'/' |分隔符 |
|
||||
|separatorClass |String | |图标分隔符 class |
|
||||
|
||||
### Breadcrumb Item Props
|
||||
|
||||
|属性名 |类型 |默认值 |说明 |
|
||||
|:-: |:-: |:-: |:-: |
|
||||
|to |String | |路由跳转页面路径 |
|
||||
|replace|Boolean | |在使用 to 进行路由跳转时,启用 replace 将不会向 history 添加新记录(仅 h5 支持) |
|
||||
|
||||
|
||||
|
||||
|
||||
## 组件示例
|
||||
|
||||
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb](https://hellouniapp.dcloud.net.cn/pages/extUI/breadcrumb/breadcrumb)
|
||||
@@ -1,28 +0,0 @@
|
||||
## 1.4.11(2024-01-10)
|
||||
- 修复 回到今天时,月份显示不一致问题
|
||||
## 1.4.10(2023-04-10)
|
||||
- 修复 某些情况 monthSwitch 未触发的Bug
|
||||
## 1.4.9(2023-02-02)
|
||||
- 修复 某些情况切换月份错误的Bug
|
||||
## 1.4.8(2023-01-30)
|
||||
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
|
||||
## 1.4.7(2022-09-16)
|
||||
- 优化 支持使用 uni-scss 控制主题色
|
||||
## 1.4.6(2022-09-08)
|
||||
- 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件的Bug
|
||||
## 1.4.5(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.4(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式的Bug
|
||||
## 1.4.3(2021-09-22)
|
||||
- 修复 startDate、 endDate 属性失效的Bug
|
||||
## 1.4.2(2021-08-24)
|
||||
- 新增 支持国际化
|
||||
## 1.4.1(2021-08-05)
|
||||
- 修复 弹出层被 tabbar 遮盖的Bug
|
||||
## 1.4.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.3.16(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.3.15(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -1,546 +0,0 @@
|
||||
/**
|
||||
* @1900-2100区间内的公历、农历互转
|
||||
* @charset UTF-8
|
||||
* @github https://github.com/jjonline/calendar.js
|
||||
* @Author Jea杨(JJonline@JJonline.Cn)
|
||||
* @Time 2014-7-21
|
||||
* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
|
||||
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
|
||||
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
|
||||
* @Version 1.0.3
|
||||
* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
|
||||
* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
|
||||
*/
|
||||
/* eslint-disable */
|
||||
var calendar = {
|
||||
|
||||
/**
|
||||
* 农历1900-2100的润大小信息表
|
||||
* @Array Of Property
|
||||
* @return Hex
|
||||
*/
|
||||
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
|
||||
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
|
||||
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
|
||||
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
|
||||
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
|
||||
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
|
||||
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
|
||||
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
|
||||
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
|
||||
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
|
||||
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
|
||||
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
|
||||
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
|
||||
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
|
||||
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
|
||||
/** Add By JJonline@JJonline.Cn**/
|
||||
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
|
||||
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
|
||||
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
|
||||
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
|
||||
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
|
||||
0x0d520], // 2100
|
||||
|
||||
/**
|
||||
* 公历每个月份的天数普通表
|
||||
* @Array Of Property
|
||||
* @return Number
|
||||
*/
|
||||
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||
|
||||
/**
|
||||
* 天干地支之天干速查表
|
||||
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
|
||||
|
||||
/**
|
||||
* 天干地支之地支速查表
|
||||
* @Array Of Property
|
||||
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
|
||||
|
||||
/**
|
||||
* 天干地支之地支速查表<=>生肖
|
||||
* @Array Of Property
|
||||
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
|
||||
|
||||
/**
|
||||
* 24节气速查表
|
||||
* @Array Of Property
|
||||
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
|
||||
* @return Cn string
|
||||
*/
|
||||
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
|
||||
|
||||
/**
|
||||
* 1900-2100各年的24节气日期速查表
|
||||
* @Array Of Property
|
||||
* @return 0x string For splice
|
||||
*/
|
||||
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
|
||||
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
|
||||
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
|
||||
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
|
||||
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
|
||||
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
|
||||
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
|
||||
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
|
||||
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
|
||||
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
|
||||
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
|
||||
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
|
||||
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
|
||||
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
|
||||
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
|
||||
|
||||
/**
|
||||
* 数字转中文速查表
|
||||
* @Array Of Property
|
||||
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
|
||||
|
||||
/**
|
||||
* 日期转农历称呼速查表
|
||||
* @Array Of Property
|
||||
* @trans ['初','十','廿','卅']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
|
||||
|
||||
/**
|
||||
* 月份转农历称呼速查表
|
||||
* @Array Of Property
|
||||
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
|
||||
|
||||
/**
|
||||
* 返回农历y年一整年的总天数
|
||||
* @param lunar Year
|
||||
* @return Number
|
||||
* @eg:var count = calendar.lYearDays(1987) ;//count=387
|
||||
*/
|
||||
lYearDays: function (y) {
|
||||
var i; var sum = 348
|
||||
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
|
||||
return (sum + this.leapDays(y))
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0-12)
|
||||
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
|
||||
*/
|
||||
leapMonth: function (y) { // 闰字编码 \u95f0
|
||||
return (this.lunarInfo[y - 1900] & 0xf)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年闰月的天数 若该年没有闰月则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0、29、30)
|
||||
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
|
||||
*/
|
||||
leapDays: function (y) {
|
||||
if (this.leapMonth(y)) {
|
||||
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
|
||||
}
|
||||
return (0)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
|
||||
* @param lunar Year
|
||||
* @return Number (-1、29、30)
|
||||
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
|
||||
*/
|
||||
monthDays: function (y, m) {
|
||||
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
|
||||
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回公历(!)y年m月的天数
|
||||
* @param solar Year
|
||||
* @return Number (-1、28、29、30、31)
|
||||
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
|
||||
*/
|
||||
solarDays: function (y, m) {
|
||||
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||
var ms = m - 1
|
||||
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
|
||||
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
|
||||
} else {
|
||||
return (this.solarMonth[ms])
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 农历年份转换为干支纪年
|
||||
* @param lYear 农历年的年份数
|
||||
* @return Cn string
|
||||
*/
|
||||
toGanZhiYear: function (lYear) {
|
||||
var ganKey = (lYear - 3) % 10
|
||||
var zhiKey = (lYear - 3) % 12
|
||||
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
|
||||
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
|
||||
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
|
||||
},
|
||||
|
||||
/**
|
||||
* 公历月、日判断所属星座
|
||||
* @param cMonth [description]
|
||||
* @param cDay [description]
|
||||
* @return Cn string
|
||||
*/
|
||||
toAstro: function (cMonth, cDay) {
|
||||
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
|
||||
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
|
||||
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入offset偏移量返回干支
|
||||
* @param offset 相对甲子的偏移量
|
||||
* @return Cn string
|
||||
*/
|
||||
toGanZhi: function (offset) {
|
||||
return this.Gan[offset % 10] + this.Zhi[offset % 12]
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入公历(!)y年获得该年第n个节气的公历日期
|
||||
* @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
|
||||
* @return day Number
|
||||
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
|
||||
*/
|
||||
getTerm: function (y, n) {
|
||||
if (y < 1900 || y > 2100) { return -1 }
|
||||
if (n < 1 || n > 24) { return -1 }
|
||||
var _table = this.sTermInfo[y - 1900]
|
||||
var _info = [
|
||||
parseInt('0x' + _table.substr(0, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(5, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(10, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(15, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(20, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(25, 5)).toString()
|
||||
]
|
||||
var _calday = [
|
||||
_info[0].substr(0, 1),
|
||||
_info[0].substr(1, 2),
|
||||
_info[0].substr(3, 1),
|
||||
_info[0].substr(4, 2),
|
||||
|
||||
_info[1].substr(0, 1),
|
||||
_info[1].substr(1, 2),
|
||||
_info[1].substr(3, 1),
|
||||
_info[1].substr(4, 2),
|
||||
|
||||
_info[2].substr(0, 1),
|
||||
_info[2].substr(1, 2),
|
||||
_info[2].substr(3, 1),
|
||||
_info[2].substr(4, 2),
|
||||
|
||||
_info[3].substr(0, 1),
|
||||
_info[3].substr(1, 2),
|
||||
_info[3].substr(3, 1),
|
||||
_info[3].substr(4, 2),
|
||||
|
||||
_info[4].substr(0, 1),
|
||||
_info[4].substr(1, 2),
|
||||
_info[4].substr(3, 1),
|
||||
_info[4].substr(4, 2),
|
||||
|
||||
_info[5].substr(0, 1),
|
||||
_info[5].substr(1, 2),
|
||||
_info[5].substr(3, 1),
|
||||
_info[5].substr(4, 2)
|
||||
]
|
||||
return parseInt(_calday[n - 1])
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历数字月份返回汉语通俗表示法
|
||||
* @param lunar month
|
||||
* @return Cn string
|
||||
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
|
||||
*/
|
||||
toChinaMonth: function (m) { // 月 => \u6708
|
||||
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||
var s = this.nStr3[m - 1]
|
||||
s += '\u6708'// 加上月字
|
||||
return s
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历日期数字返回汉字表示法
|
||||
* @param lunar day
|
||||
* @return Cn string
|
||||
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
|
||||
*/
|
||||
toChinaDay: function (d) { // 日 => \u65e5
|
||||
var s
|
||||
switch (d) {
|
||||
case 10:
|
||||
s = '\u521d\u5341'; break
|
||||
case 20:
|
||||
s = '\u4e8c\u5341'; break
|
||||
break
|
||||
case 30:
|
||||
s = '\u4e09\u5341'; break
|
||||
break
|
||||
default :
|
||||
s = this.nStr2[Math.floor(d / 10)]
|
||||
s += this.nStr1[d % 10]
|
||||
}
|
||||
return (s)
|
||||
},
|
||||
|
||||
/**
|
||||
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
|
||||
* @param y year
|
||||
* @return Cn string
|
||||
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
|
||||
*/
|
||||
getAnimal: function (y) {
|
||||
return this.Animals[(y - 4) % 12]
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
|
||||
* @param y solar year
|
||||
* @param m solar month
|
||||
* @param d solar day
|
||||
* @return JSON object
|
||||
* @eg:console.log(calendar.solar2lunar(1987,11,01));
|
||||
*/
|
||||
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
|
||||
// 年份限定、上限
|
||||
if (y < 1900 || y > 2100) {
|
||||
return -1// undefined转换为数字变为NaN
|
||||
}
|
||||
// 公历传参最下限
|
||||
if (y == 1900 && m == 1 && d < 31) {
|
||||
return -1
|
||||
}
|
||||
// 未传参 获得当天
|
||||
if (!y) {
|
||||
var objDate = new Date()
|
||||
} else {
|
||||
var objDate = new Date(y, parseInt(m) - 1, d)
|
||||
}
|
||||
var i; var leap = 0; var temp = 0
|
||||
// 修正ymd参数
|
||||
var y = objDate.getFullYear()
|
||||
var m = objDate.getMonth() + 1
|
||||
var d = objDate.getDate()
|
||||
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
|
||||
for (i = 1900; i < 2101 && offset > 0; i++) {
|
||||
temp = this.lYearDays(i)
|
||||
offset -= temp
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += temp; i--
|
||||
}
|
||||
|
||||
// 是否今天
|
||||
var isTodayObj = new Date()
|
||||
var isToday = false
|
||||
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
|
||||
isToday = true
|
||||
}
|
||||
// 星期几
|
||||
var nWeek = objDate.getDay()
|
||||
var cWeek = this.nStr1[nWeek]
|
||||
// 数字表示周几顺应天朝周一开始的惯例
|
||||
if (nWeek == 0) {
|
||||
nWeek = 7
|
||||
}
|
||||
// 农历年
|
||||
var year = i
|
||||
var leap = this.leapMonth(i) // 闰哪个月
|
||||
var isLeap = false
|
||||
|
||||
// 效验闰月
|
||||
for (i = 1; i < 13 && offset > 0; i++) {
|
||||
// 闰月
|
||||
if (leap > 0 && i == (leap + 1) && isLeap == false) {
|
||||
--i
|
||||
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
|
||||
} else {
|
||||
temp = this.monthDays(year, i)// 计算农历普通月天数
|
||||
}
|
||||
// 解除闰月
|
||||
if (isLeap == true && i == (leap + 1)) { isLeap = false }
|
||||
offset -= temp
|
||||
}
|
||||
// 闰月导致数组下标重叠取反
|
||||
if (offset == 0 && leap > 0 && i == leap + 1) {
|
||||
if (isLeap) {
|
||||
isLeap = false
|
||||
} else {
|
||||
isLeap = true; --i
|
||||
}
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += temp; --i
|
||||
}
|
||||
// 农历月
|
||||
var month = i
|
||||
// 农历日
|
||||
var day = offset + 1
|
||||
// 天干地支处理
|
||||
var sm = m - 1
|
||||
var gzY = this.toGanZhiYear(year)
|
||||
|
||||
// 当月的两个节气
|
||||
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
|
||||
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
|
||||
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
|
||||
|
||||
// 依据12节气修正干支月
|
||||
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
|
||||
if (d >= firstNode) {
|
||||
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
|
||||
}
|
||||
|
||||
// 传入的日期的节气与否
|
||||
var isTerm = false
|
||||
var Term = null
|
||||
if (firstNode == d) {
|
||||
isTerm = true
|
||||
Term = this.solarTerm[m * 2 - 2]
|
||||
}
|
||||
if (secondNode == d) {
|
||||
isTerm = true
|
||||
Term = this.solarTerm[m * 2 - 1]
|
||||
}
|
||||
// 日柱 当月一日与 1900/1/1 相差天数
|
||||
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
|
||||
var gzD = this.toGanZhi(dayCyclical + d - 1)
|
||||
// 该日期所属的星座
|
||||
var astro = this.toAstro(m, d)
|
||||
|
||||
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
|
||||
* @param y lunar year
|
||||
* @param m lunar month
|
||||
* @param d lunar day
|
||||
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
|
||||
* @return JSON object
|
||||
* @eg:console.log(calendar.lunar2solar(1987,9,10));
|
||||
*/
|
||||
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
|
||||
var isLeapMonth = !!isLeapMonth
|
||||
var leapOffset = 0
|
||||
var leapMonth = this.leapMonth(y)
|
||||
var leapDay = this.leapDays(y)
|
||||
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
|
||||
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
|
||||
var day = this.monthDays(y, m)
|
||||
var _day = day
|
||||
// bugFix 2016-9-25
|
||||
// if month is leap, _day use leapDays method
|
||||
if (isLeapMonth) {
|
||||
_day = this.leapDays(y, m)
|
||||
}
|
||||
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
|
||||
|
||||
// 计算农历的时间差
|
||||
var offset = 0
|
||||
for (var i = 1900; i < y; i++) {
|
||||
offset += this.lYearDays(i)
|
||||
}
|
||||
var leap = 0; var isAdd = false
|
||||
for (var i = 1; i < m; i++) {
|
||||
leap = this.leapMonth(y)
|
||||
if (!isAdd) { // 处理闰月
|
||||
if (leap <= i && leap > 0) {
|
||||
offset += this.leapDays(y); isAdd = true
|
||||
}
|
||||
}
|
||||
offset += this.monthDays(y, i)
|
||||
}
|
||||
// 转换闰月农历 需补充该年闰月的前一个月的时差
|
||||
if (isLeapMonth) { offset += day }
|
||||
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
|
||||
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
|
||||
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
|
||||
var cY = calObj.getUTCFullYear()
|
||||
var cM = calObj.getUTCMonth() + 1
|
||||
var cD = calObj.getUTCDate()
|
||||
|
||||
return this.solar2lunar(cY, cM, cD)
|
||||
}
|
||||
}
|
||||
|
||||
export default calendar
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"uni-calender.ok": "ok",
|
||||
"uni-calender.cancel": "cancel",
|
||||
"uni-calender.today": "today",
|
||||
"uni-calender.MON": "MON",
|
||||
"uni-calender.TUE": "TUE",
|
||||
"uni-calender.WED": "WED",
|
||||
"uni-calender.THU": "THU",
|
||||
"uni-calender.FRI": "FRI",
|
||||
"uni-calender.SAT": "SAT",
|
||||
"uni-calender.SUN": "SUN"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import en from './en.json'
|
||||
import zhHans from './zh-Hans.json'
|
||||
import zhHant from './zh-Hant.json'
|
||||
export default {
|
||||
en,
|
||||
'zh-Hans': zhHans,
|
||||
'zh-Hant': zhHant
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"uni-calender.ok": "确定",
|
||||
"uni-calender.cancel": "取消",
|
||||
"uni-calender.today": "今日",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"uni-calender.ok": "確定",
|
||||
"uni-calender.cancel": "取消",
|
||||
"uni-calender.today": "今日",
|
||||
"uni-calender.SUN": "日",
|
||||
"uni-calender.MON": "一",
|
||||
"uni-calender.TUE": "二",
|
||||
"uni-calender.WED": "三",
|
||||
"uni-calender.THU": "四",
|
||||
"uni-calender.FRI": "五",
|
||||
"uni-calender.SAT": "六"
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-calendar-item__weeks-box" :class="{
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}"
|
||||
@click="choiceDate(weeks)">
|
||||
<view class="uni-calendar-item__weeks-box-item">
|
||||
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
|
||||
<text class="uni-calendar-item__weeks-box-text" :class="{
|
||||
'uni-calendar-item--isDay-text': weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.date}}</text>
|
||||
<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}">{{todayText}}</text>
|
||||
<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
|
||||
<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--extra':weeks.extraInfo.info,
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.extraInfo.info}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const { t } = initVueI18n(i18nMessages)
|
||||
|
||||
export default {
|
||||
emits:['change'],
|
||||
props: {
|
||||
weeks: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
calendar: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
todayText() {
|
||||
return t("uni-calender.today")
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
choiceDate(weeks) {
|
||||
this.$emit('change', weeks)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$uni-font-size-base:14px;
|
||||
$uni-text-color:#333;
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-color-error: #e43d33;
|
||||
$uni-opacity-disabled: 0.3;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
$uni-primary: #2979ff !default;
|
||||
.uni-calendar-item__weeks-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-text {
|
||||
font-size: $uni-font-size-base;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-lunar-text {
|
||||
font-size: $uni-font-size-sm;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-circle {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 8px;
|
||||
background-color: $uni-color-error;
|
||||
|
||||
}
|
||||
|
||||
.uni-calendar-item--disable {
|
||||
background-color: rgba(249, 249, 249, $uni-opacity-disabled);
|
||||
color: $uni-text-color-disable;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay {
|
||||
background-color: $uni-primary;
|
||||
opacity: 0.8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar-item--extra {
|
||||
color: $uni-color-error;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--checked {
|
||||
background-color: $uni-primary;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--multiple {
|
||||
background-color: $uni-primary;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
.uni-calendar-item--before-checked {
|
||||
background-color: #ff5a5f;
|
||||
color: #fff;
|
||||
}
|
||||
.uni-calendar-item--after-checked {
|
||||
background-color: #ff5a5f;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,567 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-calendar">
|
||||
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
|
||||
<view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
|
||||
<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
|
||||
<view class="uni-calendar__header-btn-box" @click="close">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{cancelText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__header-btn-box" @click="confirm">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">{{okText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__header">
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="pre">
|
||||
<view class="uni-calendar__header-btn uni-calendar--left"></view>
|
||||
</view>
|
||||
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
|
||||
<text class="uni-calendar__header-text">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>
|
||||
</picker>
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="next">
|
||||
<view class="uni-calendar__header-btn uni-calendar--right"></view>
|
||||
</view>
|
||||
<text class="uni-calendar__backtoday" @click="backToday">{{todayText}}</text>
|
||||
|
||||
</view>
|
||||
<view class="uni-calendar__box">
|
||||
<view v-if="showMonth" class="uni-calendar__box-bg">
|
||||
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks">
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{monText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{THUText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">{{SATText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
|
||||
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
|
||||
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Calendar from './util.js';
|
||||
import CalendarItem from './uni-calendar-item.vue'
|
||||
|
||||
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||
import i18nMessages from './i18n/index.js'
|
||||
const { t } = initVueI18n(i18nMessages)
|
||||
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
|
||||
* @property {String} date 自定义当前时间,默认为今天
|
||||
* @property {Boolean} lunar 显示农历
|
||||
* @property {String} startDate 日期选择范围-开始日期
|
||||
* @property {String} endDate 日期选择范围-结束日期
|
||||
* @property {Boolean} range 范围选择
|
||||
* @property {Boolean} insert = [true|false] 插入模式,默认为false
|
||||
* @value true 弹窗模式
|
||||
* @value false 插入模式
|
||||
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
|
||||
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
|
||||
* @property {Boolean} showMonth 是否选择月份为背景
|
||||
* @event {Function} change 日期改变,`insert :ture` 时生效
|
||||
* @event {Function} confirm 确认选择`insert :false` 时生效
|
||||
* @event {Function} monthSwitch 切换月份时触发
|
||||
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
|
||||
*/
|
||||
export default {
|
||||
components: {
|
||||
CalendarItem
|
||||
},
|
||||
emits:['close','confirm','change','monthSwitch'],
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
startDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
endDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
insert: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
clearDate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
weeks: [],
|
||||
calendar: {},
|
||||
nowDate: '',
|
||||
aniMaskShow: false
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
/**
|
||||
* for i18n
|
||||
*/
|
||||
|
||||
okText() {
|
||||
return t("uni-calender.ok")
|
||||
},
|
||||
cancelText() {
|
||||
return t("uni-calender.cancel")
|
||||
},
|
||||
todayText() {
|
||||
return t("uni-calender.today")
|
||||
},
|
||||
monText() {
|
||||
return t("uni-calender.MON")
|
||||
},
|
||||
TUEText() {
|
||||
return t("uni-calender.TUE")
|
||||
},
|
||||
WEDText() {
|
||||
return t("uni-calender.WED")
|
||||
},
|
||||
THUText() {
|
||||
return t("uni-calender.THU")
|
||||
},
|
||||
FRIText() {
|
||||
return t("uni-calender.FRI")
|
||||
},
|
||||
SATText() {
|
||||
return t("uni-calender.SAT")
|
||||
},
|
||||
SUNText() {
|
||||
return t("uni-calender.SUN")
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
date(newVal) {
|
||||
// this.cale.setDate(newVal)
|
||||
this.init(newVal)
|
||||
},
|
||||
startDate(val){
|
||||
this.cale.resetSatrtDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
endDate(val){
|
||||
this.cale.resetEndDate(val)
|
||||
this.cale.setDate(this.nowDate.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
},
|
||||
selected(newVal) {
|
||||
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
|
||||
this.weeks = this.cale.weeks
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.cale = new Calendar({
|
||||
selected: this.selected,
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate,
|
||||
range: this.range,
|
||||
})
|
||||
this.init(this.date)
|
||||
},
|
||||
methods: {
|
||||
// 取消穿透
|
||||
clean() {},
|
||||
bindDateChange(e) {
|
||||
const value = e.detail.value + '-1'
|
||||
this.setDate(value)
|
||||
|
||||
const { year,month } = this.cale.getDate(value)
|
||||
this.$emit('monthSwitch', {
|
||||
year,
|
||||
month
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 初始化日期显示
|
||||
* @param {Object} date
|
||||
*/
|
||||
init(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.calendar = this.cale.getInfo(date)
|
||||
},
|
||||
/**
|
||||
* 打开日历弹窗
|
||||
*/
|
||||
open() {
|
||||
// 弹窗模式并且清理数据
|
||||
if (this.clearDate && !this.insert) {
|
||||
this.cale.cleanMultipleStatus()
|
||||
// this.cale.setDate(this.date)
|
||||
this.init(this.date)
|
||||
}
|
||||
this.show = true
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.aniMaskShow = true
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 关闭日历弹窗
|
||||
*/
|
||||
close() {
|
||||
this.aniMaskShow = false
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
this.$emit('close')
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 确认按钮
|
||||
*/
|
||||
confirm() {
|
||||
this.setEmit('confirm')
|
||||
this.close()
|
||||
},
|
||||
/**
|
||||
* 变化触发
|
||||
*/
|
||||
change() {
|
||||
if (!this.insert) return
|
||||
this.setEmit('change')
|
||||
},
|
||||
/**
|
||||
* 选择月份触发
|
||||
*/
|
||||
monthSwitch() {
|
||||
let {
|
||||
year,
|
||||
month
|
||||
} = this.nowDate
|
||||
this.$emit('monthSwitch', {
|
||||
year,
|
||||
month: Number(month)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 派发事件
|
||||
* @param {Object} name
|
||||
*/
|
||||
setEmit(name) {
|
||||
let {
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fullDate,
|
||||
lunar,
|
||||
extraInfo
|
||||
} = this.calendar
|
||||
this.$emit(name, {
|
||||
range: this.cale.multipleStatus,
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fulldate: fullDate,
|
||||
lunar,
|
||||
extraInfo: extraInfo || {}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 选择天触发
|
||||
* @param {Object} weeks
|
||||
*/
|
||||
choiceDate(weeks) {
|
||||
if (weeks.disable) return
|
||||
this.calendar = weeks
|
||||
// 设置多选
|
||||
this.cale.setMultiple(this.calendar.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 回到今天
|
||||
*/
|
||||
backToday() {
|
||||
const nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`
|
||||
const date = this.cale.getDate(new Date())
|
||||
const todayYearMonth = `${date.year}-${date.month}`
|
||||
|
||||
this.init(date.fullDate)
|
||||
|
||||
if(nowYearMonth !== todayYearMonth) {
|
||||
this.monthSwitch()
|
||||
}
|
||||
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 上个月
|
||||
*/
|
||||
pre() {
|
||||
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
|
||||
this.setDate(preDate)
|
||||
this.monthSwitch()
|
||||
|
||||
},
|
||||
/**
|
||||
* 下个月
|
||||
*/
|
||||
next() {
|
||||
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
|
||||
this.setDate(nextDate)
|
||||
this.monthSwitch()
|
||||
},
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.cale.getInfo(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$uni-bg-color-mask: rgba($color: #000000, $alpha: 0.4);
|
||||
$uni-border-color: #EDEDED;
|
||||
$uni-text-color: #333;
|
||||
$uni-bg-color-hover:#f1f1f1;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-color-subtitle: #555555;
|
||||
$uni-text-color-grey:#999;
|
||||
.uni-calendar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-calendar__mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $uni-bg-color-mask;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
opacity: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--mask-show {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.uni-calendar--fixed {
|
||||
position: fixed;
|
||||
/* #ifdef APP-NVUE */
|
||||
bottom: 0;
|
||||
/* #endif */
|
||||
left: 0;
|
||||
right: 0;
|
||||
transition-property: transform;
|
||||
transition-duration: 0.3s;
|
||||
transform: translateY(460px);
|
||||
/* #ifndef APP-NVUE */
|
||||
bottom: calc(var(--window-bottom));
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--ani-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.uni-calendar__content {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar__header {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
border-bottom-color: $uni-border-color;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-top {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
border-top-color: $uni-border-color;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-width {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__backtoday {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 25rpx;
|
||||
padding: 0 5px;
|
||||
padding-left: 10px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 12px;
|
||||
border-top-left-radius: 25px;
|
||||
border-bottom-left-radius: 25px;
|
||||
color: $uni-text-color;
|
||||
background-color: $uni-bg-color-hover;
|
||||
}
|
||||
|
||||
.uni-calendar__header-text {
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
font-size: $uni-font-size-base;
|
||||
color: $uni-text-color;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-left-color: $uni-text-color-placeholder;
|
||||
border-left-style: solid;
|
||||
border-left-width: 2px;
|
||||
border-top-color: $uni-color-subtitle;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
.uni-calendar--left {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.uni-calendar--right {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
|
||||
.uni-calendar__weeks {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
border-bottom-color: #F5F5F5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-calendar__box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg-text {
|
||||
font-size: 200px;
|
||||
font-weight: bold;
|
||||
color: $uni-text-color-grey;
|
||||
opacity: 0.1;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: 1;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
@@ -1,360 +0,0 @@
|
||||
import CALENDAR from './calendar.js'
|
||||
|
||||
class Calendar {
|
||||
constructor({
|
||||
date,
|
||||
selected,
|
||||
startDate,
|
||||
endDate,
|
||||
range
|
||||
} = {}) {
|
||||
// 当前日期
|
||||
this.date = this.getDate(new Date()) // 当前初入日期
|
||||
// 打点信息
|
||||
this.selected = selected || [];
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
this.range = range
|
||||
// 多选状态
|
||||
this.cleanMultipleStatus()
|
||||
// 每周日期
|
||||
this.weeks = {}
|
||||
// this._getWeek(this.date.fullDate)
|
||||
}
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.selectDate = this.getDate(date)
|
||||
this._getWeek(this.selectDate.fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理多选状态
|
||||
*/
|
||||
cleanMultipleStatus() {
|
||||
this.multipleStatus = {
|
||||
before: '',
|
||||
after: '',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置开始日期
|
||||
*/
|
||||
resetSatrtDate(startDate) {
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置结束日期
|
||||
*/
|
||||
resetEndDate(endDate) {
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任意时间
|
||||
*/
|
||||
getDate(date, AddDayCount = 0, str = 'day') {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
if (typeof date !== 'object') {
|
||||
date = date.replace(/-/g, '/')
|
||||
}
|
||||
const dd = new Date(date)
|
||||
switch (str) {
|
||||
case 'day':
|
||||
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
case 'month':
|
||||
if (dd.getDate() === 31 && AddDayCount>0) {
|
||||
dd.setDate(dd.getDate() + AddDayCount)
|
||||
} else {
|
||||
const preMonth = dd.getMonth()
|
||||
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
|
||||
const nextMonth = dd.getMonth()
|
||||
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
|
||||
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
|
||||
}
|
||||
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
|
||||
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
|
||||
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'year':
|
||||
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
}
|
||||
const y = dd.getFullYear()
|
||||
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
|
||||
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
|
||||
return {
|
||||
fullDate: y + '-' + m + '-' + d,
|
||||
year: y,
|
||||
month: m,
|
||||
date: d,
|
||||
day: dd.getDay()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取上月剩余天数
|
||||
*/
|
||||
_getLastMonthDays(firstDay, full) {
|
||||
let dateArr = []
|
||||
for (let i = firstDay; i > 0; i--) {
|
||||
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
|
||||
dateArr.push({
|
||||
date: beforeDate,
|
||||
month: full.month - 1,
|
||||
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取本月天数
|
||||
*/
|
||||
_currentMonthDys(dateData, full) {
|
||||
let dateArr = []
|
||||
let fullDate = this.date.fullDate
|
||||
for (let i = 1; i <= dateData; i++) {
|
||||
let nowDate = full.year + '-' + (full.month < 10 ?
|
||||
full.month : full.month) + '-' + (i < 10 ?
|
||||
'0' + i : i)
|
||||
// 是否今天
|
||||
let isDay = fullDate === nowDate
|
||||
// 获取打点信息
|
||||
let info = this.selected && this.selected.find((item) => {
|
||||
if (this.dateEqual(nowDate, item.date)) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
|
||||
// 日期禁用
|
||||
let disableBefore = true
|
||||
let disableAfter = true
|
||||
if (this.startDate) {
|
||||
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
|
||||
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
|
||||
disableBefore = this.dateCompare(this.startDate, nowDate)
|
||||
}
|
||||
|
||||
if (this.endDate) {
|
||||
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
|
||||
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
|
||||
disableAfter = this.dateCompare(nowDate, this.endDate)
|
||||
}
|
||||
let multiples = this.multipleStatus.data
|
||||
let checked = false
|
||||
let multiplesStatus = -1
|
||||
if (this.range) {
|
||||
if (multiples) {
|
||||
multiplesStatus = multiples.findIndex((item) => {
|
||||
return this.dateEqual(item, nowDate)
|
||||
})
|
||||
}
|
||||
if (multiplesStatus !== -1) {
|
||||
checked = true
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
fullDate: nowDate,
|
||||
year: full.year,
|
||||
date: i,
|
||||
multiple: this.range ? checked : false,
|
||||
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
|
||||
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
|
||||
month: full.month,
|
||||
lunar: this.getlunar(full.year, full.month, i),
|
||||
disable: !(disableBefore && disableAfter),
|
||||
isDay
|
||||
}
|
||||
if (info) {
|
||||
data.extraInfo = info
|
||||
}
|
||||
|
||||
dateArr.push(data)
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取下月天数
|
||||
*/
|
||||
_getNextMonthDays(surplus, full) {
|
||||
let dateArr = []
|
||||
for (let i = 1; i < surplus + 1; i++) {
|
||||
dateArr.push({
|
||||
date: i,
|
||||
month: Number(full.month) + 1,
|
||||
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期详情
|
||||
* @param {Object} date
|
||||
*/
|
||||
getInfo(date) {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
|
||||
return dateInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间大小
|
||||
*/
|
||||
dateCompare(startDate, endDate) {
|
||||
// 计算截止时间
|
||||
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
|
||||
if (startDate <= endDate) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间是否相等
|
||||
*/
|
||||
dateEqual(before, after) {
|
||||
// 计算截止时间
|
||||
before = new Date(before.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
after = new Date(after.replace('-', '/').replace('-', '/'))
|
||||
if (before.getTime() - after.getTime() === 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取日期范围内所有日期
|
||||
* @param {Object} begin
|
||||
* @param {Object} end
|
||||
*/
|
||||
geDateAll(begin, end) {
|
||||
var arr = []
|
||||
var ab = begin.split('-')
|
||||
var ae = end.split('-')
|
||||
var db = new Date()
|
||||
db.setFullYear(ab[0], ab[1] - 1, ab[2])
|
||||
var de = new Date()
|
||||
de.setFullYear(ae[0], ae[1] - 1, ae[2])
|
||||
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
|
||||
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
|
||||
for (var k = unixDb; k <= unixDe;) {
|
||||
k = k + 24 * 60 * 60 * 1000
|
||||
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
/**
|
||||
* 计算阴历日期显示
|
||||
*/
|
||||
getlunar(year, month, date) {
|
||||
return CALENDAR.solar2lunar(year, month, date)
|
||||
}
|
||||
/**
|
||||
* 设置打点
|
||||
*/
|
||||
setSelectInfo(data, value) {
|
||||
this.selected = value
|
||||
this._getWeek(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多选状态
|
||||
*/
|
||||
setMultiple(fullDate) {
|
||||
let {
|
||||
before,
|
||||
after
|
||||
} = this.multipleStatus
|
||||
|
||||
if (!this.range) return
|
||||
if (before && after) {
|
||||
this.multipleStatus.before = ''
|
||||
this.multipleStatus.after = ''
|
||||
this.multipleStatus.data = []
|
||||
} else {
|
||||
if (!before) {
|
||||
this.multipleStatus.before = fullDate
|
||||
} else {
|
||||
this.multipleStatus.after = fullDate
|
||||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
|
||||
} else {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._getWeek(fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每周数据
|
||||
* @param {Object} dateData
|
||||
*/
|
||||
_getWeek(dateData) {
|
||||
const {
|
||||
year,
|
||||
month
|
||||
} = this.getDate(dateData)
|
||||
let firstDay = new Date(year, month - 1, 1).getDay()
|
||||
let currentDay = new Date(year, month, 0).getDate()
|
||||
let dates = {
|
||||
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
|
||||
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
|
||||
nextMonthDays: [], // 下个月开始几天
|
||||
weeks: []
|
||||
}
|
||||
let canlender = []
|
||||
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
|
||||
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
|
||||
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
|
||||
let weeks = {}
|
||||
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
|
||||
for (let i = 0; i < canlender.length; i++) {
|
||||
if (i % 7 === 0) {
|
||||
weeks[parseInt(i / 7)] = new Array(7)
|
||||
}
|
||||
weeks[parseInt(i / 7)][i % 7] = canlender[i]
|
||||
}
|
||||
this.canlender = canlender
|
||||
this.weeks = weeks
|
||||
}
|
||||
|
||||
//静态方法
|
||||
// static init(date) {
|
||||
// if (!this.instance) {
|
||||
// this.instance = new Calendar(date);
|
||||
// }
|
||||
// return this.instance;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
export default Calendar
|
||||
@@ -1,85 +0,0 @@
|
||||
{
|
||||
"id": "uni-calendar",
|
||||
"displayName": "uni-calendar 日历",
|
||||
"version": "1.4.11",
|
||||
"description": "日历组件",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"日历",
|
||||
"",
|
||||
"打卡",
|
||||
"日历选择"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
|
||||
|
||||
## Calendar 日历
|
||||
> **组件名:uni-calendar**
|
||||
> 代码块: `uCalendar`
|
||||
|
||||
|
||||
日历组件
|
||||
|
||||
> **注意事项**
|
||||
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
|
||||
> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
|
||||
> - 仅支持自定义组件模式
|
||||
> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
|
||||
> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
|
||||
> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
|
||||
|
||||
|
||||
### 安装方式
|
||||
|
||||
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
|
||||
|
||||
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
|
||||
|
||||
### 基本用法
|
||||
|
||||
在 ``template`` 中使用组件
|
||||
|
||||
```html
|
||||
<view>
|
||||
<uni-calendar
|
||||
:insert="true"
|
||||
:lunar="true"
|
||||
:start-date="'2019-3-2'"
|
||||
:end-date="'2019-5-20'"
|
||||
@change="change"
|
||||
/>
|
||||
</view>
|
||||
```
|
||||
|
||||
### 通过方法打开日历
|
||||
|
||||
需要设置 `insert` 为 `false`
|
||||
|
||||
```html
|
||||
<view>
|
||||
<uni-calendar
|
||||
ref="calendar"
|
||||
:insert="false"
|
||||
@confirm="confirm"
|
||||
/>
|
||||
<button @click="open">打开日历</button>
|
||||
</view>
|
||||
```
|
||||
|
||||
```javascript
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
open(){
|
||||
this.$refs.calendar.open();
|
||||
},
|
||||
confirm(e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Calendar Props
|
||||
|
||||
| 属性名 | 类型 | 默认值| 说明 |
|
||||
| - | - | - | - |
|
||||
| date | String |- | 自定义当前时间,默认为今天 |
|
||||
| lunar | Boolean | false | 显示农历 |
|
||||
| startDate | String |- | 日期选择范围-开始日期 |
|
||||
| endDate | String |- | 日期选择范围-结束日期 |
|
||||
| range | Boolean | false | 范围选择 |
|
||||
| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 |
|
||||
|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
|
||||
| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|
||||
|showMonth | Boolean | true | 是否显示月份为背景 |
|
||||
|
||||
### Calendar Events
|
||||
|
||||
| 事件名 | 说明 |返回值|
|
||||
| - | - | - |
|
||||
| open | 弹出日历组件,`insert :false` 时生效|- |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 组件示例
|
||||
|
||||
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)
|
||||
@@ -1,26 +0,0 @@
|
||||
## 1.3.1(2021-12-20)
|
||||
- 修复 在vue页面下略缩图显示不正常的bug
|
||||
## 1.3.0(2021-11-19)
|
||||
- 重构插槽的用法 ,header 替换为 title
|
||||
- 新增 actions 插槽
|
||||
- 新增 cover 封面图属性和插槽
|
||||
- 新增 padding 内容默认内边距离
|
||||
- 新增 margin 卡片默认外边距离
|
||||
- 新增 spacing 卡片默认内边距
|
||||
- 新增 shadow 卡片阴影属性
|
||||
- 取消 mode 属性,可使用组合插槽代替
|
||||
- 取消 note 属性 ,使用actions插槽代替
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
|
||||
## 1.2.1(2021-07-30)
|
||||
- 优化 vue3下事件警告的问题
|
||||
## 1.2.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.8(2021-07-01)
|
||||
- 优化 图文卡片无图片加载时,提供占位图标
|
||||
- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
|
||||
- 修复 thumbnail 不存在仍然占位的 bug
|
||||
## 1.1.7(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.1.6(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -1,270 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
|
||||
:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
|
||||
<!-- 封面 -->
|
||||
<slot name="cover">
|
||||
<view v-if="cover" class="uni-card__cover">
|
||||
<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
|
||||
</view>
|
||||
</slot>
|
||||
<slot name="title">
|
||||
<view v-if="title || extra" class="uni-card__header">
|
||||
<!-- 卡片标题 -->
|
||||
<view class="uni-card__header-box" @click="onClick('title')">
|
||||
<view v-if="thumbnail" class="uni-card__header-avatar">
|
||||
<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="uni-card__header-content">
|
||||
<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
|
||||
<text v-if="title&&subTitle"
|
||||
class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-card__header-extra" @click="onClick('extra')">
|
||||
<text class="uni-card__header-extra-text">{{ extra }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
<!-- 卡片内容 -->
|
||||
<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="uni-card__actions" @click="onClick('actions')">
|
||||
<slot name="actions"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Card 卡片
|
||||
* @description 卡片视图组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=22
|
||||
* @property {String} title 标题文字
|
||||
* @property {String} subTitle 副标题
|
||||
* @property {Number} padding 内容内边距
|
||||
* @property {Number} margin 卡片外边距
|
||||
* @property {Number} spacing 卡片内边距
|
||||
* @property {String} extra 标题额外信息
|
||||
* @property {String} cover 封面图(本地路径需要引入)
|
||||
* @property {String} thumbnail 标题左侧缩略图
|
||||
* @property {Boolean} is-full = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
|
||||
* @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
|
||||
* @property {String} shadow 卡片阴影
|
||||
* @property {Boolean} border 卡片边框
|
||||
* @event {Function} click 点击 Card 触发事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniCard',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '10px'
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '15px'
|
||||
},
|
||||
spacing: {
|
||||
type: String,
|
||||
default: '0 10px'
|
||||
},
|
||||
extra: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
cover: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
thumbnail: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isFull: {
|
||||
// 内容区域是否通栏
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShadow: {
|
||||
// 是否开启阴影
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
shadow: {
|
||||
type: String,
|
||||
default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick(type) {
|
||||
this.$emit('click', type)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$uni-border-3: #EBEEF5 !default;
|
||||
$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
|
||||
$uni-main-color: #3a3a3a !default;
|
||||
$uni-base-color: #6a6a6a !default;
|
||||
$uni-secondary-color: #909399 !default;
|
||||
$uni-spacing-sm: 8px !default;
|
||||
$uni-border-color:$uni-border-3;
|
||||
$uni-shadow: $uni-shadow-base;
|
||||
$uni-card-title: 15px;
|
||||
$uni-cart-title-color:$uni-main-color;
|
||||
$uni-card-subtitle: 12px;
|
||||
$uni-cart-subtitle-color:$uni-secondary-color;
|
||||
$uni-card-spacing: 10px;
|
||||
$uni-card-content-color: $uni-base-color;
|
||||
|
||||
.uni-card {
|
||||
margin: $uni-card-spacing;
|
||||
padding: 0 $uni-spacing-sm;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
background-color: #fff;
|
||||
flex: 1;
|
||||
|
||||
.uni-card__cover {
|
||||
position: relative;
|
||||
margin-top: $uni-card-spacing;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
.uni-card__cover-image {
|
||||
flex: 1;
|
||||
// width: 100%;
|
||||
/* #ifndef APP-PLUS */
|
||||
vertical-align: middle;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__header {
|
||||
display: flex;
|
||||
border-bottom: 1px $uni-border-color solid;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: $uni-card-spacing;
|
||||
overflow: hidden;
|
||||
|
||||
.uni-card__header-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-card__header-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
margin-right: $uni-card-spacing;
|
||||
.uni-card__header-avatar-image {
|
||||
flex: 1;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__header-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
// height: 40px;
|
||||
overflow: hidden;
|
||||
|
||||
.uni-card__header-content-title {
|
||||
font-size: $uni-card-title;
|
||||
color: $uni-cart-title-color;
|
||||
// line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-card__header-content-subtitle {
|
||||
font-size: $uni-card-subtitle;
|
||||
margin-top: 5px;
|
||||
color: $uni-cart-subtitle-color;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__header-extra {
|
||||
line-height: 12px;
|
||||
|
||||
.uni-card__header-extra-text {
|
||||
font-size: 12px;
|
||||
color: $uni-cart-subtitle-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__content {
|
||||
padding: $uni-card-spacing;
|
||||
font-size: 14px;
|
||||
color: $uni-card-content-color;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-card__actions {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card--border {
|
||||
border: 1px solid $uni-border-color;
|
||||
}
|
||||
|
||||
.uni-card--shadow {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-shadow: $uni-shadow;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-card--full {
|
||||
margin: 0;
|
||||
border-left-width: 0;
|
||||
border-left-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-card--full:after {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-ellipsis {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
@@ -1,90 +0,0 @@
|
||||
{
|
||||
"id": "uni-card",
|
||||
"displayName": "uni-card 卡片",
|
||||
"version": "1.3.1",
|
||||
"description": "Card 组件,提供常见的卡片样式。",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"card",
|
||||
"",
|
||||
"卡片"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-icons",
|
||||
"uni-scss"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
|
||||
## Card 卡片
|
||||
> **组件名:uni-card**
|
||||
> 代码块: `uCard`
|
||||
|
||||
卡片视图组件。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
## 1.4.4(2024-03-20)
|
||||
- 修复 titleBorder类型修正
|
||||
## 1.4.3(2022-01-25)
|
||||
- 修复 初始化的时候 ,open 属性失效的bug
|
||||
## 1.4.2(2022-01-21)
|
||||
- 修复 微信小程序resize后组件收起的bug
|
||||
## 1.4.1(2021-11-22)
|
||||
- 修复 vue3中个别scss变量无法找到的问题
|
||||
## 1.4.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse)
|
||||
## 1.3.3(2021-08-17)
|
||||
- 优化 show-arrow 属性默认为true
|
||||
## 1.3.2(2021-08-17)
|
||||
- 新增 show-arrow 属性,控制是否显示右侧箭头
|
||||
## 1.3.1(2021-07-30)
|
||||
- 优化 vue3下小程序事件警告的问题
|
||||
## 1.3.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.2.2(2021-07-21)
|
||||
- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug
|
||||
## 1.2.1(2021-07-21)
|
||||
- 优化 组件示例
|
||||
## 1.2.0(2021-07-21)
|
||||
- 新增 组件折叠动画
|
||||
- 新增 value\v-model 属性 ,动态修改面板折叠状态
|
||||
- 新增 title 插槽 ,可定义面板标题
|
||||
- 新增 border 属性 ,显示隐藏面板内容分隔线
|
||||
- 新增 title-border 属性 ,显示隐藏面板标题分隔线
|
||||
- 修复 resize 方法失效的Bug
|
||||
- 修复 change 事件返回参数不正确的Bug
|
||||
- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法
|
||||
## 1.1.7(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.1.6(2021-02-05)
|
||||
- 优化 组件引用关系,通过uni_modules引用组件
|
||||
## 1.1.5(2021-02-05)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -1,402 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-collapse-item">
|
||||
<!-- onClick(!isOpen) -->
|
||||
<view @click="onClick(!isOpen)" class="uni-collapse-item__title"
|
||||
:class="{'is-open':isOpen &&titleBorder === 'auto' ,'uni-collapse-item-border':titleBorder !== 'none'}">
|
||||
<view class="uni-collapse-item__title-wrap">
|
||||
<slot name="title">
|
||||
<view class="uni-collapse-item__title-box" :class="{'is-disabled':disabled}">
|
||||
<image v-if="thumb" :src="thumb" class="uni-collapse-item__title-img" />
|
||||
<text class="uni-collapse-item__title-text">{{ title }}</text>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view v-if="showArrow"
|
||||
:class="{ 'uni-collapse-item__title-arrow-active': isOpen, 'uni-collapse-item--animation': showAnimation === true }"
|
||||
class="uni-collapse-item__title-arrow">
|
||||
<uni-icons :color="disabled?'#ddd':'#bbb'" size="14" type="bottom" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-collapse-item__wrap" :class="{'is--transition':showAnimation}"
|
||||
:style="{height: (isOpen?height:0) +'px'}">
|
||||
<view :id="elId" ref="collapse--hook" class="uni-collapse-item__wrap-content"
|
||||
:class="{open:isheight,'uni-collapse-item--border':border&&isOpen}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
// #endif
|
||||
/**
|
||||
* CollapseItem 折叠面板子组件
|
||||
* @description 折叠面板子组件
|
||||
* @property {String} title 标题文字
|
||||
* @property {String} thumb 标题左侧缩略图
|
||||
* @property {String} name 唯一标志符
|
||||
* @property {Boolean} open = [true|false] 是否展开组件
|
||||
* @property {Boolean} titleBorder = [true|false] 是否显示标题分隔线
|
||||
* @property {String} border = ['auto'|'show'|'none'] 是否显示分隔线
|
||||
* @property {Boolean} disabled = [true|false] 是否展开面板
|
||||
* @property {Boolean} showAnimation = [true|false] 开启动画
|
||||
* @property {Boolean} showArrow = [true|false] 是否显示右侧箭头
|
||||
*/
|
||||
export default {
|
||||
name: 'uniCollapseItem',
|
||||
props: {
|
||||
// 列表标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
name: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// #ifdef APP-PLUS
|
||||
// 是否显示动画,app 端默认不开启动画,卡顿严重
|
||||
showAnimation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
// 是否显示动画
|
||||
showAnimation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// #endif
|
||||
// 是否展开
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 缩略图
|
||||
thumb: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题分隔线显示类型
|
||||
titleBorder: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
// TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug
|
||||
const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
return {
|
||||
isOpen: false,
|
||||
isheight: null,
|
||||
height: 0,
|
||||
elId,
|
||||
nameSync: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
open(val) {
|
||||
this.isOpen = val
|
||||
this.onClick(val, 'init')
|
||||
}
|
||||
},
|
||||
updated(e) {
|
||||
this.$nextTick(() => {
|
||||
this.init(true)
|
||||
})
|
||||
},
|
||||
created() {
|
||||
this.collapse = this.getCollapse()
|
||||
this.oldHeight = 0
|
||||
this.onClick(this.open, 'init')
|
||||
},
|
||||
// #ifndef VUE3
|
||||
// TODO vue2
|
||||
destroyed() {
|
||||
if (this.__isUnmounted) return
|
||||
this.uninstall()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
// TODO vue3
|
||||
unmounted() {
|
||||
this.__isUnmounted = true
|
||||
this.uninstall()
|
||||
},
|
||||
// #endif
|
||||
mounted() {
|
||||
if (!this.collapse) return
|
||||
if (this.name !== '') {
|
||||
this.nameSync = this.name
|
||||
} else {
|
||||
this.nameSync = this.collapse.childrens.length + ''
|
||||
}
|
||||
if (this.collapse.names.indexOf(this.nameSync) === -1) {
|
||||
this.collapse.names.push(this.nameSync)
|
||||
} else {
|
||||
console.warn(`name 值 ${this.nameSync} 重复`);
|
||||
}
|
||||
if (this.collapse.childrens.indexOf(this) === -1) {
|
||||
this.collapse.childrens.push(this)
|
||||
}
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init(type) {
|
||||
// #ifndef APP-NVUE
|
||||
this.getCollapseHeight(type)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.getNvueHwight(type)
|
||||
// #endif
|
||||
},
|
||||
uninstall() {
|
||||
if (this.collapse) {
|
||||
this.collapse.childrens.forEach((item, index) => {
|
||||
if (item === this) {
|
||||
this.collapse.childrens.splice(index, 1)
|
||||
}
|
||||
})
|
||||
this.collapse.names.forEach((item, index) => {
|
||||
if (item === this.nameSync) {
|
||||
this.collapse.names.splice(index, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onClick(isOpen, type) {
|
||||
if (this.disabled) return
|
||||
this.isOpen = isOpen
|
||||
if (this.isOpen && this.collapse) {
|
||||
this.collapse.setAccordion(this)
|
||||
}
|
||||
if (type !== 'init') {
|
||||
this.collapse.onChange(isOpen, this)
|
||||
}
|
||||
},
|
||||
getCollapseHeight(type, index = 0) {
|
||||
const views = uni.createSelectorQuery().in(this)
|
||||
views
|
||||
.select(`#${this.elId}`)
|
||||
.fields({
|
||||
size: true
|
||||
}, data => {
|
||||
// TODO 百度中可能获取不到节点信息 ,需要循环获取
|
||||
if (index >= 10) return
|
||||
if (!data) {
|
||||
index++
|
||||
this.getCollapseHeight(false, index)
|
||||
return
|
||||
}
|
||||
// #ifdef APP-NVUE
|
||||
this.height = data.height + 1
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
this.height = data.height
|
||||
// #endif
|
||||
this.isheight = true
|
||||
if (type) return
|
||||
this.onClick(this.isOpen, 'init')
|
||||
})
|
||||
.exec()
|
||||
},
|
||||
getNvueHwight(type) {
|
||||
const result = dom.getComponentRect(this.$refs['collapse--hook'], option => {
|
||||
if (option && option.result && option.size) {
|
||||
// #ifdef APP-NVUE
|
||||
this.height = option.size.height + 1
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
this.height = option.size.height
|
||||
// #endif
|
||||
this.isheight = true
|
||||
if (type) return
|
||||
this.onClick(this.open, 'init')
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getCollapse(name = 'uniCollapse') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false;
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.uni-collapse-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
|
||||
/* #endif */
|
||||
&__title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
transition: border-bottom-color .3s;
|
||||
|
||||
// transition-property: border-bottom-color;
|
||||
// transition-duration: 5s;
|
||||
&-wrap {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
|
||||
}
|
||||
|
||||
&-box {
|
||||
padding: 0 15px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
background-color: #fff;
|
||||
color: #303133;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
||||
/* #endif */
|
||||
&.is-disabled {
|
||||
.uni-collapse-item__title-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.uni-collapse-item-border {
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
&.is-open {
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
&-img {
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
/* #ifndef APP-NVUE */
|
||||
white-space: nowrap;
|
||||
color: inherit;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&-arrow {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 10px;
|
||||
transform: rotate(0deg);
|
||||
|
||||
&-active {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
&__wrap {
|
||||
/* #ifndef APP-NVUE */
|
||||
will-change: height;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 0;
|
||||
|
||||
&.is--transition {
|
||||
// transition: all 0.3s;
|
||||
transition-property: height, border-bottom-width;
|
||||
transition-duration: 0.3s;
|
||||
/* #ifndef APP-NVUE */
|
||||
will-change: height;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
|
||||
|
||||
&-content {
|
||||
position: absolute;
|
||||
font-size: 13px;
|
||||
color: #303133;
|
||||
// transition: height 0.3s;
|
||||
border-bottom-color: transparent;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 0;
|
||||
|
||||
&.uni-collapse-item--border {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: red;
|
||||
border-bottom-color: #ebeef5;
|
||||
}
|
||||
|
||||
&.open {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--animation {
|
||||
transition-property: transform;
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-collapse">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* Collapse 折叠面板
|
||||
* @description 展示可以折叠 / 展开的内容区域
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=23
|
||||
* @property {String|Array} value 当前激活面板改变时触发(如果是手风琴模式,参数类型为string,否则为array)
|
||||
* @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
|
||||
* @event {Function} change 切换面板时触发,如果是手风琴模式,返回类型为string,否则为array
|
||||
*/
|
||||
export default {
|
||||
name: 'uniCollapse',
|
||||
emits:['change','activeItem','input','update:modelValue'],
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
accordion: {
|
||||
// 是否开启手风琴效果
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
// TODO 兼容 vue2 和 vue3
|
||||
dataValue() {
|
||||
let value = (typeof this.value === 'string' && this.value === '') ||
|
||||
(Array.isArray(this.value) && this.value.length === 0)
|
||||
let modelValue = (typeof this.modelValue === 'string' && this.modelValue === '') ||
|
||||
(Array.isArray(this.modelValue) && this.modelValue.length === 0)
|
||||
if (value) {
|
||||
return this.modelValue
|
||||
}
|
||||
if (modelValue) {
|
||||
return this.value
|
||||
}
|
||||
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dataValue(val) {
|
||||
this.setOpen(val)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
this.names = []
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(()=>{
|
||||
this.setOpen(this.dataValue)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
setOpen(val) {
|
||||
let str = typeof val === 'string'
|
||||
let arr = Array.isArray(val)
|
||||
this.childrens.forEach((vm, index) => {
|
||||
if (str) {
|
||||
if (val === vm.nameSync) {
|
||||
if (!this.accordion) {
|
||||
console.warn('accordion 属性为 false ,v-model 类型应该为 array')
|
||||
return
|
||||
}
|
||||
vm.isOpen = true
|
||||
}
|
||||
}
|
||||
if (arr) {
|
||||
val.forEach(v => {
|
||||
if (v === vm.nameSync) {
|
||||
if (this.accordion) {
|
||||
console.warn('accordion 属性为 true ,v-model 类型应该为 string')
|
||||
return
|
||||
}
|
||||
vm.isOpen = true
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
this.emit(val)
|
||||
},
|
||||
setAccordion(self) {
|
||||
if (!this.accordion) return
|
||||
this.childrens.forEach((vm, index) => {
|
||||
if (self !== vm) {
|
||||
vm.isOpen = false
|
||||
}
|
||||
})
|
||||
},
|
||||
resize() {
|
||||
this.childrens.forEach((vm, index) => {
|
||||
// #ifndef APP-NVUE
|
||||
vm.getCollapseHeight()
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
vm.getNvueHwight()
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
onChange(isOpen, self) {
|
||||
let activeItem = []
|
||||
|
||||
if (this.accordion) {
|
||||
activeItem = isOpen ? self.nameSync : ''
|
||||
} else {
|
||||
this.childrens.forEach((vm, index) => {
|
||||
if (vm.isOpen) {
|
||||
activeItem.push(vm.nameSync)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.$emit('change', activeItem)
|
||||
this.emit(activeItem)
|
||||
},
|
||||
emit(val){
|
||||
this.$emit('input', val)
|
||||
this.$emit('update:modelValue', val)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
.uni-collapse {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,86 +0,0 @@
|
||||
{
|
||||
"id": "uni-collapse",
|
||||
"displayName": "uni-collapse 折叠面板",
|
||||
"version": "1.4.4",
|
||||
"description": "Collapse 组件,可以折叠 / 展开的内容区域。",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"折叠",
|
||||
"折叠面板",
|
||||
"手风琴"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-scss",
|
||||
"uni-icons"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
|
||||
## Collapse 折叠面板
|
||||
> **组件名:uni-collapse**
|
||||
> 代码块: `uCollapse`
|
||||
> 关联组件:`uni-collapse-item`、`uni-icons`。
|
||||
|
||||
|
||||
折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
@@ -1,15 +0,0 @@
|
||||
## 1.0.1(2021-11-23)
|
||||
- 优化 label、label-width 属性
|
||||
## 1.0.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox)
|
||||
## 0.1.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 0.0.6(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 0.0.5(2021-04-21)
|
||||
- 优化 添加依赖 uni-icons, 导入后自动下载依赖
|
||||
## 0.0.4(2021-02-05)
|
||||
- 优化 组件引用关系,通过uni_modules引用组件
|
||||
## 0.0.3(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -1,275 +0,0 @@
|
||||
<template>
|
||||
<view class="uni-combox" :class="border ? '' : 'uni-combox__no-border'">
|
||||
<view v-if="label" class="uni-combox__label" :style="labelStyle">
|
||||
<text>{{label}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__input-box">
|
||||
<input class="uni-combox__input" type="text" :placeholder="placeholder"
|
||||
placeholder-class="uni-combox__input-plac" v-model="inputVal" @input="onInput" @focus="onFocus"
|
||||
@blur="onBlur" />
|
||||
<uni-icons :type="showSelector? 'top' : 'bottom'" size="14" color="#999" @click="toggleSelector">
|
||||
</uni-icons>
|
||||
</view>
|
||||
<view class="uni-combox__selector" v-if="showSelector">
|
||||
<view class="uni-popper__arrow"></view>
|
||||
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
|
||||
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
|
||||
<text>{{emptyTips}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index"
|
||||
@click="onSelectorClick(index)">
|
||||
<text>{{item}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Combox 组合输入框
|
||||
* @description 组合输入框一般用于既可以输入也可以选择的场景
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=1261
|
||||
* @property {String} label 左侧文字
|
||||
* @property {String} labelWidth 左侧内容宽度
|
||||
* @property {String} placeholder 输入框占位符
|
||||
* @property {Array} candidates 候选项列表
|
||||
* @property {String} emptyTips 筛选结果为空时显示的文字
|
||||
* @property {String} value 组合框的值
|
||||
*/
|
||||
export default {
|
||||
name: 'uniCombox',
|
||||
emits: ['input', 'update:modelValue'],
|
||||
props: {
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
candidates: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
emptyTips: {
|
||||
type: String,
|
||||
default: '无匹配项'
|
||||
},
|
||||
// #ifndef VUE3
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSelector: false,
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelStyle() {
|
||||
if (this.labelWidth === 'auto') {
|
||||
return ""
|
||||
}
|
||||
return `width: ${this.labelWidth}`
|
||||
},
|
||||
filterCandidates() {
|
||||
return this.candidates.filter((item) => {
|
||||
return item.toString().indexOf(this.inputVal) > -1
|
||||
})
|
||||
},
|
||||
filterCandidatesLength() {
|
||||
return this.filterCandidates.length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// #ifndef VUE3
|
||||
value: {
|
||||
handler(newVal) {
|
||||
this.inputVal = newVal
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
this.inputVal = newVal
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
toggleSelector() {
|
||||
this.showSelector = !this.showSelector
|
||||
},
|
||||
onFocus() {
|
||||
this.showSelector = true
|
||||
},
|
||||
onBlur() {
|
||||
setTimeout(() => {
|
||||
this.showSelector = false
|
||||
}, 153)
|
||||
},
|
||||
onSelectorClick(index) {
|
||||
this.inputVal = this.filterCandidates[index]
|
||||
this.showSelector = false
|
||||
this.$emit('input', this.inputVal)
|
||||
this.$emit('update:modelValue', this.inputVal)
|
||||
},
|
||||
onInput() {
|
||||
setTimeout(() => {
|
||||
this.$emit('input', this.inputVal)
|
||||
this.$emit('update:modelValue', this.inputVal)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uni-combox {
|
||||
font-size: 14px;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 4px;
|
||||
padding: 6px 10px;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
// height: 40px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
// border-bottom: solid 1px #DDDDDD;
|
||||
}
|
||||
|
||||
.uni-combox__label {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
padding-right: 10px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.uni-combox__input-box {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-combox__input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-combox__input-plac {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-combox__selector {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
top: calc(100% + 12px);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border: 1px solid #EBEEF5;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
z-index: 2;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.uni-combox__selector-scroll {
|
||||
/* #ifndef APP-NVUE */
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-combox__selector-empty,
|
||||
.uni-combox__selector-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
line-height: 36px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
// border-bottom: solid 1px #DDDDDD;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.uni-combox__selector-item:hover {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.uni-combox__selector-empty:last-child,
|
||||
.uni-combox__selector-item:last-child {
|
||||
/* #ifndef APP-NVUE */
|
||||
border-bottom: none;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
// picker 弹出层通用的指示小三角
|
||||
.uni-popper__arrow,
|
||||
.uni-popper__arrow::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 6px;
|
||||
}
|
||||
|
||||
.uni-popper__arrow {
|
||||
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
|
||||
top: -6px;
|
||||
left: 10%;
|
||||
margin-right: 3px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.uni-popper__arrow::after {
|
||||
content: " ";
|
||||
top: 1px;
|
||||
margin-left: -6px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.uni-combox__no-border {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,90 +0,0 @@
|
||||
{
|
||||
"id": "uni-combox",
|
||||
"displayName": "uni-combox 组合框",
|
||||
"version": "1.0.1",
|
||||
"description": "可以选择也可以输入的表单项 ",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"combox",
|
||||
"组合框",
|
||||
"select"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-scss",
|
||||
"uni-icons"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "n"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
|
||||
|
||||
## Combox 组合框
|
||||
> **组件名:uni-combox**
|
||||
> 代码块: `uCombox`
|
||||
|
||||
|
||||
组合框组件。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
@@ -1,26 +0,0 @@
|
||||
## 1.2.3(2024-02-20)
|
||||
- 新增 支持控制小时,分钟的显隐:showHour showMinute
|
||||
## 1.2.2(2022-01-19)
|
||||
- 修复 在微信小程序中样式不生效的bug
|
||||
## 1.2.1(2022-01-18)
|
||||
- 新增 update 方法 ,在动态更新时间后,刷新组件
|
||||
## 1.2.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown)
|
||||
## 1.1.3(2021-10-18)
|
||||
- 重构
|
||||
- 新增 font-size 支持自定义字体大小
|
||||
## 1.1.2(2021-08-24)
|
||||
- 新增 支持国际化
|
||||
## 1.1.1(2021-07-30)
|
||||
- 优化 vue3下小程序事件警告的问题
|
||||
## 1.1.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.0.5(2021-06-18)
|
||||
- 修复 uni-countdown 重复赋值跳两秒的 bug
|
||||
## 1.0.4(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.0.3(2021-05-08)
|
||||
- 修复 uni-countdown 不能控制倒计时的 bug
|
||||
## 1.0.2(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"uni-countdown.day": "day",
|
||||
"uni-countdown.h": "h",
|
||||
"uni-countdown.m": "m",
|
||||
"uni-countdown.s": "s"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import en from './en.json'
|
||||
import zhHans from './zh-Hans.json'
|
||||
import zhHant from './zh-Hant.json'
|
||||
export default {
|
||||
en,
|
||||
'zh-Hans': zhHans,
|
||||
'zh-Hant': zhHant
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"uni-countdown.day": "天",
|
||||
"uni-countdown.h": "时",
|
||||
"uni-countdown.m": "分",
|
||||
"uni-countdown.s": "秒"
|
||||
}
|
||||