增加补充相关资料和小程序源码

This commit is contained in:
Wang Beihong
2026-02-07 23:14:57 +08:00
parent a0febb1e5b
commit a9be1dd6b9
1255 changed files with 476253 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
---
name: 添加闹钟设置功能
overview: 在模式设置tab中添加三个闹钟时间设置功能支持时分设置、开关启用/禁用、以及通过MQTT下发控制指令到设备端。
design:
architecture:
component: tdesign
styleKeywords:
- 简洁
- 卡片式
- 列表
fontSystem:
fontFamily: PingFang SC
heading:
size: 16px
weight: 500
subheading:
size: 14px
weight: 400
body:
size: 14px
weight: 400
colorSystem:
primary:
- "#1989fa"
background:
- "#f7f8fa"
- "#ffffff"
text:
- "#323233"
- "#969799"
functional:
- "#07c160"
- "#ee0a24"
todos:
- id: add-alarm-data
content: 在homeContril.js中添加3个闹钟的数据定义
status: completed
- id: add-alarm-ui
content: 在homeContril.wxml"模式设置"tab中添加闹钟设置UI
status: completed
dependencies:
- add-alarm-data
- id: add-alarm-handlers
content: 在homeContril.js中添加闹钟时间选择和开关事件处理
status: completed
dependencies:
- add-alarm-data
- id: add-alarm-parse
content: 在parseESP32Data中添加闹钟状态解析
status: completed
dependencies:
- add-alarm-handlers
---
## 产品概述
在"模式设置"Tab中添加闹钟设置功能支持3个闹钟的配置每个闹钟包含时间和启用状态。
## 核心功能
- 3个独立闹钟设置闹钟1、闹钟2、闹钟3
- 每个闹钟可设置时间(时:分)
- 每个闹钟可启用/禁用
- 闹钟设置通过MQTT控制指令下发到设备
- 保持与现有代码风格一致使用Vant Weapp组件
## 技术栈
- 微信小程序原生框架
- Vant Weapp UI组件库
- MQTT通信协议
## 实现方案
在现有`homeContril.js``homeContril.wxml`基础上扩展:
1. **数据模型**添加3个闹钟的状态数据时间、启用状态
2. **UI界面**:使用`van-cell-group``van-cell`构建闹钟列表,每个闹钟包含:
- 时间选择器使用picker组件
- 开关控制启用/禁用
3. **控制指令**:复用现有的`sendControlCommand`方法,发送格式:
```
{"alarm1_time": "07:30", "alarm1_enable": true, "alarm2_time": "08:00", ...}
```
4. **设备数据解析**:在`parseESP32Data`中解析设备上报的闹钟状态
## 目录结构
```
pages/homeControl/
├── homeContril.wxml # [MODIFY] 在"模式设置"tab添加闹钟设置UI
├── homeContril.js # [MODIFY] 添加闹钟数据定义和事件处理
└── homeContril.wxss # [MODIFY] 添加闹钟样式(如需要)
```
## 设计内容
在"模式设置"Tab中实现3个闹钟设置卡片每个卡片包含
- 左侧:闹钟图标+编号闹钟1/2/3
- 中间:时间显示(点击弹出时间选择器)
- 右侧:启用/禁用开关
使用Vant Weapp的`van-cell-group`组织布局,保持与"传感器数据"tab一致的设计风格。

View File

@@ -0,0 +1,31 @@
/*
* Eslint config file
* Documentation: https://eslint.org/docs/user-guide/configuring/
* Install the Eslint extension before using this feature.
*/
module.exports = {
env: {
es6: true,
browser: true,
node: true,
},
ecmaFeatures: {
modules: true,
},
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
globals: {
wx: true,
App: true,
Page: true,
getCurrentPages: true,
getApp: true,
Component: true,
requirePlugin: true,
requireMiniProgram: true,
},
// extends: 'eslint:recommended',
rules: {},
}

10
微信小程序源码/iot-home/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@@ -0,0 +1,31 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="17">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="van-nav-bar" />
<item index="7" class="java.lang.String" itemvalue="van-notice-bar" />
<item index="8" class="java.lang.String" itemvalue="van-tabs" />
<item index="9" class="java.lang.String" itemvalue="van-tab" />
<item index="10" class="java.lang.String" itemvalue="van-grid" />
<item index="11" class="java.lang.String" itemvalue="van-grid-item" />
<item index="12" class="java.lang.String" itemvalue="van-switch" />
<item index="13" class="java.lang.String" itemvalue="van-cell-group" />
<item index="14" class="java.lang.String" itemvalue="van-cell" />
<item index="15" class="java.lang.String" itemvalue="van-tag" />
<item index="16" class="java.lang.String" itemvalue="van-slider" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/iot-home.iml" filepath="$PROJECT_DIR$/.idea/iot-home.iml" />
</modules>
</component>
</project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@@ -0,0 +1,3 @@
{
"Codegeex.RepoIndex": true
}

View File

@@ -0,0 +1,347 @@
# 智能家居控制系统
这是一个基于微信小程序的智能家居控制系统,通过 MQTT 协议与 ESP32 等物联网设备进行通信。
## 功能特点
- 实时监控 ESP32 设备传感器数据(温度、湿度等)
- 远程控制智能家居设备LED、风扇、窗帘等
- 设备状态管理
- 统一的物联网设备通信协议
## 通信协议
### 消息结构
所有通信消息均采用统一的 JSON 格式:
```json
{
"type": "device_message|control_command|response",
"device_id": "string",
"device_type": "string",
"timestamp": 1234567890123,
"message_type": "string",
"request_id": "string (optional)",
"data": {
// 具体数据内容
},
"status_code": 200,
"status_message": "string (optional)"
}
```
### 字段说明
- `type`: 消息类型
- `device_message`: 设备主动上报的消息(如传感器数据、状态变化)
- `control_command`: 控制命令(如开关设备、设置参数,包含配置更新)
- `response`: 响应消息(设备对命令的响应)
- `device_id`: 设备唯一标识符,例如:"esp32_bedroom_001"
- `device_type`: 设备类型,固定为:"bedroom_controller"(卧室控制器)
- `timestamp`: Unix 时间戳(毫秒),由设备或服务器提供
- `message_type`: 消息子类型,如:"sensor_data", "status_report", "control_request", "config_set"
- `request_id`: 请求 ID用于匹配请求和响应格式为"req_YYYYMMDDHHMMSS_xxx"
- `data`: 具体的数据内容,包含状态、遥测、控制或配置信息
- `status_code`: 状态码200 表示成功,其他表示错误)
- `status_message`: 状态描述,可选字段
## 3. 智能卧室系统专用消息类型
### 3.1 传感器数据上传 (device_message)
ESP32 定期上报卧室环境数据:
```json
{
"type": "device_message",
"device_id": "esp32_bedroom_001",
"device_type": "bedroom_controller",
"timestamp": 1768223104055,
"message_type": "sensor_data",
"data": {
"state": {
"online": true,
"current_mode": "night_mode",
"home_status": true,
"standby_mode": false,
"error_code": 0
},
"telemetry": {
"temperature": 23.5,
"humidity": 65.2,
"light_intensity": 300,
"air_quality": 850,
"curtain_state": "closed",
"led_state": true,
"led_brightness": 80,
"fan_state": false,
"fan_speed": 0,
"buzzer_state": false,
"battery_level": 100,
"signal_strength": -55
}
}
}
```
**参数解释:**
- `state.online`: 设备是否在线
- `state.current_mode`: 当前运行模式("away_mode", "home_mode", "wake_mode", "day_mode", "night_mode", "cooling_mode", "high_temp_alert", "display_on", "ventilation_mode"
- `state.home_status`: 用户是否在家状态(对应"到家状态"
- `state.standby_mode`: 是否处于待机状态
- `telemetry.temperature`: 温度传感器(DHT22)读取的当前温度(摄氏度)
- `telemetry.humidity`: 湿度传感器(DHT22)读取的当前湿度(百分比)
- `telemetry.light_intensity`: 光敏电阻读取的光照强度(数值越大表示越亮)
- `telemetry.air_quality`: 空气质量传感器读取的 CO2 浓度ppm
- `telemetry.curtain_state`: 舵机控制的窗帘状态("open", "closed"
- `telemetry.led_state`: LED 灯开关状态
- `telemetry.led_brightness`: LED 灯亮度0-100 百分比)
- `telemetry.fan_state`: 风扇开关状态
- `telemetry.fan_speed`: 风扇速度档位0-3
- `telemetry.buzzer_state`: 蜂鸣器开关状态
### 控制命令消息格式
微信小程序向 ESP32 发送控制指令,同时包含设备控制和配置更新:
```json
{
"type": "control_command",
"device_id": "esp32_bedroom_001",
"device_type": "bedroom_controller",
"timestamp": 1768223104055,
"message_type": "control_request",
"request_id": "req_123456789_abcde",
"data": {
"controls": {
"mode": "night_mode",
"curtain": "open",
"led_power": true,
"led_brightness": 80,
"fan_power": true,
"fan_speed": 2,
"buzzer_power": false
},
"config": {
"wake_up_time": "08:00",
"day_mode_start": "06:00",
"night_mode_start": "18:00",
"cooling_temp_threshold": 26,
"high_temp_alarm_threshold": 35,
"ventilation_air_quality_threshold": 1000,
"report_interval": 30,
"oled_display_enabled": true
}
}
}
```
**参数解释:**
- `controls.mode`: 设置系统运行模式("wake_mode", "day_mode", "night_mode", "cooling_mode", "ventilation_mode"等)
- `controls.curtain`: 控制窗帘开关("open", "close"
- `controls.led_power`: 控制 LED 灯开关
- `controls.led_brightness`: 设置 LED 灯亮度0-100 百分比)
- `controls.fan_power`: 控制风扇开关
- `controls.fan_speed`: 设置风扇速度0-3 档)
- `controls.buzzer_power`: 控制蜂鸣器开关
- `config.wake_up_time`: 起床模式触发时间HH:MM 格式)
- `config.day_mode_start`: 白天模式开始时间
- `config.night_mode_start`: 夜间模式开始时间
- `config.cooling_temp_threshold`: 降温模式启动温度阈值(摄氏度)
- `config.high_temp_alarm_threshold`: 高温提醒阈值35°C 时触发)
- `config.ventilation_air_quality_threshold`: 自动通风启动空气质量阈值CO2>1000ppm 时启动)
- `config.report_interval`: 传感器数据上报间隔(秒)
- `config.oled_display_enabled`: OLED 显示屏是否启用
### 设备状态上报消息格式
```json
{
"type": "device_message",
"device_id": "esp32_bedroom_001",
"device_type": "bedroom_controller",
"timestamp": 1768223104055,
"message_type": "sensor_data",
"data": {
"state": {
"online": true,
"current_mode": "day_mode",
"led_state": true,
"fan_state": false
},
"telemetry": {
"temperature": 23.5,
"humidity": 65.2,
"light_intensity": 300,
"air_quality": 45
}
}
}
```
### 响应消息 (response)
ESP32 对控制命令的响应:
```json
{
"type": "response",
"device_id": "esp32_bedroom_001",
"device_type": "bedroom_controller",
"timestamp": 1768223104060,
"message_type": "control_response",
"request_id": "req_123456789_abcde",
"data": {
"result": {
"led_power": true,
"led_brightness": 80,
"execution_time": 120
}
},
"status_code": 200,
"status_message": "Command executed successfully"
}
```
**参数解释:**
- `request_id`: 对应的请求 ID用于匹配请求和响应
- `result.execution_time`: 命令执行耗时(毫秒)
- `status_code`: 执行结果状态码200 成功500 失败等)
- `status_message`: 执行结果描述
## MQTT 主题
- **设备数据上报**: `sensor/{device_id}`
- **控制命令下发**: `control/{device_id}`
## 设备管理
- 自动注册:设备首次发送消息时自动添加到系统
- 数据更新:根据设备发送的消息自动更新状态
- 在线状态:实时跟踪设备连接状态
## 控制功能
- LED 开关控制
- 风扇开关和速度控制
- 窗帘开关控制
- 设备配置更新(温度阈值、警报阈值等)
## 使用方法
1. 配置 MQTT 服务器参数
2. 连接到 ESP32 设备
3. 实时查看传感器数据
4. 通过控制面板发送控制命令
5. 更新设备配置参数
```javascript
/**
* 示例切换LED状态
*/
toggleLed() {
// 获取当前LED状态
const currentLedState = this.extractESP32Info("data.telemetry.led_state");
// 发送控制命令切换LED状态
this.sendControlCommand({
led_power: !currentLedState,
});
},
/**
* 示例:打开窗帘
*/
setCurtainOpen() {
this.sendControlCommand({
curtain: "open",
});
},
/**
* 示例:关闭窗帘
*/
setCurtainClose() {
this.sendControlCommand({
curtain: "close",
});
},
/**
* 示例:设置风扇速度
*/
setFanSpeed(speed) {
this.sendControlCommand({
fan_power: true,
fan_speed: parseInt(speed),
});
},
/**
* 示例:更新设备配置
*/
updateDeviceConfig(config) {
// 发送配置更新命令(现在通过控制命令发送)
this.sendControlCommand({}, { config: config });
},
/**
* 示例:设置温度阈值
*/
setTemperatureThreshold(threshold) {
this.updateDeviceConfig({
cooling_temp_threshold: threshold,
});
},
/**
* 示例:设置高温警报阈值
*/
setHighTempAlarmThreshold(threshold) {
this.updateDeviceConfig({
high_temp_alarm_threshold: threshold,
});
},
/**
* 示例:显示设备最新数据
*/
showLatestData() {
// 提取温度
const temperature = this.extractESP32Info("data.telemetry.temperature");
console.log("当前温度:", temperature);
// 提取湿度
const humidity = this.extractESP32Info("data.telemetry.humidity");
console.log("当前湿度:", humidity);
// 提取当前模式
const mode = this.extractESP32Info("data.state.current_mode");
console.log("当前模式:", mode);
// 提取所有设备数据
const allData = this.extractESP32Info();
console.log("全部数据:", allData);
},
```
以上的代码片段中,我们定义了一些示例方法,用于切换 LED 状态、打开窗帘、关闭窗帘、设置风扇速度、更新设备配置、设置温度阈值、设置高温警报阈值和显示设备最新数据。
这些方法通过调用`this.sendControlCommand()``this.updateDeviceConfig()`方法来发送控制命令和配置更新命令,并使用`this.extractESP32Info()`方法来提取设备数据。
这些方法中,`this.sendControlCommand()``this.updateDeviceConfig()`方法用于发送控制命令和配置更新命令, respectively。 它们接受一个参数,即控制命令或配置更新。
`this.extractESP32Info()`方法用于从设备数据中提取信息。 它接受一个参数,即要提取的信息的路径。 例如,`this.extractESP32Info("data.telemetry.temperature")`将返回当前温度。
```javascript
this.sendControlCommand(
{ light: "on", fan: "off" }, // 控制参数
{ brightness: 80 }, // 配置参数
(response) => {
// 回调函数
console.log("响应:", response);
}
);
```

View File

@@ -0,0 +1,25 @@
// app.js
App({
globalData: {
// 两个全局变量
mqttClient: null, // 用于存储MQTT连接实例
mqttConnected: false // 用于存储MQTT连接状态
},
// 添加两个方法
/**
* 提供访问MQTT实例的接口 其他页面通过getApp().getMQTTClient()获取实例
*/
getMQTTClient: function() {
return this.globalData.mqttClient;
},
/**
* 检查MQTT是否连接 避免在未连接时进行MQTT操作
*/
isMQTTConnected: function() {
return this.globalData.mqttConnected;
}
});

View File

@@ -0,0 +1,81 @@
{
"pages": [
"pages/index/index",
"pages/user-info/user-info",
"pages/homeControl/homeContril"
],
"window": {
"navigationBarTextStyle": "black",
"navigationStyle": "custom"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "assets/icons/info.png",
"selectedIconPath": "assets/icons/info-active.png",
"text": "首页"
},
{
"pagePath": "pages/homeControl/homeContril",
"iconPath": "assets/icons/control.png",
"selectedIconPath": "assets/icons/control-active.png",
"text": "家庭控制"
}
]
},
"renderer": "skyline",
"rendererOptions": {
"skyline": {
"defaultDisplayBlock": true,
"defaultContentBox": true,
"disableABTest": true,
"sdkVersionBegin": "3.0.0",
"sdkVersionEnd": "15.255.255"
}
},
"componentFramework": "glass-easel",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents",
"usingComponents": {
"fui-button": "components/firstui/fui-button/fui-button",
"fui-text": "components/firstui/fui-text/fui-text",
"fui-input": "components/firstui/fui-input/fui-input",
"fui-top-popup": "components/firstui/fui-top-popup/fui-top-popup",
"fui-icon": "components/firstui/fui-icon/fui-icon",
"fui-toast": "/components/firstui/fui-toast/fui-toast",
"van-button": "@vant/weapp/button/index",
"van-nav-bar": "@vant/weapp/nav-bar/index",
"van-switch": "@vant/weapp/switch/index",
"van-icon": "@vant/weapp/icon/index",
"van-slider": "@vant/weapp/slider/index",
"van-notice-bar": "@vant/weapp/notice-bar/index",
"van-tab": "@vant/weapp/tab/index",
"van-tabs": "@vant/weapp/tabs/index",
"van-grid": "@vant/weapp/grid/index",
"van-grid-item": "@vant/weapp/grid-item/index",
"van-cell": "@vant/weapp/cell/index",
"van-cell-group": "@vant/weapp/cell-group/index",
"van-tag": "@vant/weapp/tag/index"
},
"permission": {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
}
},
"networkTimeout": {
"request": 30000,
"connectSocket": 30000,
"uploadFile": 30000,
"downloadFile": 30000
},
"subpackages": [],
"debug": true
}

View File

@@ -0,0 +1,10 @@
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,124 @@
Component({
properties: {
//是否显示操作菜单
show: {
type: Boolean,
value: false
},
//菜单按钮数组,可自定义文本颜色
itemList: {
type: Array,
value: [],
observer(val) {
this.initData(val)
}
},
textKey: {
type: String,
value: 'text'
},
//菜单按钮字体大小 rpx
itemSize: {
type: String,
optionalTypes: [Number],
value: 32
},
//v2.4.0+
itemColor: {
type: String,
value: '#181818'
},
//v2.4.0+
itemDarkColor: {
type: String,
value: '#D1D1D1'
},
//提示信息
tips: {
type: String,
value: ""
},
//提示信息文本颜色
color: {
type: String,
value: "#7F7F7F"
},
//提示文字大小 rpx
size: {
type: String,
optionalTypes: [Number],
value: 26
},
//是否需要圆角
radius: {
type: Boolean,
value: true
},
//是否需要取消按钮
isCancel: {
type: Boolean,
value: true
},
//v2.4.0+
cancelSize: {
type: String,
optionalTypes: [Number],
value: 32
},
//light/dark
theme: {
type: String,
value: 'light'
},
//点击遮罩 是否可关闭
maskClosable: {
type: Boolean,
value: false
},
zIndex: {
type: String,
optionalTypes: [Number],
value: 1001
}
},
data: {
vals: []
},
lifetimes: {
attached: function () {
this.initData(this.data.itemList)
}
},
methods: {
initData(vals) {
if (vals && vals.length > 0) {
if (typeof vals[0] !== 'object') {
vals = vals.map(item => {
return {
[this.data.textKey]: item
}
})
}
this.setData({
vals: vals
})
}
},
handleClickMask() {
if (!this.data.maskClosable) return;
this.handleClickCancel();
},
handleClickItem(e) {
let index = Number(e.currentTarget.dataset.index)
if (!this.data.show) return;
this.triggerEvent('click', {
index: index,
...this.data.vals[index]
});
},
handleClickCancel() {
this.triggerEvent('cancel');
},
stop() {}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,18 @@
<view catchtouchmove="stop">
<view class="fui-actionsheet__mask {{show?'fui-actionsheet__mask-show':''}}" bindtap="handleClickMask" style="{{parse.getStyle(zIndex)}}"></view>
<view class="fui-actionsheet__wrap {{show?'fui-actionsheet__show':''}} {{radius?'fui-actionsheet__radius':''}} {{theme==='light'?'fui-as__bg-light':''}} {{theme==='dark'?'fui-as__bg-dark':''}}" style="z-index:{{zIndex}}">
<text class="fui-actionsheet__tips {{radius?'fui-actionsheet__radius':''}} {{theme==='light'?'fui-as__btn-light':''}} {{theme==='dark'?'fui-as__btn-dark':''}}" style="font-size:{{size}}rpx;color:{{color}}" wx:if="{{tips}}">{{tips}}</text>
<view class="{{isCancel?'fui-actionsheet__operate-box':''}}">
<text class="fui-actionsheet__btn {{!isCancel && index==vals.length-1?'fui-actionsheet__btn-last':''}} {{radius && !tips && index===0?'fui-actionsheet__radius':''}} {{(index!==0 || tips) && theme==='light'?'fui-as__divider-light':''}} {{(index!==0 || tips) && theme==='dark'?'fui-as__divider-dark':''}} {{theme==='light'?'fui-as__btn-light':''}} {{theme==='dark'?'fui-as__btn-dark':''}}" style="color:{{theme==='dark'?(item.darkColor || itemDarkColor):(item.color || itemColor)}};font-size:{{item.size || itemSize}}rpx" wx:for="{{vals}}" wx:key="index" bindtap="handleClickItem" data-index="{{index}}">{{item[textKey]}}</text>
</view>
<text style="color:{{theme==='dark'?itemDarkColor:itemColor}};font-size:{{cancelSize || itemSize}}rpx" class="fui-actionsheet__btn fui-actionsheet__cancel {{theme==='light'?'fui-as__btn-light':''}} {{theme==='dark'?'fui-as__btn-dark':''}}" wx:if="{{isCancel}}" bindtap="handleClickCancel">取消</text>
</view>
</view>
<wxs module="parse">
module.exports = {
getStyle: function (zIndex) {
return "z-index:" + (parseInt(zIndex) - 10)
}
}
</wxs>

View File

@@ -0,0 +1,128 @@
.fui-actionsheet__wrap {
width: 100%;
visibility: hidden;
min-height: 100rpx;
position: fixed;
left: 0;
right: 0;
bottom: 0;
transform: translate3d(0, 100%, 0);
transition: all 0.25s ease-in-out;
transform-origin: center center;
}
.fui-as__bg-light {
background-color: #F8F8F8;
}
.fui-as__bg-dark {
background-color: #111111;
}
.fui-actionsheet__radius {
border-top-left-radius: 24rpx;
border-top-right-radius: 24rpx;
overflow: hidden;
}
.fui-actionsheet__show {
transform: translate3d(0, 0, 0);
visibility: visible;
}
.fui-actionsheet__tips {
width: 100%;
box-sizing: border-box;
flex: 1;
padding: 40rpx 60rpx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
font-weight: normal;
}
.fui-as__btn-light {
background-color: #FFFFFF;
}
.fui-as__btn-dark {
background-color: #222222;
}
.fui-actionsheet__operate-box {
padding-bottom: 12rpx;
}
.fui-actionsheet__btn {
width: 100%;
flex: 1;
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
/* font-size: 32rpx; */
font-weight: normal;
position: relative;
}
.fui-actionsheet__btn:active {
background-color: var(--fui-bg-color-hover, rgba(0, 0, 0, 0.2)) !important;
}
.fui-actionsheet__btn-last {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.fui-as__divider-light::before {
content: " ";
position: absolute;
top: 0;
right: 0;
left: 0;
border-top: 1px solid var(--fui-color-border, #EEEEEE);
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transform-origin: 0 0;
z-index: 2;
pointer-events: none;
}
.fui-as__divider-dark::before {
content: " ";
position: absolute;
top: 0;
right: 0;
left: 0;
border-top: 1px solid #333;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transform-origin: 0 0;
z-index: 2;
pointer-events: none;
}
.fui-actionsheet__cancel {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
.fui-actionsheet__mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--fui-bg-color-mask, rgba(0, 0, 0, 0.6));
transition: opacity 0.3s ease-in-out;
opacity: 0;
visibility: hidden;
}
.fui-actionsheet__mask-show {
visibility: visible;
opacity: 1;
}

View File

@@ -0,0 +1,112 @@
Component({
options: {
multipleSlots: true
},
properties: {
//info, success, warn, waiting,clear
type: {
type: String,
value: ''
},
//背景色如果设置type对应颜色失效
background: {
type: String,
value: ''
},
//padding值
padding: {
type: String,
value:'20rpx 32rpx'
},
//margin-top值单位rpx
marginTop: {
type: String,
optionalTypes: [Number],
value: 0
},
//margin-bottom值单位rpx
marginBottom: {
type: String,
optionalTypes: [Number],
value: 0
},
//圆角
radius: {
type: String,
value: '16rpx'
},
//icon颜色
iconColor: {
type: String,
value: '#fff'
},
//icon字体大小px
iconSize: {
type: Number,
value: 22
},
closable: {
type: Boolean,
value: false
},
closeColor: {
type: String,
value: '#fff'
},
//关闭icon字体大小px
closeSize: {
type: Number,
value: 22
},
//是否自定义左侧内容,默认图标失效
isLeft: {
type: Boolean,
value: false
},
//内容是否与图标之间有间隔isLeft为true时生效
spacing: {
type: Boolean,
value: false
},
title: {
type: String,
value: ''
},
color: {
type: String,
value: '#fff'
},
size: {
type: String,
value: '14px'
},
desc: {
type: String,
value: ''
},
descColor: {
type: String,
value: '#fff'
},
descSize: {
type: String,
value: '12px'
},
//描述文字单行展示,超出隐藏
single: {
type: Boolean,
value: false
}
},
methods: {
leftClick() {
this.triggerEvent('leftClick', {})
},
onClick() {
this.triggerEvent('click', {})
},
close() {
this.triggerEvent('close', {})
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,30 @@
<view class="fui-alert__wrap {{background?'':'fui-alert__'+type}}" style="background:{{background || parse.getColor(type)}};border-radius:{{radius}};padding:{{padding}};margin-top:{{marginTop}}rpx;margin-bottom:{{marginBottom}}rpx">
<view class="fui-alert__shrink" catchtap="leftClick">
<slot></slot>
<icon type="{{type}}" size="{{iconSize}}" color="{{iconColor}}" wx:if="{{!isLeft && type && type!==true}}"></icon>
</view>
<view class="fui-alert__content {{(!isLeft && type && type!=='true') || (spacing && isLeft)?'fui-text__p-left':''}} {{closable?'fui-text__p-right':''}}" catchtap="onClick">
<text class="fui-alert__text" style="font-size:{{size}};color:{{color}}" wx:if="{{title}}">{{title}}</text>
<text class="fui-alert__text fui-desc__padding {{single?'fui-alert__single':''}}" style="font-size:{{descSize}};color:{{descColor}}" wx:if="{{desc}}">{{desc}}</text>
<slot name="content"></slot>
</view>
<view class="fui-alert__shrink">
<slot name="right"></slot>
</view>
<icon catchtap="close" type="cancel" size="{{closeSize}}" color="{{closeColor}}" wx:if="{{closable}}" class=" {{desc?'fui-alert__icon-close':''}}">
</icon>
</view>
<wxs module="parse">
module.exports = {
getColor: function (type) {
var color = "#465CFF"
var colors = {
'success': '#09BE4F',
'warn': '#FFB703',
'clear': '#FF2B2B'
}
return colors[type] ? colors[type] : color;
}
}
</wxs>

View File

@@ -0,0 +1,71 @@
.fui-alert__wrap {
display: flex;
width: 100%;
box-sizing: border-box;
flex-direction: row;
align-items: center;
position: relative;
}
.fui-alert__info,
.fui-alert__waiting {
background-color: var(--fui-color-primary, #465CFF) !important;
}
.fui-alert__success {
background-color: var(--fui-color-success, #09BE4F) !important;
}
.fui-alert__warn {
background-color: var(--fui-color-warning, #FFB703) !important;
}
.fui-alert__clear {
background-color: var(--fui-color-danger, #FF2B2B) !important;
}
.fui-alert__shrink {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.fui-alert__content {
flex: 1;
flex-direction: column;
overflow: hidden;
}
.fui-alert__text {
word-break: break-all;
display: block;
box-sizing: border-box;
}
.fui-desc__padding {
padding-top: 3px;
}
.fui-text__p-left {
padding-left: 20rpx;
}
.fui-text__p-right {
padding-right: 60rpx;
}
.fui-alert__single {
display: block;
width: 100%;
white-space: nowrap;
flex-direction: row;
overflow: hidden;
text-overflow: ellipsis;
}
.fui-alert__icon-close {
position: absolute;
right: 30rpx;
top: 16rpx;
}

View File

@@ -0,0 +1,180 @@
Component({
properties: {
//是否显示
show: {
type: Boolean,
value: false,
observer(newVal) {
if (newVal) {
this.open();
} else {
this.data.isShow && this.close();
}
}
},
/*
过渡动画类型
['fade,'slide-top','slide-right','slide-bottom','slide-left','zoom-in','zoom-out']
*/
animationType: {
type: Array,
value: []
},
duration: {
type: Number,
value: 300
},
//styles 组件样式,同 css 样式
styles: {
type: Object,
value: {},
observer(val) {
this.setData({
stylesObject: this.getStylesObject()
})
}
}
},
data: {
isShow: false,
transform: '',
ani: {
in: '',
active: ''
},
stylesObject: ''
},
lifetimes: {
attached: function () {
this.setData({
stylesObject: this.getStylesObject()
})
}
},
methods: {
getStylesObject() {
//默认值
const defStyles = {
position: 'fixed',
bottom: 0,
top: 0,
left: 0,
right: 0,
display: 'flex',
'justify-content': 'center',
'align-items': 'center'
};
const mergeStyles = Object.assign({}, defStyles, this.data.styles);
let styles = {
...mergeStyles,
'transition-duration': this.data.duration / 1000 + 's'
};
let transfrom = '';
for (let i in styles) {
let line = this.toLine(i);
transfrom += line + ':' + styles[i] + ';';
}
return transfrom;
},
change() {
this.triggerEvent('click', {
value: this.data.isShow
});
},
open() {
clearTimeout(this.data.timer);
let ani_in = `ani.in`
this.setData({
isShow: true,
transform: '',
[ani_in]: ''
})
let transform = ''
for (let i in this.getTranfrom(false)) {
if (i === 'opacity') {
let ani_in = `ani.in`
this.setData({
[ani_in]: 'fui-popup__fade-out'
})
} else {
transform += `${this.getTranfrom(false)[i]} `;
}
}
this.setData({
transform: transform
}, () => {
setTimeout(() => {
this._animation(true);
}, 50);
})
},
close(type) {
clearTimeout(this.data.timer);
this._animation(false);
},
_animation(type) {
let styles = this.getTranfrom(type);
this.setData({
transform: ''
})
let transform = ''
let ani_in = `ani.in`
for (let i in styles) {
if (i === 'opacity') {
this.setData({
[ani_in]: `fui-popup__fade-${type ? 'in' : 'out'}`
})
} else {
transform += `${styles[i]} `;
}
}
this.setData({
transform: transform
})
this.data.timer = setTimeout(() => {
if (!type) {
this.setData({
isShow: false
})
}
this.triggerEvent('change', {
value: this.data.isShow
});
}, this.data.duration);
},
getTranfrom(type) {
let styles = {
transform: ''
};
this.data.animationType.forEach(mode => {
switch (mode) {
case 'fade':
styles.opacity = type ? 1 : 0;
break;
case 'slide-top':
styles.transform += `translateY(${type ? '0' : '-100%'}) `;
break;
case 'slide-right':
styles.transform += `translateX(${type ? '0' : '100%'}) `;
break;
case 'slide-bottom':
styles.transform += `translateY(${type ? '0' : '100%'}) `;
break;
case 'slide-left':
styles.transform += `translateX(${type ? '0' : '-100%'}) `;
break;
case 'zoom-in':
styles.transform += `scale(${type ? 1 : 0.8}) `;
break;
case 'zoom-out':
styles.transform += `scale(${type ? 1 : 1.2}) `;
break;
}
});
return styles;
},
toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,4 @@
<view wx:if="{{isShow}}" class="fui-popup__animation {{ani.in}}"
style="{{'transform:' + transform + ';' + stylesObject}}" bindtap="change">
<slot></slot>
</view>

View File

@@ -0,0 +1,20 @@
.fui-popup__animation {
transition-timing-function: ease;
transition-duration: 0.3s;
transition-property: transform, opacity;
position: relative;
z-index: 99;
}
.fui-popup__fade-out {
opacity: 0;
}
.fui-popup__fade-in {
opacity: 1;
}
.fui-popup__unshow{
opacity: 0;
visibility: hidden;
}

View File

@@ -0,0 +1,144 @@
Component({
properties: {
src: {
type: String,
value: '',
observer(val){
if(val){
this.setData({
showImg: val
})
}
}
},
errorSrc: {
type: String,
value: ''
},
mode: {
type: String,
value: 'scaleToFill'
},
//微信小程序、百度小程序、字节跳动小程序
//图片懒加载。只针对page与scroll-view下的image有效
lazyLoad: {
type: Boolean,
value: true
},
//默认不解析 webP 格式,只支持网络资源 微信小程序2.9.0
webp: {
type: Boolean,
value: false
},
background: {
type: String,
value: '#D1D1D1'
},
//small64、middle96、large128
size: {
type: String,
value: 'middle'
},
//图片宽度设置大于0的数值生效默认使用size
width: {
type: String,
optionalTypes: [Number],
value: 0
},
//默认等宽设置图大于0的数值且设置了图片宽度生效
height: {
type: String,
optionalTypes: [Number],
value: 0
},
//指定头像的形状,可选值为 circle、square
shape: {
type: String,
value: 'circle'
},
//图片圆角值默认使用shape当设置大于等于0的数值shape失效
radius: {
type: String,
optionalTypes: [Number],
value: -1
},
//没有src时可以使用文本代替
text: {
type: String,
value: ''
},
color: {
type: String,
value: '#fff'
},
//默认使用size下字体大小
fontSize: {
type: String,
optionalTypes: [Number],
value: 0
},
fontWeight: {
type: String,
optionalTypes: [Number],
value: 600
},
marginRight: {
type: String,
optionalTypes: [Number],
value: 0
},
marginBottom: {
type: String,
optionalTypes: [Number],
value: 0
},
block: {
type: Boolean,
value: false
},
//在列表中的索引值
index: {
type: Number,
value: 0
},
//其他参数
params: {
type: String,
optionalTypes: [Number],
value: 0
}
},
data:{
showImg:''
},
lifetimes:{
attached:function(){
if(this.data.src){
this.setData({
showImg: this.data.src
})
}
}
},
methods: {
handleError(e) {
if (this.data.src) {
if(this.data.errorSrc){
this.setData({
showImg: this.data.errorSrc
})
}
this.triggerEvent('error', {
index: this.data.index,
params: this.data.params
})
}
},
handleClick() {
this.triggerEvent('click', {
index: this.data.index,
params: this.data.params
})
}
}
})

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1,33 @@
<view class="fui-avatar__wrap {{width?'':'fui-avatar__size-'+size}} {{radius===-1?'fui-avatar__'+shape:''}} {{block?'fui-avatar__block':'fui-avatar__inline'}}" style="{{parse.wrapStyles(background, marginRight, marginBottom, width, height, radius)}}" bindtap="handleClick">
<image class="fui-avatar__img {{radius===-1?'fui-avatar__'+shape:''}} {{width?'':'fui-avatar__size-'+size}}" style="{{parse.styles(width, height, radius)}}" src="{{showImg}}" mode="{{mode}}" wx:if="{{src && src!==true}}" webp="{{webp}}" lazy-load="{{lazyLoad}}" binderror="handleError"></image>
<text class="fui-avatar__text {{width?'':'fui-avatar__text-'+size}}" wx:if="{{!src && src!==true && text}}" style="{{parse.textStyles(color, fontWeight, fontSize)}}">{{text}}</text>
<slot></slot>
</view>
<wxs module="parse">
var styles = function (width, height, radius) {
var styles = '';
if (width) {
styles = "width:" + width + "rpx;height:" + (height || width) + "rpx;"
}
if (radius !== -1) {
styles += "border-radius:" + radius + "rpx;"
}
return styles;
}
module.exports = {
wrapStyles: function (background, marginRight, marginBottom, width, height, radius) {
var style = "background:" + background + ";margin-right:" + marginRight + "rpx;margin-bottom:" + marginBottom + "rpx;"
return style + styles(width, height, radius)
},
styles: styles,
textStyles: function (color, fontWeight, fontSize) {
var styles = "color:" + color + ";font-weight:" + fontWeight + ";";
if (fontSize) {
styles += "font-size:" + fontSize + "rpx;"
}
return styles;
}
}
</wxs>

View File

@@ -0,0 +1,68 @@
.fui-avatar__wrap {
position: relative;
flex-direction: row;
align-items: center;
justify-content: center;
z-index: 3;
overflow: hidden;
}
.fui-avatar__block {
display: flex;
}
.fui-avatar__inline {
display: inline-flex;
vertical-align: middle;
}
.fui-avatar__img {
flex: 1;
display: block;
object-fit: cover;
}
.fui-avatar__text {
flex: 1;
display: block;
white-space: nowrap;
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
}
.fui-avatar__size-small {
width: 64rpx !important;
height: 64rpx !important;
}
.fui-avatar__text-small {
font-size: 32rpx !important;
}
.fui-avatar__size-middle {
width: 96rpx !important;
height: 96rpx !important;
}
.fui-avatar__text-middle {
font-size: 44rpx !important;
}
.fui-avatar__size-large {
width: 128rpx !important;
height: 128rpx !important;
}
.fui-avatar__text-large {
font-size: 56rpx !important;
}
.fui-avatar__circle {
border-radius: 50% !important;
}
.fui-avatar__square {
border-radius: 8rpx !important;
}

View File

@@ -0,0 +1,31 @@
Component({
properties: {
show: {
type: Boolean,
value: false
},
background: {
type: String,
value: ''
},
absolute: {
type: Boolean,
value: false
},
zIndex: {
type: Number,
value: 999
},
closable: {
type: Boolean,
value: false
}
},
methods: {
handleClick() {
if (this.data.closable && this.data.show) {
this.triggerEvent('click')
}
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,3 @@
<view class="fui-backdrop__wrap {{background?'':'fui-backdrop__bg'}} {{show?'fui-backdrop__show':''}}" style="background:{{background}};position:{{absolute?'absolute':'fixed'}};z-index:{{zIndex}}" bindtap="handleClick">
<slot></slot>
</view>

View File

@@ -0,0 +1,21 @@
.fui-backdrop__wrap {
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
opacity: 0;
visibility: hidden;
transition: all 0.3s;
align-items: center;
justify-content: center;
}
.fui-backdrop__bg {
background: var(--fui-bg-color-mask, rgba(0, 0, 0, 0.6)) !important;
}
.fui-backdrop__show {
visibility: visible !important;
opacity: 1 !important;
}

View File

@@ -0,0 +1,119 @@
Component({
properties: {
value: {
type: String,
optionalTypes: [Number],
value: '',
observer(val) {
this.getWidth()
}
},
max:{
type: String,
optionalTypes: [Number],
value: -1
},
//类型primarysuccesswarningdangerpurplewhite
type: {
type: String,
value: 'primary'
},
//背景色如果设置背景则type失效
background: {
type: String,
value: ''
},
//字体颜色
color: {
type: String,
value: '#FFFFFF'
},
//是否显示为圆点
dot: {
type: Boolean,
value: false
},
//margin-top值单位rpx
marginTop: {
type: String,
optionalTypes: [Number],
value: 0
},
//margin-left值单位rpx
marginLeft: {
type: String,
optionalTypes: [Number],
value: 0
},
//margin-right值单位rpx
marginRight: {
type: String,
optionalTypes: [Number],
value: 0
},
//是否绝对定位
absolute: {
type: Boolean,
value: false
},
top: {
type: String,
value: '-8rpx'
},
right: {
type: String,
value: '-18rpx'
},
//缩放比例
scaleRatio: {
type: Number,
value: 1
}
},
data: {
width: 0,
showValue: ''
},
lifetimes: {
ready() {
this.getWidth()
}
},
methods: {
_getTextWidth(text) {
let sum = 0;
for (let i = 0, len = text.length; i < len; i++) {
if (text.charCodeAt(i) >= 0 && text.charCodeAt(i) <= 255)
sum = sum + 1;
else
sum = sum + 2;
}
const sys = wx.getSystemInfoSync()
const px = sys.windowWidth / 750 * (text.length > 1 ? 32 : 24)
var strCode = text.charCodeAt();
let multiplier = 12;
if (strCode >= 65 && strCode <= 90) {
multiplier = 15;
}
return (sum / 2 * multiplier) + px + 'px';
},
getWidth() {
let max = Number(this.data.max)
let val = Number(this.data.value)
let value = ''
if (val === NaN || max === -1) {
value = this.data.value
} else {
value = val > max ? `${max}+` : val
}
let width = this.data.dot ? '8px' : this._getTextWidth(String(value))
this.setData({
showValue:value,
width: width
})
},
handleClick() {
this.triggerEvent('click');
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1 @@
<text class="{{dot?'fui-badge__dot':'fui-badge__wrap'}} {{background?'':('fui-badge__bg-'+type)}} {{absolute?'fui-badge__absolute':''}} {{!background && type==='white'?'fui-badge__text-color':''}}" style="top:{{absolute?top:'auto'}};right:{{absolute?right:'auto'}};zoom:{{scaleRatio}};margin-top:{{marginTop}}rpx;margin-left:{{marginLeft}}rpx;margin-right:{{marginRight}}rpx;width:{{width}};color:{{color}};background:{{background}}" bindtap="handleClick" wx:if="{{showValue || dot}}">{{dot?'':showValue}}</text>

View File

@@ -0,0 +1,55 @@
.fui-badge__wrap {
height: 36rpx;
padding: 0 12rpx;
color: #FFFFFF;
font-size: 24rpx;
line-height: 36rpx;
border-radius: 100px;
min-width: 36rpx !important;
display: flex;
box-sizing: border-box;
flex-direction: row;
align-items: center;
justify-content: center;
z-index: 10;
}
.fui-badge__dot {
height: 8px !important;
width: 8px !important;
display: inline-block;
border-radius: 50%;
z-index: 10;
}
.fui-badge__bg-primary {
background-color: var(--fui-color-primary, #465CFF) !important;
}
.fui-badge__bg-success {
background-color: var(--fui-color-success, #09BE4F) !important;
}
.fui-badge__bg-warning {
background-color: var(--fui-color-warning, #FFB703) !important;
}
.fui-badge__bg-danger {
background-color: var(--fui-color-danger, #FF2B2B) !important;
}
.fui-badge__bg-purple {
background-color: var(--fui-color-purple, #6831FF) !important;
}
.fui-badge__bg-white {
background-color: var(--fui-color-white, #FFFFFF) !important;
}
.fui-badge__text-color {
color: var(--fui-color-danger, #FF2B2B) !important;
}
.fui-badge__absolute {
position: absolute;
}

View File

@@ -0,0 +1,40 @@
Component({
properties: {
show: {
type: Boolean,
value: false
},
//背景颜色
background: {
type: String,
value: '#fff'
},
//圆角
radius: {
type: Number,
optionalTypes: [String],
value: 24
},
zIndex: {
type: Number,
optionalTypes: [String],
value: 1001
},
//点击遮罩 是否可关闭
maskClosable: {
type: Boolean,
value: true
},
maskBackground: {
type: String,
value: 'rgba(0,0,0,.6)'
}
},
methods: {
handleClose(e) {
if (!this.data.maskClosable) return;
this.triggerEvent('close', {});
},
stop(e) {}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,5 @@
<view class="fui-bottom__popup-wrap {{show?'fui-bottom__popwrap-show':''}}" style="z-index:{{zIndex}};background:{{maskBackground}}" catchtap="handleClose" catchtouchmove="stop">
<view class="fui-bottom__popup {{show?'fui-bottom__popup-show':''}}" style="border-top-left-radius:{{radius}}rpx;border-top-right-radius:{{radius}}rpx;background:{{background}}" catchtap="stop">
<slot></slot>
</view>
</view>

View File

@@ -0,0 +1,36 @@
.fui-bottom__popup-wrap {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1001;
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: center;
transition: all ease-in-out .2s;
visibility: hidden;
opacity: 0;
overflow: hidden;
}
.fui-bottom__popwrap-show {
opacity: 1;
visibility: visible;
}
.fui-bottom__popup {
width: 100%;
transform: translate3d(0, 100%, 0);
transition: all 0.3s ease-in-out;
min-height: 20rpx;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
flex: 1;
overflow: hidden;
}
.fui-bottom__popup-show {
transform: translate3d(0, 0, 0);
}

View File

@@ -0,0 +1,224 @@
Component({
behaviors: ['wx://form-field-button'],
properties: {
//样式类型primarysuccess warningdangerlinkpurplegray
type: {
type: String,
value: 'primary'
},
//按钮背景色当传入值时type失效
background: {
type: String,
value: ''
},
//按钮显示文本
text: {
type: String,
value: ''
},
//按钮字体颜色
color: {
type: String,
value: '#fff'
},
//按钮禁用背景色
disabledBackground: {
type: String,
value: ''
},
//按钮禁用字体颜色
disabledColor: {
type: String,
value: ''
},
borderWidth: {
type: String,
value: '1px'
},
borderColor: {
type: String,
value: ''
},
//V1.9.8+ 按钮大小优先级高于width和heightmedium、small、mini
btnSize: {
type: String,
value: ''
},
//宽度
width: {
type: String,
value: '100%'
},
//高度
height: {
type: String,
value: ''
},
//字体大小单位rpx
size: {
type: Number,
optionalTypes: [String],
value: 0
},
bold: {
type: Boolean,
value: false
},
margin: {
type: String,
value: '0'
},
//圆角
radius: {
type: String,
value: ''
},
plain: {
type: Boolean,
value: false
},
disabled: {
type: Boolean,
value: false
},
loading: {
type: Boolean,
value: false
},
formType: {
type: String,
value: ''
},
openType: {
type: String,
value: ''
},
appParameter: {
type: String,
value: ''
},
//v2.3.0+
hoverStopPropagation: {
type: Boolean,
value: false
},
lang: {
type: String,
value: 'en'
},
sessionFrom: {
type: String,
value: ''
},
sendMessageTitle: {
type: String,
value: ''
},
sendMessagePath: {
type: String,
value: ''
},
sendMessageImg: {
type: String,
value: ''
},
showMessageCard: {
type: Boolean,
value: false
},
phoneNumberNoQuotaToast: {
type: Boolean,
value: true
},
index: {
type: Number,
optionalTypes: [String],
value: 0
}
},
data: {
time: 0,
trigger: false,
tap: false,
c_height: (wx.$fui && wx.$fui.fuiButton.height) || '96rpx',
c_size: (wx.$fui && wx.$fui.fuiButton.size) || 32,
c_radius: (wx.$fui && wx.$fui.fuiButton.radius) || '16rpx'
},
methods: {
handleStart() {
if (this.data.disabled) return;
this.data.trigger = false;
this.data.tap = true;
if (new Date().getTime() - this.data.time <= 150) return;
this.data.trigger = true;
this.setData({
time: new Date().getTime()
})
},
handleClick() {
if (this.data.disabled || !this.data.trigger) return;
this.setData({
time: 0
})
},
handleEnd() {
if (this.data.disabled) return;
setTimeout(() => {
this.setData({
time: 0
})
}, 150);
},
handleTap() {
if (this.disabled) return;
this.triggerEvent('click', {
index: Number(this.data.index)
});
},
bindgetuserinfo({
detail = {}
} = {}) {
this.triggerEvent('getuserinfo', detail);
},
bindcontact({
detail = {}
} = {}) {
this.triggerEvent('contact', detail);
},
bindgetphonenumber({
detail = {}
} = {}) {
this.triggerEvent('getphonenumber', detail);
},
binderror({
detail = {}
} = {}) {
this.triggerEvent('error', detail);
},
bindopensetting({
detail = {}
} = {}) {
this.triggerEvent('opensetting', detail);
},
bindchooseavatar({
detail = {}
} = {}) {
this.triggerEvent('chooseavatar', detail);
},
bindlaunchapp({
detail = {}
} = {}) {
this.triggerEvent('launchapp', detail);
},
agreeprivacyauthorization({
detail = {}
} = {}) {
this.triggerEvent('agreeprivacyauthorization', detail);
},
bindgetrealtimephonenumber({
detail = {}
} = {}) {
this.triggerEvent('getrealtimephonenumber', detail);
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,64 @@
<view class="fui-button__wrap {{(!width || width==='100%' || width===true) && !btnSize?'fui-button__flex-1':''}}" style="width: {{parse.getWidth(width,btnSize)}};height: {{parse.getHeight(height,btnSize,c_height)}};margin:{{margin}};border-radius:{{radius}}" bindtouchstart="handleStart" bindtouchend="handleClick" bindtouchcancel="handleEnd">
<button
class="fui-button {{bold ? 'fui-text__bold' : ''}} {{ time && (plain || type==='link') ? 'fui-button__opacity' : ''}} {{disabled && !disabledBackground ? 'fui-button__opacity' : ''}} {{!background && !disabledBackground && !plain?('fui-button__'+type):''}} {{(!width || width==='100%' || width===true) && !btnSize?'fui-button__flex-1':''}} {{time && !plain && type!=='link' ? 'fui-button__active' : ''}}"
style="width: {{parse.getWidth(width,btnSize)}};height: {{parse.getHeight(height,btnSize,c_height)}};line-height: {{parse.getHeight(height,btnSize,c_height)}};background: {{disabled && disabledBackground ? disabledBackground : (plain ? 'transparent' : background)}};border-radius: {{radius}};font-size: {{parse.getSize(size,btnSize,c_size)}}rpx;color: {{disabled && disabledBackground ? disabledColor : color}}"
loading="{{loading}}" form-type="{{formType}}" app-parameter="{{appParameter}}" open-type="{{openType}}" hoverStopPropagation="{{hoverStopPropagation}}" lang="{{lang}}" sessionFrom="{{sessionFrom}}" sendMessageTitle="{{sendMessageTitle}}" sendMessagePath="{{sendMessagePath}}" sendMessageImg="{{sendMessageImg}}" showMessageCard="{{showMessageCard}}" phoneNumberNoQuotaToast="{{phoneNumberNoQuotaToast}}" bindgetuserinfo="bindgetuserinfo"
bindgetphonenumber="bindgetphonenumber" bindcontact="bindcontact" binderror="binderror"
bindopensetting="bindopensetting" bindchooseavatar="bindchooseavatar" bindlaunchapp="bindlaunchapp" bindagreeprivacyauthorization="agreeprivacyauthorization" bindgetrealtimephonenumber="bindgetrealtimephonenumber" disabled="{{disabled}}" catchtap="handleTap">
<text
class="fui-button__text {{!background && !disabledBackground && !plain && type==='gray' && color==='#fff'?'fui-btn__gray-color':''}} {{bold?'fui-text__bold':''}}"
wx:if="{{text}}"
style="font-size: {{parse.getSize(size,btnSize,c_size)}}rpx;line-height:{{parse.getSize(size,btnSize,c_size)}}rpx;color:{{disabled && disabledBackground ? disabledColor : color}}">{{text}}</text>
<slot></slot>
</button>
<view wx:if="{{borderColor}}" class="fui-button__thin-border {{time && (plain || type==='link') && !disabled ? 'fui-button__opacity' : ''}} {{disabled && !disabledBackground ? 'fui-button__opacity' : ''}}"
style="border-width:{{borderWidth}};border-color:{{borderColor ? borderColor : disabled && disabledBackground ? disabledBackground : (background || 'transparent')}};border-radius:{{parse.getBorderRadius(radius,c_radius)}}">
</view>
</view>
<wxs module="parse">
module.exports={
getBorderRadius:function(radius,c_radius) {
var radius = radius || c_radius || '0'
if (~radius.indexOf('rpx')) {
radius = (parseInt(radius.replace('rpx', '')) * 2) + 'rpx'
} else if (~radius.indexOf('px')) {
radius = (parseInt(radius.replace('px', '')) * 2) + 'px'
} else if (~radius.indexOf('%')) {
radius = (parseInt(radius.replace('%', '')) * 2) + '%'
}
return radius
},
getWidth:function(width,btnSize){
var w = width;
if (btnSize && btnSize !== true) {
w = {
'medium': '400rpx',
'small': '200rpx',
'mini': '120rpx'
} [btnSize] || w
}
return w
},
getHeight:function(height,btnSize,c_height) {
var h = height || c_height
if (btnSize && btnSize !== true) {
h = {
'medium': '84rpx',
'small': '72rpx',
'mini': '64rpx'
} [btnSize] || height
}
return h
},
getSize:function(size,btnSize,c_size) {
var fontSize = size || c_size;
if (btnSize === 'small') {
fontSize = fontSize > 28 ? 28 : fontSize;
} else if (btnSize === 'mini') {
fontSize = fontSize > 28 ? 24 : fontSize;
}
return fontSize;
}
}
</wxs>

View File

@@ -0,0 +1,116 @@
.fui-button__wrap {
position: relative;
}
.fui-button__thin-border {
position: absolute;
width: 200%;
height: 200%;
transform-origin: 0 0;
transform: scale(0.5, 0.5) translateZ(0);
box-sizing: border-box;
left: 0;
top: 0;
border-radius: 32rpx;
border-style: solid;
pointer-events: none;
}
.fui-button {
border-width: 0;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
border-style: solid;
position: relative;
padding-left: 0;
padding-right: 0;
overflow: hidden;
transform: translateZ(0);
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
.fui-button__flex-1 {
flex: 1;
width: 100%;
}
.fui-button::after {
border: 0;
}
.fui-button__active {
overflow: hidden !important;
}
.fui-button__active::after {
content: ' ';
background-color: var(--fui-bg-color-hover, rgba(0, 0, 0, 0.2));
position: absolute;
width: 100%;
height: 100%;
left: 0;
right: 0;
top: 0;
transform: none;
z-index: 1;
border-radius: 0;
}
.fui-button__text {
text-align: center;
flex-direction: row;
align-items: center;
justify-content: center !important;
padding-left: 0 !important;
}
.fui-button__opacity {
opacity: 0.5;
}
.fui-text__bold {
font-weight: bold;
}
.fui-button__link {
border-color: transparent !important;
background: transparent !important;
}
.fui-button__primary {
border-color: var(--fui-color-primary, #465CFF) !important;
background: var(--fui-color-primary, #465CFF) !important;
}
.fui-button__success {
border-color: var(--fui-color-success, #09BE4F) !important;
background: var(--fui-color-success, #09BE4F) !important;
}
.fui-button__warning {
border-color: var(--fui-color-warning, #FFB703) !important;
background: var(--fui-color-warning, #FFB703) !important;
}
.fui-button__danger {
border-color: var(--fui-color-danger, #FF2B2B) !important;
background: var(--fui-color-danger, #FF2B2B) !important;
}
.fui-button__purple {
border-color: var(--fui-color-purple, #6831FF) !important;
background: var(--fui-color-purple, #6831FF) !important;
}
.fui-button__gray {
border-color: var(--fui-bg-color-content, #F8F8F8) !important;
background: var(--fui-bg-color-content, #F8F8F8) !important;
color: var(--fui-color-primary, #465CFF) !important;
}
.fui-btn__gray-color {
color: var(--fui-color-primary, #465CFF) !important;
}

View File

@@ -0,0 +1,116 @@
Component({
options: {
multipleSlots: true,
styleIsolation: 'apply-shared'
},
properties: {
//card margin值
margin: {
type: String,
value: '0 32rpx'
},
//是否通栏为true时margin-leftmargin-right失效
full: {
type: Boolean,
value: false
},
//card 背景色
background: {
type: String,
value: '#fff'
},
//card 圆角showBorder为false时生效
radius: {
type: String,
value: '16rpx'
},
//阴影
shadow: {
type: String,
value: '0 2rpx 4rpx 0 rgba(2, 4, 38, 0.05)'
},
//是否显示card 边框为true时box-shadow失效
showBorder: {
type: Boolean,
value: false
},
headerBackground: {
type: String,
value: '#fff'
},
//是否需要header底部线条
headerLine: {
type: Boolean,
value: true
},
//是否需要内容与footer之间线条
footerLine: {
type: Boolean,
value: false
},
//header padding值
padding: {
type: String,
value: '20rpx 20rpx'
},
//头像,图标图片地址
src: {
type: String,
value: ''
},
//图片宽度单位rpx
width: {
type: Number,
optionalTypes: [String],
value: 64
},
//图片高度单位rpx
height: {
type: Number,
optionalTypes: [String],
value: 64
},
//图片圆角
imageRadius: {
type: String,
value: '8rpx'
},
title: {
type: String,
value: ''
},
size: {
type: Number,
optionalTypes: [String],
value: 30
},
color: {
type: String,
value: '#7F7F7F'
},
tag: {
type: String,
value: ''
},
tagSize: {
type: Number,
optionalTypes: [String],
value: 24
},
tagColor: {
type: String,
value: '#b2b2b2'
},
index: {
type: Number,
value: 0
}
},
methods: {
handleClick() {
this.triggerEvent('click', {
index: this.data.index
})
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,17 @@
<view class="fui-card__wrap {{full?'fui-card__full':''}} {{showBorder?'fui-card__border':''}} {{showBorder && !isNvue && !full?'fui-card__border-radius':''}}" style="margin:{{margin}};background:{{background}};border-radius:{{radius}};box-shadow:{{shadow}}" bindtap="handleClick">
<view class="fui-card__header {{headerLine?'fui-card__header-line':''}}" wx:if="{{tag || title || src}}" style="border-top-left-radius:{{full?0:radius}};border-top-right-radius:{{full?0:radius}};padding:{{padding}}">
<view class="fui-card__header-left">
<image src="{{src}}" class="fui-card__header-thumb" mode="widthFix" wx:if="{{src}}" style="height:{{height}}rpx;width:{{width}}rpx;border-radius:{{imageRadius}}"></image>
<text class="fui-card__header-title" style="font-size:{{size}}rpx;color:{{color}}" wx:if="{{title}}">{{title}}</text>
</view>
<view class="fui-card__header-right" wx:if="{{tag}}">
<text style="font-size:{{tagSize}}rpx;color:{{tagColor}}">{{tag}}</text>
</view>
</view>
<view class="fui-card__body {{footerLine?'fui-card__header-line':''}}">
<slot></slot>
</view>
<view class="fui-card__footer" style="border-bottom-left-radius:{{full?0:radius}};border-bottom-right-radius:{{full?0:radius}}">
<slot name="footer"></slot>
</view>
</view>

View File

@@ -0,0 +1,99 @@
.fui-card__wrap {
overflow: hidden;
flex: 1;
box-sizing: border-box;
}
.fui-card__full {
margin-left: 0 !important;
margin-right: 0 !important;
border-radius: 0 !important;
}
.fui-card__full::after {
border-radius: 0 !important;
}
.fui-card__border {
position: relative;
box-shadow: none !important;
}
.fui-card__border-radius {
border-radius: 16rpx !important;
}
.fui-card__border::after {
content: ' ';
position: absolute;
height: 200%;
width: 200%;
border: 1px solid var(--fui-color-border, #EEEEEE);
transform-origin: 0 0;
-webkit-transform-origin: 0 0;
-webkit-transform: scale(0.5);
transform: scale(0.5);
left: 0;
top: 0;
border-radius: 32rpx;
box-sizing: border-box;
pointer-events: none;
}
.fui-card__header {
width: 100%;
display: flex;
box-sizing: border-box;
align-items: center;
justify-content: space-between;
position: relative;
overflow: hidden;
}
.fui-card__header-left {
white-space: nowrap;
}
.fui-card__header-line {
position: relative;
}
.fui-card__header-line::after {
content: '';
position: absolute;
border-bottom: 1px solid var(--fui-color-border, #EEEEEE);
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transform-origin: 0 100%;
bottom: 0;
right: 0;
left: 0;
pointer-events: none;
}
.fui-card__header-thumb {
vertical-align: middle;
margin-right: 20rpx;
}
.fui-card__header-title {
display: inline-block;
vertical-align: middle;
max-width: 380rpx;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.fui-card__header-right {
text-align: right;
flex-shrink: 0;
}
.fui-card__body {
box-sizing: border-box;
}
.fui-card__footer {
box-sizing: border-box;
}

View File

@@ -0,0 +1,70 @@
Component({
behaviors: ['wx://form-field-group'],
properties: {
name: {
type: String,
value: ''
},
value: {
type: String,
value: "[]",
observer(vals) {
this.modelChange(vals)
}
}
},
relations: {
'../fui-checkbox/fui-checkbox': {
type: 'descendant',
linked: function (target) {
this.data.childrens.push(target)
let vals = JSON.parse(this.data.value || '[]')
if (vals.length > 0) {
target.setData({
val: vals.includes(target.data.value)
})
}
}
}
},
data: {
vals: '[]',
childrens: []
},
methods: {
checkboxChange(e) {
this.setData({
value: JSON.stringify(e.value)
})
this.triggerEvent('change', e)
},
changeValue(checked, target) {
let vals = []
this.data.childrens.forEach(item => {
if (item.data.val) {
vals.push(item.data.value);
}
})
this.setData({
vals: vals
})
let e = {
value: vals
}
this.checkboxChange(e)
},
modelChange(vals) {
this.data.childrens.forEach(item => {
if (vals.includes(item.data.value)) {
item.setData({
val: true
})
} else {
item.setData({
val: false
})
}
})
}
}
})

View File

@@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"fui-form-field": "/components/firstui/fui-form-field/fui-form-field"
}
}

View File

@@ -0,0 +1,3 @@
<fui-form-field name="{{name}}" value="{{vals}}">
<slot></slot>
</fui-form-field>

View File

@@ -0,0 +1,96 @@
Component({
options: {
virtualHost: true
},
properties: {
value: {
type: String,
value: ''
},
checked: {
type: Boolean,
value: false,
observer(newVal) {
this.setData({
val: newVal
},()=>{
this.parentChangeValue()
})
}
},
disabled: {
type: Boolean,
value: false
},
//checkbox选中背景颜色
color: {
type: String,
value: ''
},
//checkbox未选中时边框颜色
borderColor: {
type: String,
value: '#ccc'
},
borderRadius: {
type: String,
value: '50%'
},
//是否只展示对号,无边框背景
isCheckMark: {
type: Boolean,
value: false
},
//对号颜色
checkMarkColor: {
type: String,
value: '#fff'
},
scaleRatio: {
type: String,
optionalTypes: [Number],
value: 1
}
},
relations: {
'../fui-checkbox-group/fui-checkbox-group': {
type: 'ancestor'
},
'../fui-label/fui-label': {
type: 'ancestor'
}
},
data: {
val: false
},
lifetimes: {
attached: function () {
this.setData({
val: this.data.checked
},()=>{
this.parentChangeValue()
})
}
},
methods: {
checkboxChange(e) {
if (this.data.disabled) return;
this.setData({
val: !this.data.val
}, () => {
this.parentChangeValue()
})
},
parentChangeValue() {
const group = this.getRelationNodes('../fui-checkbox-group/fui-checkbox-group')[0]
group && group.changeValue(this.data.val, this);
this.triggerEvent('change', {
checked: this.data.val,
value: this.data.value
})
},
labelClick() {
this.checkboxChange()
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,24 @@
<view class="fui-checkbox__input {{disabled?'fui-checkbox__disabled':''}} {{!color && val && !isCheckMark?'fui-checkbox__color':''}}" style="background:{{parse.getBackgroundColor(val,isCheckMark,color)}};border-color:{{parse.getBorderColor(val,isCheckMark, color, borderColor)}};zoom:{{scaleRatio}};border-radius:{{borderRadius}}" catchtap="checkboxChange">
<view class="fui-check__mark" style="border-bottom-color:{{checkMarkColor}};border-right-color:{{checkMarkColor}}" wx:if="{{val}}"></view>
<checkbox class="fui-checkbox__hidden" color="{{color}}" disabled="{{disabled}}" value="{{value}}" checked="{{val}}">
</checkbox>
</view>
<wxs module="parse">
module.exports = {
getBackgroundColor: function (val, isCheckMark, color) {
var _color = val ? color : '#fff'
if (isCheckMark) {
_color = 'transparent'
}
return _color;
},
getBorderColor: function (val, isCheckMark, color, borderColor) {
var _color = val ? color : borderColor;
if (isCheckMark) {
_color = 'transparent'
}
return _color;
}
}
</wxs>

View File

@@ -0,0 +1,56 @@
.fui-checkbox__input {
font-size: 0;
color: rgba(0, 0, 0, 0);
width: 40rpx;
height: 40rpx;
border-width: 1px;
border-style: solid;
display: inline-flex;
box-sizing: border-box;
border-radius: 50%;
vertical-align: top;
flex-shrink: 0;
flex-direction: row;
align-items: center;
justify-content: center;
overflow: hidden;
position: relative;
}
.fui-checkbox__color {
background: var(--fui-color-primary, #465CFF) !important;
border-color: var(--fui-color-primary, #465CFF) !important;
}
.fui-check__mark {
width: 20rpx;
height: 40rpx;
border-bottom-style: solid;
border-bottom-width: 3px;
border-bottom-color: #FFFFFF;
border-right-style: solid;
border-right-width: 3px;
border-right-color: #FFFFFF;
box-sizing: border-box;
transform: rotate(45deg) scale(0.5) translateZ(0);
transform-origin: 54% 48%;
}
.fui-checkbox__hidden {
display: inline-block;
width: 100%;
height: 100%;
border: 0 none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
position: absolute;
top: 0;
left: 0;
opacity: 0;
z-index: 2;
}
.fui-checkbox__disabled {
opacity: 0.6;
}

View File

@@ -0,0 +1,131 @@
Component({
options: {
virtualHost: true
},
properties: {
//栅格占据的列数
span: {
type: Number,
value: 24,
observer(val) {
this.updateCol();
}
},
//栅格左侧的间隔格数
offset: {
type: Number,
value: 0,
observer(val) {
this.updateCol();
}
},
//栅格向右移动格数
pushLeft: {
type: Number,
value: -1,
observer(val) {
this.updateCol();
}
},
//栅格向左移动格数
pullRight: {
type: Number,
value: -1,
observer(val) {
this.updateCol();
}
},
//max-width:767px 响应式栅格数或者栅格属性对象
//Number时表示在此屏幕宽度下栅格占据的列数。Object时可配置多个描述{span: 4, offset: 4}
xs: {
type: Number,
optionalTypes: [Object],
value: -1
},
//max-width:768px 响应式栅格数或者栅格属性对象
sm: {
type: Number,
optionalTypes: [Object],
value: -1
},
//max-width:992px 响应式栅格数或者栅格属性对象
md: {
type: Number,
optionalTypes: [Object],
value: -1
},
//max-width:1200px 响应式栅格数或者栅格属性对象
lg: {
type: Number,
optionalTypes: [Object],
value: -1
},
//max-width:1920px 响应式栅格数或者栅格属性对象
xl: {
type: Number,
optionalTypes: [Object],
value: -1
}
},
relations: {
'../fui-row/fui-row': {
type: 'ancestor',
linked: function (target) {
this.updateGutter(target.data.gutter)
}
}
},
data: {
classList: 'fui-col',
gutter: 0,
right: 0,
left: 0
},
lifetimes: {
attached: function () {
this.updateCol();
}
},
methods: {
updateGutter(parentGutter) {
parentGutter = Number(parentGutter);
if (!isNaN(parentGutter)) {
this.setData({
gutter: parentGutter / 2
})
}
},
updateCol() {
let classList = ['fui-col'];
classList.push('fui-col-' + this.data.span);
classList.push('fui-col-offset-' + this.data.offset);
if (this.data.pushLeft !== -1) {
this.data.pushLeft && classList.push('fui-col-push-' + this.data.pushLeft);
}
if (this.data.pullRight !== -1) {
this.data.pullRight && classList.push('fui-col-pull-' + this.data.pullRight);
}
this.screenSizeSet('xs', classList);
this.screenSizeSet('sm', classList);
this.screenSizeSet('md', classList);
this.screenSizeSet('lg', classList);
this.screenSizeSet('xl', classList);
this.setData({
classList: classList
})
},
screenSizeSet(screen, classList) {
if (typeof this.data[screen] === 'number' && this.data[screen] !== -1) {
classList.push('fui-col-' + screen + '-' + this.data[screen]);
} else if (typeof this.data[screen] === 'object') {
typeof this.data[screen].offset === 'number' && classList.push('fui-col-' + screen + '-offset-' + this.data[
screen].offset);
typeof this.data[screen].pushLeft === 'number' && classList.push('fui-col-' + screen + '-push-' + this.data[
screen].pushLeft);
typeof this.data[screen].pullRight === 'number' && classList.push('fui-col-' + screen + '-pull-' + this.data[
screen].pullRight);
typeof this.data[screen].span === 'number' && classList.push('fui-col-' + screen + '-' + this.data[screen].span);
}
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,3 @@
<view class="{{classList}}" style="padding-left:{{gutter}}rpx; padding-right:{{gutter}}rpx">
<slot></slot>
</view>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
const elId = `fui_${Math.ceil(Math.random() * 10e5).toString(36)}`
Component({
options: {
multipleSlots: true,
virtualHost: true
},
properties: {
//item项索引或者唯一标识
index: {
type: String,
optionalTypes: [Number],
value: 0
},
// 是否禁用
disabled: {
type: Boolean,
value: false
},
background: {
type: String,
value: '#fff'
},
//是否显示动画,如果动画卡顿严重建议不开启
animation: {
type: Boolean,
value: true
},
// 是否展开
open: {
type: Boolean,
value: false,
observer(val) {
this.setData({
isOpen: val
})
}
},
isBorder: {
type: Boolean,
value: true
},
borderColor: {
type: String,
value: ''
},
borderLeft: {
type: String,
optionalTypes: [Number],
value: 0
},
arrow: {
type: Boolean,
value: true
},
arrowColor: {
type: String,
value: '#B2B2B2'
},
arrowRight: {
type: String,
optionalTypes: [Number],
value: 24
},
contentBg: {
type: String,
value: '#fff'
},
marginTop: {
type: String,
optionalTypes: [Number],
value: 0
},
marginBottom: {
type: String,
optionalTypes: [Number],
value: 0
}
},
relations: {
'../fui-collapse/fui-collapse': {
type: 'ancestor',
linked: function (target) {
this.data.collapse = target
}
}
},
data: {
isOpen: false,
isHeight: null,
height: 0,
elId: elId,
oldHeight: 0,
collapse: null
},
lifetimes: {
ready: function () {
this.setData({
isOpen: this.data.open
})
this.init()
},
detached: function () {
this.uninstall()
}
},
methods: {
init() {
this.getCollapseHeight()
},
uninstall() {
if (this.data.collapse && this.data.collapse.children) {
this.data.collapse.children.forEach((item, index) => {
if (item === this) {
this.data.collapse.children.splice(index, 1)
}
})
}
},
onClick(e) {
if (this.data.disabled) return
let isOpen = !this.data.isOpen
this.setData({
isOpen: isOpen
})
if (this.data.collapse) {
this.data.collapse.collapseChange(this, isOpen, this.data.index)
} else {
this.triggerEvent('change', {
index: this.data.index,
isOpen: isOpen
})
}
},
getCollapseHeight(index = 0) {
wx.createSelectorQuery()
.in(this)
.select(`#${this.data.elId}`)
.fields({
size: true
}, data => {
if (index >= 10) return
if (!data) {
index++
this.getCollapseHeight(index)
return
}
this.setData({
height: data.height,
isHeight: true
})
})
.exec()
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,16 @@
<view class="fui-collapse__item" style="margin-top: {{marginTop}}rpx;margin-bottom: {{marginBottom}}rpx;">
<view catchtap="onClick" class="fui-collapse-item__title {{disabled?'fui-collapse__disabled':''}}" style="background:{{background}}">
<view class="fui-collapse__title">
<slot></slot>
</view>
<view wx:if="{{arrow}}" class="fui-collapse__arrow {{isOpen?'fui-collapse__arrow-active':''}} {{animation?'fui-collapse__item-ani':''}}" style="margin-right:{{arrowRight}}rpx">
<view class="fui-collapse__arrow-inner" style="border-color:{{arrowColor}}"></view>
</view>
<view wx:if="{{isBorder}}" style="background:{{borderColor}};left:{{borderLeft}}rpx" class="fui-collapse__border {{!borderColor?'fui-collapse__border-color':''}}"></view>
</view>
<view class="fui-collapse__content-wrap {{animation?'fui-collapse-__content-ani':''}}" style="height:{{isOpen?height:0}}px;background:{{contentBg}}">
<view id="{{elId}}" class="fui-collapse__content {{isHeight?'fui-collapse__content-open':''}}">
<slot name="content"></slot>
</view>
</view>
</view>

View File

@@ -0,0 +1,91 @@
.fui-collapse__item {
box-sizing: border-box;
}
.fui-collapse-item__title {
display: flex;
width: 100%;
box-sizing: border-box;
flex-direction: row;
align-items: center;
transition: border-bottom-color 0.3s;
position: relative;
}
.fui-collapse__border {
position: absolute;
bottom: 0;
right: 0;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
transform-origin: 0 100%;
z-index: 1;
}
.fui-collapse__border-color {
background: var(--fui-color-border, #EEEEEE) !important;
}
.fui-collapse__disabled {
opacity: .5;
}
.fui-collapse__title {
width: 100%;
flex: 1;
}
.fui-collapse__arrow-inner {
height: 40rpx;
width: 40rpx;
border-width: 0 3px 3px 0;
border-style: solid;
transform: rotate(45deg) scale(.5);
box-sizing: border-box;
position: absolute;
top: -6rpx;
left: 0;
}
.fui-collapse__arrow {
width: 40rpx;
height: 40rpx;
transform: rotate(0deg);
position: relative;
flex-shrink: 0;
}
.fui-collapse__arrow-active {
transform: rotate(180deg);
}
.fui-collapse__item-ani {
transition-property: transform;
transition-duration: 0.3s;
transition-timing-function: ease;
}
.fui-collapse__content-wrap {
will-change: height;
box-sizing: border-box;
overflow: hidden;
position: relative;
height: 0;
}
.fui-collapse-__content-ani {
transition-property: height;
transition-duration: 0.3s;
will-change: height;
}
.fui-collapse__content {
width: 100%;
position: absolute;
}
.fui-collapse__content-open {
position: relative;
}

View File

@@ -0,0 +1,46 @@
Component({
properties: {
// 是否开启手风琴效果
accordion: {
type: Boolean,
value: false
},
background: {
type: String,
value: 'transparent'
}
},
data: {
children: []
},
relations: {
'../fui-collapse-item/fui-collapse-item': {
type: 'descendant',
linked: function (target) {
this.data.children.push(target)
},
linkChanged: function (target) {
setTimeout(() => {
target && target.init()
}, 50)
}
}
},
methods: {
collapseChange(obj, isOpen, idx) {
if (this.data.accordion && isOpen) {
this.data.children.forEach((item, index) => {
if (item !== obj) {
item.setData({
isOpen: false
})
}
})
}
this.triggerEvent('change', {
index: idx,
isOpen: isOpen
})
}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,3 @@
<view class="fui-collapse__wrap" style="background: {{background}};">
<slot></slot>
</view>

View File

@@ -0,0 +1,5 @@
.fui-collapse__wrap {
width: 100%;
display: flex;
flex-direction: column;
}

View File

@@ -0,0 +1,125 @@
/*
===========================================
当前组件库版本号为V2.1.0
如果与文档最新版本不一致,请更新至最新版本使用!
===========================================
*/
/*
组件属性全局配置文件。优先级全局配置文件props < 直接设置组件props
目前支持配置的组件fui-button、fui-icon、fui-text、fui-input、fui-list-cell、fui-section、fui-white-space、fui-wing-blank
*/
// 主色仅适用无法使用css变量控制颜色的组件使用【保持与fui-theme中一致】
const color = {
primary: '#465CFF',
success: '#09BE4F',
warning: '#FFB703',
danger: '#FF2B2B',
purple: '#6831FF',
link: '#465CFF'
}
//全局方法V1.9.8+
const app = {
// 设计稿 375 的宽度
rpx2px(value) {
let sys = wx.getSystemInfoSync()
return sys.windowWidth / 750 * value
},
toast: function (text, icon = 'none') {
text && wx.showToast({
title: text,
icon: icon,
duration: 2000
})
},
modal: function (title, content, callback, showCancel, confirmColor, confirmText) {
wx.showModal({
title: title,
content: content,
showCancel: showCancel || false,
cancelColor: "#7F7F7F",
confirmColor: confirmColor || color.primary,
confirmText: confirmText || "确定",
success(res) {
if (res.confirm) {
callback && callback(true)
} else {
callback && callback(false)
}
},
fail(err) {
console.log(err)
}
})
},
href(url, isMain) {
if (isMain) {
wx.switchTab({
url: url
})
} else {
wx.navigateTo({
url: url
});
}
}
}
const fuiConfig = {
//组件名称,小驼峰命名
//如fui-button写成fuiButton
fuiButton: {
//组件属性值
height: '96rpx',
size: 32,
radius: '16rpx'
},
fuiIcon: {
size: 64,
unit: 'rpx'
},
fuiText: {
size: 32,
unit: 'rpx'
},
fuiInput: {
labelSize: 32,
labelColor: '#333',
size: 32,
color: '#333'
},
fuiListCell: {
padding: '32rpx',
arrowColor: '#B2B2B2',
bottomLeft: 32
},
// V1.9.9+
fuiSection: {
size: 32,
color: '#181818',
fontWeight: 600,
descrSize: 28,
descrColor: '#B2B2B2',
descrTop: 12
},
//v2.1.0+
fuiWhiteSpace: {
size: 'default',
//设置了height则size失效
height: 0,
background: 'transparent'
},
//v2.1.0+
fuiWingBlank: {
size: 'default',
//设置了gap则size失效
gap: 0,
background: 'transparent'
},
color,
...app
}
export default fuiConfig

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
Component({
properties: {
show: {
type: Boolean,
value: false
},
title: {
type: String,
value: '温馨提示'
},
color: {
type: String,
value: '#333'
},
content: {
type: String,
value: ''
},
contentColor: {
type: String,
value: '#7F7F7F'
},
buttons: {
type: Array,
value: [{
text: '取消'
}, {
text: '确定',
color: '#465CFF'
}]
},
background: {
type: String,
value: '#fff'
},
radius: {
type: Number,
optionalTypes: [String],
value: 24
},
maskBackground: {
type: String,
value: 'rgba(0,0,0,.6)'
},
maskClosable: {
type: Boolean,
value: true
}
},
methods: {
handleClick(e) {
let index = Number(e.currentTarget.dataset.index)
this.triggerEvent('click', {
index,
...this.data.buttons[index]
});
},
maskClose() {
if (!this.data.maskClosable) return;
this.triggerEvent('close', {});
},
stop() {}
}
})

View File

@@ -0,0 +1,3 @@
{
"component": true
}

View File

@@ -0,0 +1,12 @@
<view bindtap="maskClose" style="background:{{maskBackground}}" class="fui-dialog__wrap {{show?'fui-wrap__show':''}}" catchtouchmove="stop">
<view class="fui-dialog__inner" style="background:{{background}};border-radius:{{radius}}rpx">
<text class="fui-dialog__title" style="color:{{color}}" wx:if="{{title}}">{{title}}</text>
<view class="fui-dialog__body {{title?'':'fui-dialog__mtop'}}">
<text class="fui-dialog__descr" style="color:{{contentColor}}" wx:if="{{content}}">{{content}}</text>
<slot></slot>
</view>
<view class="fui-dialog__footer">
<text wx:for="{{buttons}}" wx:key="index" style="color:{{item.color || '#333333'}}" class="fui-dialog__btn {{index===0?'fui-dialog__btn-first':''}}" bindtap="handleClick" data-index="{{index}}">{{item.text}}</text>
</view>
</view>
</view>

View File

@@ -0,0 +1,108 @@
.fui-dialog__wrap {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
transition-property: all;
transition-timing-function: ease-in;
transition-duration: 0.2s;
display: flex;
transform: scale3d(1, 1, 0);
visibility: hidden;
align-items: center;
justify-content: center;
opacity: 0;
}
.fui-dialog__inner {
width: 680rpx;
text-align: center;
overflow: hidden;
display: flex;
max-height: 90%;
flex-direction: column;
}
.fui-dialog__title {
padding: 64rpx 48rpx 0;
font-weight: 700;
font-size: 34rpx;
text-align: center;
}
.fui-dialog__body {
padding: 32rpx 48rpx;
margin-bottom: 32rpx;
word-break: break-all;
-webkit-hyphens: auto;
hyphens: auto;
}
.fui-dialog__descr {
font-size: 30rpx;
font-weight: normal;
text-align: center;
}
.fui-dialog__mtop {
margin-top: 32rpx;
}
.fui-dialog__footer {
display: flex;
flex-direction: row;
position: relative;
line-height: 112rpx;
height: 112rpx;
}
.fui-dialog__footer:after {
content: " ";
position: absolute;
left: 0;
top: 0;
right: 0;
height: 1px;
border-top: 1px solid var(--fui-color-border, #EEEEEE);
transform-origin: 0 0;
transform: scaleY(.5)
}
.fui-dialog__btn {
display: block;
text-decoration: none;
flex: 1;
height: 112rpx;
line-height: 112rpx;
font-weight: 700;
position: relative;
font-size: 34rpx;
text-align: center;
}
.fui-dialog__btn:active {
background-color: var(--fui-bg-color-hover, rgba(0, 0, 0, 0.2));
}
.fui-dialog__btn::after {
content: " ";
position: absolute;
left: 0;
top: 0;
width: 1px;
bottom: 0;
border-left: 1px solid var(--fui-color-border, #EEEEEE);
transform-origin: 0 0;
transform: scaleX(.5)
}
.fui-dialog__btn-first::after {
width: 0;
border-left: 0 !important;
}
.fui-wrap__show {
opacity: 1;
transform: scale3d(1, 1, 1);
visibility: visible;
}

View File

@@ -0,0 +1,40 @@
Component({
properties: {
text: {
type: String,
value: ''
},
//divider占据高度单位rpx
height: {
type: Number,
optionalTypes: [String],
value: 100
},
//divider宽度
width: {
type: String,
value: '400rpx'
},
//divider颜色
dividerColor: {
type: String,
value: '#CCCCCC'
},
//文字颜色
color: {
type: String,
value: '#B2B2B2'
},
//文字大小 rpx
size: {
type: Number,
optionalTypes: [String],
value: 24
},
fontWeight: {
type: Number,
optionalTypes: [String],
value: 400
}
}
})

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